From f379ff9bf48d751e31d01ce1abec7e732f916049 Mon Sep 17 00:00:00 2001 From: Aaron Jensen Date: Mon, 16 Jun 2025 17:10:59 -0700 Subject: [PATCH] Schema and DataStructure functionality is unified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 26 ++-- lib/schema/controls.rb | 1 - lib/schema/controls/data_structure.rb | 130 ------------------ lib/schema/controls/schema.rb | 101 ++++++++++++++ lib/schema/data_structure.rb | 53 +------ lib/schema/schema.rb | 51 +++++++ .../raw_attributes_are_not_transformed.rb | 2 +- .../build/from_empty_hash.rb | 4 +- .../build/from_hash.rb | 4 +- .../{data_structure => schema}/clone.rb | 10 +- .../data_structure.rb | 4 +- .../{data_structure => schema}/dup.rb | 6 +- .../{data_structure => schema}/strictness.rb | 8 +- .../transform/transform_in_alias.rb | 4 +- .../schema/transform/transform_out_alias.rb | 2 +- .../transform/transform_read.rb | 4 +- .../schema/transform/transform_write.rb | 2 +- 17 files changed, 189 insertions(+), 223 deletions(-) delete mode 100644 lib/schema/controls/data_structure.rb rename test/automated/{data_structure => schema}/build/from_empty_hash.rb (56%) rename test/automated/{data_structure => schema}/build/from_hash.rb (67%) rename test/automated/{data_structure => schema}/clone.rb (80%) rename test/automated/{data_structure => schema}/data_structure.rb (63%) rename test/automated/{data_structure => schema}/dup.rb (82%) rename test/automated/{data_structure => schema}/strictness.rb (62%) rename test/automated/{data_structure => schema}/transform/transform_in_alias.rb (66%) rename test/automated/{data_structure => schema}/transform/transform_read.rb (69%) diff --git a/README.md b/README.md index 94dea1a..7785e8a 100644 --- a/README.md +++ b/README.md @@ -174,17 +174,13 @@ some_object.age # => Schema::Attribute::TypeError ``` -## Schema::DataStructure +## Building from Hash Data -The `DataStructure` module is a specialization of `Schema` that augments the receiver with operations that are useful when implementing typical applicative code. - -When `Schema::DataStructure` is included in a class, the class method `build` is defined on the class. - -The `build` method allows the class to be constructed from a hash of values whose keys correspond to the object's attribute names. +The `Schema` module includes a `build` class method that allows construction from a hash of values whose keys correspond to the object's attribute names. ```ruby class SomeClass - include Schema::DataStructure + include Schema attribute :name, String attribute :amount, Numeric @@ -336,7 +332,7 @@ A message that has nested objects that aren't just primitive values requires spe ### Input Data -A Schema::DataStructure that implements the `transform_read(data)` method can intercept the input data that the class is constructed with. The data can be modified and customized by this method, and the object's attributes can be manipulated. +A Schema that implements the `transform_read(data)` method can intercept the input data that the class is constructed with. The data can be modified and customized by this method, and the object's attributes can be manipulated. Note that the read stage of construction of a data structure from hash data happens before the input hash's attributes are assigned to the object. @@ -344,14 +340,14 @@ To affect changes to the input data, the `transform_read` method implementation ```ruby class Address - include Schema::DataStructure + include Schema attribute :city, String attribute :state, String end class SomeClass - include Schema::DataStructure + include Schema attribute :name, String attribute :address, Address @@ -367,7 +363,7 @@ The `transform_read` method is also aliased to `transform_in`. ### Output Data -A Schema::DataStructure that implements the `transform_write(data)` method can intercept the output data that the object outputs when either `to_h` or `attributes` is invoked. The data can be modified and customized by this method. +A Schema that implements the `transform_write(data)` method can intercept the output data that the object outputs when either `to_h` or `attributes` is invoked. The data can be modified and customized by this method. Note that the write stage of converting a data structure to a hash happens after the object's attributes have been converted to a hash but just before the hash data is returned to the receiver. @@ -375,14 +371,14 @@ To affect changes to the output data, the `transform_write` method implementatio ```ruby class Address - include Schema::DataStructure + include Schema attribute :city, String attribute :state, String end class SomeClass - include Schema::DataStructure + include Schema attribute :name, String attribute :address, Address @@ -441,7 +437,7 @@ some_object.raw_attributes ### Shallow Copy -A shallow copy duplicate of a `Schema::DataStructure` instance can be be created using the `dup` method. +A shallow copy duplicate of a `Schema` instance can be be created using the `dup` method. ```ruby class SomeClass @@ -472,7 +468,7 @@ duplicate.name ### Deep Copy -The `dup` method doesn't create a deep copy by default. A duplicate of an instance of `Schema::DataStructure` whose attributes have references to instances of complex types rather than just simple primitives will share the references to those complex types with the original instance. +The `dup` method doesn't create a deep copy by default. A duplicate of an instance of `Schema` whose attributes have references to instances of complex types rather than just simple primitives will share the references to those complex types with the original instance. As with the [transformation](#intercepting-and-modifying-input-and-output-data) of attributes, deep copy behavior can be implemented by implementing the `transform_read` and `transform_write` methods in order to replace attribute values that are instances of complex types with entirely new instances. diff --git a/lib/schema/controls.rb b/lib/schema/controls.rb index d5e85b5..9d9d11d 100644 --- a/lib/schema/controls.rb +++ b/lib/schema/controls.rb @@ -4,6 +4,5 @@ require 'schema/controls/attribute' require 'schema/controls/schema' require 'schema/controls/schema/different' -require 'schema/controls/data_structure' require 'schema/controls/comparison' require 'schema/controls/comparison/entry' diff --git a/lib/schema/controls/data_structure.rb b/lib/schema/controls/data_structure.rb deleted file mode 100644 index d708a94..0000000 --- a/lib/schema/controls/data_structure.rb +++ /dev/null @@ -1,130 +0,0 @@ -module Schema - module Controls - module DataStructure - def self.example - example = Example.new - example.some_attribute = some_attribute - example - end - - def self.some_attribute - Attribute::Value.some_attribute - end - - def self.ancestors - example.class.ancestors - end - - def self.attributes - example.attributes - end - - def self.hash - { - some_attribute: some_attribute - } - end - - class Example - include ::Schema::DataStructure - - attribute :some_attribute - end - - module ExtraAttributes - def self.data - { - :some_attribute => DataStructure.some_attribute, - :some_other_attribute => some_other_attribute - } - end - - def self.some_other_attribute - Attribute::Value.some_other_attribute - end - end - - module ConfigureDependencies - def self.example - Example.build - end - - class Example - include ::Schema::DataStructure - - attr_accessor :some_dependency - - def configure - self.some_dependency = :set - end - end - end - - module ReadAndWrite - def self.example - example = Example.new - example.some_attribute = DataStructure.some_attribute - example - end - - class Example - include ::Schema::DataStructure - - attribute :some_attribute - - def transform_read(data) - data[:some_attribute] = 'some read value' - end - - def transform_write(data) - data[:some_attribute] = 'some written value' - end - end - - module Data - def self.example - { - some_attribute: DataStructure.some_attribute - } - end - end - - module InAndOutAliases - def self.example - example = Example.new - example.some_attribute = DataStructure.some_attribute - example - end - - class Example - include ::Schema::DataStructure - - attribute :some_attribute - - def transform_in(data) - data[:some_attribute] = 'some read value' - end - - def transform_out(data) - data[:some_attribute] = 'some written value' - end - end - end - end - - module TransformReadFail - Error = Class.new(RuntimeError) - - class Example < DataStructure::Example - include ::Schema::DataStructure - - attribute :some_attribute - - def transform_read(data) - raise Error - end - end - end - end - end -end diff --git a/lib/schema/controls/schema.rb b/lib/schema/controls/schema.rb index c154986..8dbc25a 100644 --- a/lib/schema/controls/schema.rb +++ b/lib/schema/controls/schema.rb @@ -208,6 +208,107 @@ module Validator end end end + + module ExtraAttributes + def self.data + { + :some_attribute => Schema.some_attribute, + :some_other_attribute => some_other_attribute + } + end + + def self.some_other_attribute + Attribute::Value.some_other_attribute + end + + class Example + include ::Schema + attribute :some_attribute + end + end + + + module ConfigureDependencies + def self.example + Example.build + end + + class Example + include ::Schema + + attr_accessor :some_dependency + + def configure + self.some_dependency = :set + end + end + end + + module ReadAndWrite + def self.example + example = Example.new + example.some_attribute = Schema.some_attribute + example + end + + class Example + include ::Schema + + attribute :some_attribute + + def transform_read(data) + data[:some_attribute] = 'some read value' + end + + def transform_write(data) + data[:some_attribute] = 'some written value' + end + end + + module Data + def self.example + { + some_attribute: Schema.some_attribute + } + end + end + + module InAndOutAliases + def self.example + example = Example.new + example.some_attribute = Schema.some_attribute + example + end + + class Example + include ::Schema + + attribute :some_attribute + + def transform_in(data) + data[:some_attribute] = 'some read value' + end + + def transform_out(data) + data[:some_attribute] = 'some written value' + end + end + end + end + + module TransformReadFail + Error = Class.new(RuntimeError) + + class Example < Schema::Example + include ::Schema + + attribute :some_attribute + + def transform_read(data) + raise Error + end + end + end end end end diff --git a/lib/schema/data_structure.rb b/lib/schema/data_structure.rb index 26bda58..5629f23 100644 --- a/lib/schema/data_structure.rb +++ b/lib/schema/data_structure.rb @@ -1,61 +1,10 @@ module Schema module DataStructure def self.included(cls) + warn "Schema::DataStructure is deprecated. Use Schema directly instead." cls.class_exec do - include TemplateMethod include Schema - extend Build - - template_method :configure - template_method :configure_dependencies do - configure - end - - template_method :transform_read do |data| - transform_in(data) - end - template_method :transform_in end end - - module Build - def build(data=nil, strict=nil) - data ||= {} - strict ||= false - - new.tap do |instance| - if not data.empty? - instance.transform_read(data) - set_attributes(instance, data, strict) - end - - instance.configure_dependencies - end - end - - def set_attributes(instance, data, strict) - begin - SetAttributes.(instance, data, strict: strict) - rescue SetAttributes::Assign::Error => e - raise Schema::Error, e.message - end - end - end - - def deep_copy - data = to_h - self.class.build(data) - end - alias :dup :deep_copy - - def clone - duplicate = deep_copy - - if frozen? - duplicate.freeze - end - - duplicate - end end end diff --git a/lib/schema/schema.rb b/lib/schema/schema.rb index 19cef7f..30c9a5b 100644 --- a/lib/schema/schema.rb +++ b/lib/schema/schema.rb @@ -6,9 +6,20 @@ def self.included(cls) extend Info extend AttributeMacro extend Attributes + extend Build include TemplateMethod + template_method :configure + template_method :configure_dependencies do + configure + end + + template_method :transform_read do |data| + transform_in(data) + end + template_method :transform_in + template_method :transform_write do |data| transform_out(data) end @@ -211,4 +222,44 @@ def ==(other, attributes_names=nil, ignore_class: nil) def hash [self.class, raw_attributes].hash end + + module Build + def build(data=nil, strict=nil) + data ||= {} + strict ||= false + + new.tap do |instance| + if not data.empty? + instance.transform_read(data) + set_attributes(instance, data, strict) + end + + instance.configure_dependencies + end + end + + def set_attributes(instance, data, strict) + begin + SetAttributes.(instance, data, strict: strict) + rescue SetAttributes::Assign::Error => e + raise Schema::Error, e.message + end + end + end + + def deep_copy + data = to_h + self.class.build(data) + end + alias :dup :deep_copy + + def clone + duplicate = deep_copy + + if frozen? + duplicate.freeze + end + + duplicate + end end diff --git a/test/automated/schema/attributes/instance/raw_attributes/raw_attributes_are_not_transformed.rb b/test/automated/schema/attributes/instance/raw_attributes/raw_attributes_are_not_transformed.rb index 5a13613..582ed3f 100644 --- a/test/automated/schema/attributes/instance/raw_attributes/raw_attributes_are_not_transformed.rb +++ b/test/automated/schema/attributes/instance/raw_attributes/raw_attributes_are_not_transformed.rb @@ -4,7 +4,7 @@ context "Instance" do context "Raw Attributes" do context "Raw Attributes Are Not Transformed" do - data_structure = Schema::Controls::DataStructure::ReadAndWrite.example + data_structure = Schema::Controls::Schema::ReadAndWrite.example data = data_structure.raw_attributes diff --git a/test/automated/data_structure/build/from_empty_hash.rb b/test/automated/schema/build/from_empty_hash.rb similarity index 56% rename from test/automated/data_structure/build/from_empty_hash.rb rename to test/automated/schema/build/from_empty_hash.rb index ae29144..6f24278 100644 --- a/test/automated/data_structure/build/from_empty_hash.rb +++ b/test/automated/schema/build/from_empty_hash.rb @@ -4,8 +4,8 @@ context "Build" do context "From Empty Hash" do test "Data is not transformed" do - refute_raises Schema::Controls::DataStructure::TransformReadFail::Error do - Schema::Controls::DataStructure::TransformReadFail::Example.build + refute_raises Schema::Controls::Schema::TransformReadFail::Error do + Schema::Controls::Schema::TransformReadFail::Example.build end end end diff --git a/test/automated/data_structure/build/from_hash.rb b/test/automated/schema/build/from_hash.rb similarity index 67% rename from test/automated/data_structure/build/from_hash.rb rename to test/automated/schema/build/from_hash.rb index 263898f..25a5dca 100644 --- a/test/automated/data_structure/build/from_hash.rb +++ b/test/automated/schema/build/from_hash.rb @@ -3,8 +3,8 @@ context "Data Structure" do context "Build" do context "From Hash" do - data = Schema::Controls::DataStructure.hash - data_structure = Schema::Controls::DataStructure::Example.build data + data = Schema::Controls::Schema.hash + data_structure = Schema::Controls::Schema::Example.build data test "Hash values are the attributes' values" do assert(data_structure.some_attribute == 'some value') diff --git a/test/automated/data_structure/clone.rb b/test/automated/schema/clone.rb similarity index 80% rename from test/automated/data_structure/clone.rb rename to test/automated/schema/clone.rb index 0c9720b..9d407fb 100644 --- a/test/automated/data_structure/clone.rb +++ b/test/automated/schema/clone.rb @@ -3,7 +3,7 @@ context "Data Structure" do context "Clone" do context "Shallow Copy" do - data_structure = Schema::Controls::DataStructure.example + data_structure = Schema::Controls::Schema.example duplicate = data_structure.clone @@ -19,11 +19,11 @@ end context "Deep Copy Via Transformation" do - data = Schema::Controls::DataStructure.hash + data = Schema::Controls::Schema.hash refute(data[:some_attribute] == 'some read value') - data_structure = Schema::Controls::DataStructure::ReadAndWrite::Example.build(data) + data_structure = Schema::Controls::Schema::ReadAndWrite::Example.build(data) duplicate = data_structure.clone @@ -38,7 +38,7 @@ context "Frozen" do context "Frozen Data Structure" do - data_structure = Schema::Controls::DataStructure.example + data_structure = Schema::Controls::Schema.example data_structure.freeze @@ -50,7 +50,7 @@ end context "Not Frozen" do - data_structure = Schema::Controls::DataStructure.example + data_structure = Schema::Controls::Schema.example duplicate = data_structure.clone diff --git a/test/automated/data_structure/data_structure.rb b/test/automated/schema/data_structure.rb similarity index 63% rename from test/automated/data_structure/data_structure.rb rename to test/automated/schema/data_structure.rb index 54c7436..e56074a 100644 --- a/test/automated/data_structure/data_structure.rb +++ b/test/automated/schema/data_structure.rb @@ -2,12 +2,12 @@ context "Data Structure" do test "Is a Schema object" do - ancestors = Schema::Controls::DataStructure.ancestors + ancestors = Schema::Controls::Schema.ancestors assert(ancestors.include? Schema) end test "Can configure its dependencies" do - example = Schema::Controls::DataStructure::ConfigureDependencies.example + example = Schema::Controls::Schema::ConfigureDependencies.example assert(example.some_dependency == :set) end end diff --git a/test/automated/data_structure/dup.rb b/test/automated/schema/dup.rb similarity index 82% rename from test/automated/data_structure/dup.rb rename to test/automated/schema/dup.rb index 793265e..dfcd0ec 100644 --- a/test/automated/data_structure/dup.rb +++ b/test/automated/schema/dup.rb @@ -3,7 +3,7 @@ context "Data Structure" do context "Dup" do context "Shallow Copy" do - data_structure = Schema::Controls::DataStructure.example + data_structure = Schema::Controls::Schema.example duplicate = data_structure.dup @@ -19,11 +19,11 @@ end context "Deep Copy Via Transformation" do - data = Schema::Controls::DataStructure.hash + data = Schema::Controls::Schema.hash refute(data[:some_attribute] == 'some read value') - data_structure = Schema::Controls::DataStructure::ReadAndWrite::Example.build(data) + data_structure = Schema::Controls::Schema::ReadAndWrite::Example.build(data) duplicate = data_structure.dup diff --git a/test/automated/data_structure/strictness.rb b/test/automated/schema/strictness.rb similarity index 62% rename from test/automated/data_structure/strictness.rb rename to test/automated/schema/strictness.rb index e5309b9..ddba6b2 100644 --- a/test/automated/data_structure/strictness.rb +++ b/test/automated/schema/strictness.rb @@ -4,8 +4,8 @@ context "Strictness" do context "Hash with extra attributes" do context "Not Strict (Default)" do - data = Schema::Controls::DataStructure::ExtraAttributes.data - data_structure = Schema::Controls::DataStructure::Example.build(data) + data = Schema::Controls::Schema::ExtraAttributes.data + data_structure = Schema::Controls::Schema::ExtraAttributes::Example.build(data) test "Sets matching attributes" do assert(data_structure.some_attribute == 'some value') @@ -15,11 +15,11 @@ context "Strict" do strict = true - data = Schema::Controls::DataStructure::ExtraAttributes.data + data = Schema::Controls::Schema::ExtraAttributes.data test "Is incorrect" do assert_raises(Schema::Error) do - Schema::Controls::DataStructure::Example.build(data, strict) + Schema::Controls::Schema::ExtraAttributes::Example.build(data, strict) end end end diff --git a/test/automated/data_structure/transform/transform_in_alias.rb b/test/automated/schema/transform/transform_in_alias.rb similarity index 66% rename from test/automated/data_structure/transform/transform_in_alias.rb rename to test/automated/schema/transform/transform_in_alias.rb index 6f48158..ba77624 100644 --- a/test/automated/data_structure/transform/transform_in_alias.rb +++ b/test/automated/schema/transform/transform_in_alias.rb @@ -2,11 +2,11 @@ context "Data Structure" do context "Transform In Alias" do - data = Schema::Controls::DataStructure.hash + data = Schema::Controls::Schema.hash refute(data[:some_attribute] == 'some read value') - data_structure = Schema::Controls::DataStructure::ReadAndWrite::InAndOutAliases::Example.build(data) + data_structure = Schema::Controls::Schema::ReadAndWrite::InAndOutAliases::Example.build(data) test "The input data has been intercepted and modified" do assert(data_structure.some_attribute == 'some read value') diff --git a/test/automated/schema/transform/transform_out_alias.rb b/test/automated/schema/transform/transform_out_alias.rb index 3ab7927..de51f13 100644 --- a/test/automated/schema/transform/transform_out_alias.rb +++ b/test/automated/schema/transform/transform_out_alias.rb @@ -2,7 +2,7 @@ context "Schema" do context "Transform Out Alias" do - data_structure = Schema::Controls::DataStructure::ReadAndWrite.example + data_structure = Schema::Controls::Schema::ReadAndWrite.example refute(data_structure.some_attribute == 'some written value') diff --git a/test/automated/data_structure/transform/transform_read.rb b/test/automated/schema/transform/transform_read.rb similarity index 69% rename from test/automated/data_structure/transform/transform_read.rb rename to test/automated/schema/transform/transform_read.rb index ca8e742..d571ded 100644 --- a/test/automated/data_structure/transform/transform_read.rb +++ b/test/automated/schema/transform/transform_read.rb @@ -2,11 +2,11 @@ context "Data Structure" do context "Transform Read" do - data = Schema::Controls::DataStructure.hash + data = Schema::Controls::Schema.hash refute(data[:some_attribute] == 'some read value') - data_structure = Schema::Controls::DataStructure::ReadAndWrite::Example.build(data) + data_structure = Schema::Controls::Schema::ReadAndWrite::Example.build(data) test "The input data has been intercepted and modified" do assert(data_structure.some_attribute == 'some read value') diff --git a/test/automated/schema/transform/transform_write.rb b/test/automated/schema/transform/transform_write.rb index 333efe8..27659ca 100644 --- a/test/automated/schema/transform/transform_write.rb +++ b/test/automated/schema/transform/transform_write.rb @@ -2,7 +2,7 @@ context "Schema" do context "Transform Write" do - data_structure = Schema::Controls::DataStructure::ReadAndWrite.example + data_structure = Schema::Controls::Schema::ReadAndWrite.example refute(data_structure.some_attribute == 'some written value')