From 795b27a8e028c371f4398141e840e899b72c2601 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 6 Jun 2024 21:58:18 +0700 Subject: [PATCH 01/10] Add getter and setter DSL to Datory Added getter and setter Domain Specific Language (DSL) as features to the Datory framework. This change includes creation of new files for getter, setter, and their collection classes. Moreover, adjustments were made in the test files and examples to demonstrate the usage of these newly added features. --- examples/usual/example2/product.rb | 9 ++++++ lib/datory/attributes/serialization/model.rb | 8 ++++++ lib/datory/attributes/workspace.rb | 4 +-- lib/datory/base.rb | 2 ++ lib/datory/context/callable.rb | 6 ++-- lib/datory/context/workspace.rb | 16 +++++++---- lib/datory/getters/collection.rb | 18 ++++++++++++ lib/datory/getters/dsl.rb | 30 ++++++++++++++++++++ lib/datory/getters/getter.rb | 14 +++++++++ lib/datory/getters/workspace.rb | 17 +++++++++++ lib/datory/setters/collection.rb | 18 ++++++++++++ lib/datory/setters/dsl.rb | 30 ++++++++++++++++++++ lib/datory/setters/setter.rb | 14 +++++++++ lib/datory/setters/workspace.rb | 19 +++++++++++++ spec/examples/usual/example2/product_spec.rb | 27 ++++++++++++++++++ 15 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 lib/datory/getters/collection.rb create mode 100644 lib/datory/getters/dsl.rb create mode 100644 lib/datory/getters/getter.rb create mode 100644 lib/datory/getters/workspace.rb create mode 100644 lib/datory/setters/collection.rb create mode 100644 lib/datory/setters/dsl.rb create mode 100644 lib/datory/setters/setter.rb create mode 100644 lib/datory/setters/workspace.rb diff --git a/examples/usual/example2/product.rb b/examples/usual/example2/product.rb index b109472..69d79bc 100644 --- a/examples/usual/example2/product.rb +++ b/examples/usual/example2/product.rb @@ -6,6 +6,7 @@ class Product < Datory::Base uuid! :id string! :title + string! :formatted_title money! :price money? :discount @@ -13,6 +14,14 @@ class Product < Datory::Base integer! :quantity, min: 1, max: 10 duration? :installmentDuration, to: :installment_duration + + getter :formatted_title do |attributes:| + "The New #{attributes.fetch(:title)} (from getter)" + end + + setter :formatted_title do |attributes:| + "The New #{attributes.fetch(:title)} (from setter)" + end end end end diff --git a/lib/datory/attributes/serialization/model.rb b/lib/datory/attributes/serialization/model.rb index 3f81225..693edd5 100644 --- a/lib/datory/attributes/serialization/model.rb +++ b/lib/datory/attributes/serialization/model.rb @@ -47,6 +47,14 @@ def build(attributes = {}) # rubocop:disable Metrics/MethodLength self end + def add(key, value) + self.class.send(:attr_accessor, key) + + instance_variable_set(:"@#{key}", value) + + self + end + def parse(data) # rubocop:disable Metrics/MethodLength data.instance_variables.to_h do |key| value = data.instance_variable_get(key) diff --git a/lib/datory/attributes/workspace.rb b/lib/datory/attributes/workspace.rb index 7d328f1..77e5b1a 100644 --- a/lib/datory/attributes/workspace.rb +++ b/lib/datory/attributes/workspace.rb @@ -5,7 +5,7 @@ module Attributes module Workspace private - def serialize(model:, collection_of_attributes:) + def serialize(model:, collection_of_attributes:, **) super return nil if model.nil? # NOTE: When `one` is optional and not passed @@ -18,7 +18,7 @@ def serialize(model:, collection_of_attributes:) ) end - def deserialize(incoming_attributes:, collection_of_attributes:) + def deserialize(incoming_attributes:, collection_of_attributes:, **) super Deserialization::ServiceBuilder.build!(self, incoming_attributes, collection_of_attributes) diff --git a/lib/datory/base.rb b/lib/datory/base.rb index fb1bbf5..0f81fb1 100644 --- a/lib/datory/base.rb +++ b/lib/datory/base.rb @@ -4,6 +4,8 @@ module Datory class Base include Info::DSL include Context::DSL + include Getters::DSL + include Setters::DSL include Attributes::DSL end end diff --git a/lib/datory/context/callable.rb b/lib/datory/context/callable.rb index 117b5ef..dfd0113 100644 --- a/lib/datory/context/callable.rb +++ b/lib/datory/context/callable.rb @@ -67,7 +67,8 @@ def _serialize(context, model) context.send( :_serialize, model: model, - collection_of_attributes: collection_of_attributes + collection_of_attributes: collection_of_attributes, + collection_of_setters: collection_of_setters ) end @@ -75,7 +76,8 @@ def _deserialize(context, **attributes) context.send( :_deserialize, incoming_attributes: attributes.symbolize_keys, - collection_of_attributes: collection_of_attributes + collection_of_attributes: collection_of_attributes, + collection_of_getters: collection_of_getters ) end diff --git a/lib/datory/context/workspace.rb b/lib/datory/context/workspace.rb index f436f7d..c008de1 100644 --- a/lib/datory/context/workspace.rb +++ b/lib/datory/context/workspace.rb @@ -3,17 +3,19 @@ module Datory module Context module Workspace - def _serialize(model:, collection_of_attributes:) + def _serialize(model:, collection_of_attributes:, collection_of_setters:) serialize( model: model, - collection_of_attributes: collection_of_attributes + collection_of_attributes: collection_of_attributes, + collection_of_setters: collection_of_setters ) end - def _deserialize(incoming_attributes:, collection_of_attributes:) + def _deserialize(incoming_attributes:, collection_of_attributes:, collection_of_getters:) deserialize( incoming_attributes: incoming_attributes, - collection_of_attributes: collection_of_attributes + collection_of_attributes: collection_of_attributes, + collection_of_getters: collection_of_getters ) end @@ -23,14 +25,16 @@ def _to_model(attributes:) ) end - def serialize(model:, collection_of_attributes:, **) + def serialize(model:, collection_of_attributes:, collection_of_setters:, **) @model = model @collection_of_attributes = collection_of_attributes + @collection_of_setters = collection_of_setters end - def deserialize(incoming_attributes:, collection_of_attributes:, **) + def deserialize(incoming_attributes:, collection_of_attributes:, collection_of_getters:, **) @incoming_attributes = incoming_attributes @collection_of_attributes = collection_of_attributes + @collection_of_getters = collection_of_getters end def to_model(attributes:) diff --git a/lib/datory/getters/collection.rb b/lib/datory/getters/collection.rb new file mode 100644 index 0000000..e8ba8da --- /dev/null +++ b/lib/datory/getters/collection.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Datory + module Getters + class Collection + extend Forwardable + def_delegators :@collection, :<<, :each, :merge + + def initialize(collection = Set.new) + @collection = collection + end + + # def find_by(name:) + # find { |getter| getter.name == name } + # end + end + end +end diff --git a/lib/datory/getters/dsl.rb b/lib/datory/getters/dsl.rb new file mode 100644 index 0000000..3bdcdd3 --- /dev/null +++ b/lib/datory/getters/dsl.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Datory + module Getters + module DSL + def self.included(base) + base.extend(ClassMethods) + base.include(Workspace) + end + + module ClassMethods + def inherited(child) + super + + child.send(:collection_of_getters).merge(collection_of_getters) + end + + private + + def getter(name) + collection_of_getters << Getter.new(name, ->(attributes:) { yield(attributes: attributes) }) + end + + def collection_of_getters + @collection_of_getters ||= Collection.new + end + end + end + end +end diff --git a/lib/datory/getters/getter.rb b/lib/datory/getters/getter.rb new file mode 100644 index 0000000..1890cda --- /dev/null +++ b/lib/datory/getters/getter.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Datory + module Getters + class Getter + attr_reader :name, :block + + def initialize(name, block) + @name = name + @block = block + end + end + end +end diff --git a/lib/datory/getters/workspace.rb b/lib/datory/getters/workspace.rb new file mode 100644 index 0000000..99e1a95 --- /dev/null +++ b/lib/datory/getters/workspace.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Datory + module Getters + module Workspace + private + + def deserialize(incoming_attributes:, collection_of_attributes:, collection_of_getters:) + super + + collection_of_getters.each do |getter| + incoming_attributes.merge!(getter.name => getter.block.call(attributes: incoming_attributes)) + end + end + end + end +end diff --git a/lib/datory/setters/collection.rb b/lib/datory/setters/collection.rb new file mode 100644 index 0000000..cb0b84e --- /dev/null +++ b/lib/datory/setters/collection.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Datory + module Setters + class Collection + extend Forwardable + def_delegators :@collection, :<<, :each, :merge + + def initialize(collection = Set.new) + @collection = collection + end + + # def find_by(name:) + # find { |getter| getter.name == name } + # end + end + end +end diff --git a/lib/datory/setters/dsl.rb b/lib/datory/setters/dsl.rb new file mode 100644 index 0000000..aae6949 --- /dev/null +++ b/lib/datory/setters/dsl.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Datory + module Setters + module DSL + def self.included(base) + base.extend(ClassMethods) + base.include(Workspace) + end + + module ClassMethods + def inherited(child) + super + + child.send(:collection_of_setters).merge(collection_of_setters) + end + + private + + def setter(name) + collection_of_setters << Setter.new(name, ->(attributes:) { yield(attributes: attributes) }) + end + + def collection_of_setters + @collection_of_setters ||= Collection.new + end + end + end + end +end diff --git a/lib/datory/setters/setter.rb b/lib/datory/setters/setter.rb new file mode 100644 index 0000000..c082e85 --- /dev/null +++ b/lib/datory/setters/setter.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Datory + module Setters + class Setter + attr_reader :name, :block + + def initialize(name, block) + @name = name + @block = block + end + end + end +end diff --git a/lib/datory/setters/workspace.rb b/lib/datory/setters/workspace.rb new file mode 100644 index 0000000..bd286cb --- /dev/null +++ b/lib/datory/setters/workspace.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Datory + module Setters + module Workspace + private + + def serialize(model:, collection_of_attributes:, collection_of_setters:) + super + + collection_of_setters.each do |setter| + hash = Datory::Attributes::Serialization::Model.to_hash(model) + + model.add(setter.name, setter.block.call(attributes: hash)) + end + end + end + end +end diff --git a/spec/examples/usual/example2/product_spec.rb b/spec/examples/usual/example2/product_spec.rb index e322e13..dfba2a0 100644 --- a/spec/examples/usual/example2/product_spec.rb +++ b/spec/examples/usual/example2/product_spec.rb @@ -137,6 +137,7 @@ { id: "55363a14-aa9a-4eba-9276-7f7cec432123", title: "iPhone 15 Pro", + formatted_title: "The New iPhone 15 Pro (from setter)", price_cents: 999_00, price_currency: "USD", discount_cents: nil, @@ -158,6 +159,7 @@ { id: "55363a14-aa9a-4eba-9276-7f7cec432123", title: "iPhone 15 Pro", + formatted_title: "The New iPhone 15 Pro (from setter)", price_cents: 999_00, price_currency: "USD", discount_cents: nil, @@ -211,6 +213,7 @@ { id: "55363a14-aa9a-4eba-9276-7f7cec432123", title: "iPhone 15 Pro", + formatted_title: "The New iPhone 15 Pro (from setter)", price_cents: 999_00, price_currency: "USD", quantity: 5 @@ -236,6 +239,7 @@ have_attributes( id: "55363a14-aa9a-4eba-9276-7f7cec432123", title: "iPhone 15 Pro", + formatted_title: "The New iPhone 15 Pro (from getter)", price_cents: 999_00, price_currency: "USD", quantity: 5 @@ -350,6 +354,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | id | String | id | String | | title | String | title | String | + | formatted_title | String | formatted_title | String | | price_cents | Integer | price_cents | Integer | | price_currency | String | price_currency | String | | discount_cents | [Integer, NilClass] | discount_cents | [Integer, NilClass] | @@ -379,6 +384,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | id | String | id | String | | title | String | title | String | + | formatted_title | String | formatted_title | String | | price_cents | Integer | price_cents | Integer | | price_currency | String | price_currency | String | | discount_cents | [Integer, NilClass] | discount_cents | [Integer, NilClass] | @@ -444,6 +450,27 @@ include: nil } }, + formatted_title: { + from: { + name: :formatted_title, + type: String, + min: nil, + max: nil, + consists_of: false, + format: nil + }, + to: { + name: :formatted_title, + type: String, + required: true, + default: nil, + min: nil, + max: nil, + consists_of: false, + format: nil, + include: nil + } + }, price_cents: { from: { name: :price_cents, From a1a94fbcb6efdc8392c0db7f5662d15f8bba9e09 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 6 Jun 2024 22:10:15 +0700 Subject: [PATCH 02/10] Refactor formatted_title to formattedTitle in product model and test Changed attribute name 'formatted_title' to 'formattedTitle' in both product.rb file and product_spec.rb test file. The renaming is consistent with camelCase naming convention, improving readability and standardizing the codebase. --- examples/usual/example2/product.rb | 4 ++-- spec/examples/usual/example2/product_spec.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/usual/example2/product.rb b/examples/usual/example2/product.rb index 69d79bc..1cd3d93 100644 --- a/examples/usual/example2/product.rb +++ b/examples/usual/example2/product.rb @@ -6,7 +6,7 @@ class Product < Datory::Base uuid! :id string! :title - string! :formatted_title + string! :formattedTitle, to: :formatted_title money! :price money? :discount @@ -15,7 +15,7 @@ class Product < Datory::Base duration? :installmentDuration, to: :installment_duration - getter :formatted_title do |attributes:| + getter :formattedTitle do |attributes:| "The New #{attributes.fetch(:title)} (from getter)" end diff --git a/spec/examples/usual/example2/product_spec.rb b/spec/examples/usual/example2/product_spec.rb index dfba2a0..78b2387 100644 --- a/spec/examples/usual/example2/product_spec.rb +++ b/spec/examples/usual/example2/product_spec.rb @@ -137,7 +137,7 @@ { id: "55363a14-aa9a-4eba-9276-7f7cec432123", title: "iPhone 15 Pro", - formatted_title: "The New iPhone 15 Pro (from setter)", + formattedTitle: "The New iPhone 15 Pro (from setter)", price_cents: 999_00, price_currency: "USD", discount_cents: nil, @@ -159,7 +159,7 @@ { id: "55363a14-aa9a-4eba-9276-7f7cec432123", title: "iPhone 15 Pro", - formatted_title: "The New iPhone 15 Pro (from setter)", + formattedTitle: "The New iPhone 15 Pro (from setter)", price_cents: 999_00, price_currency: "USD", discount_cents: nil, @@ -354,7 +354,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | id | String | id | String | | title | String | title | String | - | formatted_title | String | formatted_title | String | + | formattedTitle | String | formatted_title | String | | price_cents | Integer | price_cents | Integer | | price_currency | String | price_currency | String | | discount_cents | [Integer, NilClass] | discount_cents | [Integer, NilClass] | @@ -384,7 +384,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | id | String | id | String | | title | String | title | String | - | formatted_title | String | formatted_title | String | + | formattedTitle | String | formatted_title | String | | price_cents | Integer | price_cents | Integer | | price_currency | String | price_currency | String | | discount_cents | [Integer, NilClass] | discount_cents | [Integer, NilClass] | @@ -450,9 +450,9 @@ include: nil } }, - formatted_title: { + formattedTitle: { from: { - name: :formatted_title, + name: :formattedTitle, type: String, min: nil, max: nil, From f652b4f459c9cc9d04e5e1faabefc4afd6dc2a8e Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 6 Jun 2024 22:13:06 +0700 Subject: [PATCH 03/10] Remove commented out find_by method The find_by method in both the setters/collection.rb and getters/collection.rb files has been commented out and is no longer in use. This commit removes these unnecessary lines of code, thus cleaning up the codebase. --- lib/datory/getters/collection.rb | 4 ---- lib/datory/setters/collection.rb | 4 ---- 2 files changed, 8 deletions(-) diff --git a/lib/datory/getters/collection.rb b/lib/datory/getters/collection.rb index e8ba8da..6d22935 100644 --- a/lib/datory/getters/collection.rb +++ b/lib/datory/getters/collection.rb @@ -9,10 +9,6 @@ class Collection def initialize(collection = Set.new) @collection = collection end - - # def find_by(name:) - # find { |getter| getter.name == name } - # end end end end diff --git a/lib/datory/setters/collection.rb b/lib/datory/setters/collection.rb index cb0b84e..e37d3c2 100644 --- a/lib/datory/setters/collection.rb +++ b/lib/datory/setters/collection.rb @@ -9,10 +9,6 @@ class Collection def initialize(collection = Set.new) @collection = collection end - - # def find_by(name:) - # find { |getter| getter.name == name } - # end end end end From 34caf01b7441c1881e9897345e838a5808494336 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 16:08:31 +0700 Subject: [PATCH 04/10] Refactor setter definition to improve encapsulation Moved the `add` method logic into a private method `define_setter` to enhance encapsulation within the `Model` class. Updated all usages of the `add` method to call `define_setter` instead, ensuring the setter is defined privately across different classes. --- lib/datory/attributes/serialization/model.rb | 18 ++++++++++-------- lib/datory/context/workspace.rb | 8 ++++++++ lib/datory/setters/workspace.rb | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/datory/attributes/serialization/model.rb b/lib/datory/attributes/serialization/model.rb index c3d395f..50dfad7 100644 --- a/lib/datory/attributes/serialization/model.rb +++ b/lib/datory/attributes/serialization/model.rb @@ -47,14 +47,6 @@ def build(attributes = {}) # rubocop:disable Metrics/MethodLength self end - def add(key, value) - self.class.send(:attr_accessor, key) - - instance_variable_set(:"@#{key}", value) - - self - end - def parse(data) # rubocop:disable Metrics/MethodLength data.instance_variables.to_h do |key| value = data.instance_variable_get(key) @@ -71,6 +63,16 @@ def parse(data) # rubocop:disable Metrics/MethodLength [key.to_s.delete_prefix("@").to_sym, value] end end + + private + + def define_setter(key, value) + self.class.send(:attr_accessor, key) + + instance_variable_set(:"@#{key}", value) + + self + end end end end diff --git a/lib/datory/context/workspace.rb b/lib/datory/context/workspace.rb index cf08a09..85a3198 100644 --- a/lib/datory/context/workspace.rb +++ b/lib/datory/context/workspace.rb @@ -5,6 +5,14 @@ module Context module Workspace private + def define_setter(key, value) + self.class.send(:attr_accessor, key) + + instance_variable_set(:"@#{key}", value) + + self + end + def merge!(attributes) attributes.each do |key, value| instance_variable_set(:"@#{key}", value) diff --git a/lib/datory/setters/workspace.rb b/lib/datory/setters/workspace.rb index bd286cb..7af9e79 100644 --- a/lib/datory/setters/workspace.rb +++ b/lib/datory/setters/workspace.rb @@ -11,7 +11,7 @@ def serialize(model:, collection_of_attributes:, collection_of_setters:) collection_of_setters.each do |setter| hash = Datory::Attributes::Serialization::Model.to_hash(model) - model.add(setter.name, setter.block.call(attributes: hash)) + model.send(:define_setter, setter.name, setter.block.call(attributes: hash)) end end end From 7a3c7deb4e31d48197b613bb253cbe05358f573d Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 16:12:41 +0700 Subject: [PATCH 05/10] Refactor --- lib/datory/attributes/serialization/model.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/datory/attributes/serialization/model.rb b/lib/datory/attributes/serialization/model.rb index 50dfad7..4451dbe 100644 --- a/lib/datory/attributes/serialization/model.rb +++ b/lib/datory/attributes/serialization/model.rb @@ -28,6 +28,16 @@ def to_hash(data) end end + private + + def define_setter(key, value) + self.class.send(:attr_accessor, key) + + instance_variable_set(:"@#{key}", value) + + self + end + def build(attributes = {}) # rubocop:disable Metrics/MethodLength attributes.each do |key, value| self.class.send(:attr_accessor, key) @@ -63,16 +73,6 @@ def parse(data) # rubocop:disable Metrics/MethodLength [key.to_s.delete_prefix("@").to_sym, value] end end - - private - - def define_setter(key, value) - self.class.send(:attr_accessor, key) - - instance_variable_set(:"@#{key}", value) - - self - end end end end From 368001eac31cda675c7a1127858b0f00bb5cca17 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 16:36:21 +0700 Subject: [PATCH 06/10] Add 'code' attribute and its getter/setter to season model This commit introduces a new 'code' attribute to the season model, derived from the season number. Modifications include updating the schema and test cases to accommodate the new attribute. These changes ensure that each season has a unique code based on its number. --- examples/usual/example1/season.rb | 9 +++++++++ spec/examples/usual/example1/serial_spec.rb | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/examples/usual/example1/season.rb b/examples/usual/example1/season.rb index 4c86af5..6c8f9cd 100644 --- a/examples/usual/example1/season.rb +++ b/examples/usual/example1/season.rb @@ -7,11 +7,20 @@ class Season < Datory::Base # uuid! :serialId, to: :serial_id integer! :number + string! :code # many! :episodes, include: Episode date! :premieredOn, to: :premiered_on date? :endedOn, to: :ended_on + + getter :code do |attributes:| + "s#{attributes.fetch(:number)}" + end + + setter :code do |attributes:| + "s#{attributes.fetch(:number)}" + end end end end diff --git a/spec/examples/usual/example1/serial_spec.rb b/spec/examples/usual/example1/serial_spec.rb index b2d7135..1b429c8 100644 --- a/spec/examples/usual/example1/serial_spec.rb +++ b/spec/examples/usual/example1/serial_spec.rb @@ -296,6 +296,7 @@ { id: "27df8a44-556f-4e08-9984-4aa663b78f98", number: 1, + code: "s1", premieredOn: "2008-09-03", endedOn: "2008-11-26" } @@ -354,6 +355,7 @@ { id: "27df8a44-556f-4e08-9984-4aa663b78f98", number: 1, + code: "s1", premieredOn: "2008-09-03", endedOn: "2008-11-26" } @@ -1203,6 +1205,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | id | String | id | String | | number | Integer | number | Integer | + | code | String | code | String | | premieredOn | String | premiered_on | Date | | endedOn | [String, NilClass] | ended_on | [Date, NilClass] | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1319,6 +1322,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | id | String | id | String | | number | Integer | number | Integer | + | code | String | code | String | | premieredOn | String | premiered_on | Date | | endedOn | [String, NilClass] | ended_on | [Date, NilClass] | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 12a5c073d793d027e5374ac75c8204bc313c7bd1 Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 17:24:52 +0700 Subject: [PATCH 07/10] Add `external_names` method to `Collection` class This new method extracts and returns the names from the `from` attributes within the collection. It complements the existing method that fetches the `to` names, enhancing the overall utility of attribute name extraction within the `Collection` class. --- lib/datory/attributes/collection.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/datory/attributes/collection.rb b/lib/datory/attributes/collection.rb index c9c8053..28fee8a 100644 --- a/lib/datory/attributes/collection.rb +++ b/lib/datory/attributes/collection.rb @@ -18,6 +18,10 @@ def internal_names map { |attribute| attribute.to.name } end + def external_names + map { |attribute| attribute.from.name } + end + def include_class_exist? @include_class_exist ||= filter do |attribute| # rubocop:disable Performance/Count include_class = attribute.to.include_class From e82cb5c9c766ec5f1c8b9282fe412f6f56acaeae Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 17:25:16 +0700 Subject: [PATCH 08/10] Refactor prepare and build methods for attribute handling Refactor methods in Model and Callable to include `collection_of_attributes` for more robust attribute management. Adjust build method to handle additional attributes, ensuring complete initialization. Update related methods to pass and process `collection_of_attributes` accordingly. --- lib/datory/attributes/serialization/model.rb | 19 +++++++++++++------ lib/datory/context/callable.rb | 11 +++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/datory/attributes/serialization/model.rb b/lib/datory/attributes/serialization/model.rb index 4451dbe..d04336e 100644 --- a/lib/datory/attributes/serialization/model.rb +++ b/lib/datory/attributes/serialization/model.rb @@ -12,9 +12,9 @@ def self.to_hash(...) new.to_hash(...) end - def prepare(data) + def prepare(data, collection_of_attributes) if data.is_a?(Hash) - build(data.deep_dup) + build(collection_of_attributes, data.deep_dup) else data end @@ -38,17 +38,24 @@ def define_setter(key, value) self end - def build(attributes = {}) # rubocop:disable Metrics/MethodLength - attributes.each do |key, value| + def build(collection_of_attributes, attributes = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + additional_attributes = collection_of_attributes.internal_names.difference(attributes.keys).to_h do |key| + [key, nil] + end + + attributes.merge(additional_attributes).each do |key, value| self.class.send(:attr_accessor, key) instance_variable_set(:"@#{key}", value) if value.is_a?(Array) - value.map! { |item| Datory::Attributes::Serialization::Model.prepare(item) } + value.map! { |item| Datory::Attributes::Serialization::Model.prepare(item, collection_of_attributes) } instance_variable_set(:"@#{key}", value) elsif value.is_a?(Hash) - instance_variable_set(:"@#{key}", Datory::Attributes::Serialization::Model.prepare(value)) + instance_variable_set( + :"@#{key}", + Datory::Attributes::Serialization::Model.prepare(value, collection_of_attributes) + ) else instance_variable_set(:"@#{key}", value) end diff --git a/lib/datory/context/callable.rb b/lib/datory/context/callable.rb index 1225ad4..4fdcc80 100644 --- a/lib/datory/context/callable.rb +++ b/lib/datory/context/callable.rb @@ -14,7 +14,7 @@ def serialize(model) # rubocop:disable Metrics/MethodLength end else context = send(:new, _datory_to_model: false) - model = Datory::Attributes::Serialization::Model.prepare(model) + model = Datory::Attributes::Serialization::Model.prepare(model, collection_of_attributes) _serialize(context, model) end rescue Datory::Service::Exceptions::Input, @@ -23,7 +23,7 @@ def serialize(model) # rubocop:disable Metrics/MethodLength raise Datory::Exceptions::SerializationError.new(message: e.message) end - def deserialize(data) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize + def deserialize(data) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity prepared_data = if data.is_a?(Datory::Base) Datory::Attributes::Serialization::Model.to_hash(data) @@ -40,6 +40,13 @@ def deserialize(data) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize else context = send(:new, _datory_to_model: false) + additional_attributes = + collection_of_attributes.external_names.difference(prepared_data.symbolize_keys.keys).to_h do |key| + [key, nil] + end + + prepared_data = prepared_data.merge(additional_attributes) + _deserialize(context, **prepared_data) end rescue Datory::Service::Exceptions::Input, From 01b7ee9da3e608ec9b3d79fb8b48101f27fdde2b Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 17:26:12 +0700 Subject: [PATCH 09/10] Add comments for getter and setter methods Added comments to indicate that 'getter' methods are for deserialization and 'setter' methods are for serialization in product.rb and season.rb files. This enhances code readability and makes the purpose of these methods clearer. --- examples/usual/example1/season.rb | 2 ++ examples/usual/example2/product.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/usual/example1/season.rb b/examples/usual/example1/season.rb index 6c8f9cd..337c760 100644 --- a/examples/usual/example1/season.rb +++ b/examples/usual/example1/season.rb @@ -14,10 +14,12 @@ class Season < Datory::Base date! :premieredOn, to: :premiered_on date? :endedOn, to: :ended_on + # deserialize getter :code do |attributes:| "s#{attributes.fetch(:number)}" end + # serialize setter :code do |attributes:| "s#{attributes.fetch(:number)}" end diff --git a/examples/usual/example2/product.rb b/examples/usual/example2/product.rb index 1cd3d93..bc43786 100644 --- a/examples/usual/example2/product.rb +++ b/examples/usual/example2/product.rb @@ -15,10 +15,12 @@ class Product < Datory::Base duration? :installmentDuration, to: :installment_duration + # deserialize getter :formattedTitle do |attributes:| "The New #{attributes.fetch(:title)} (from getter)" end + # serialize setter :formatted_title do |attributes:| "The New #{attributes.fetch(:title)} (from setter)" end From 9a375e6a367d0242eedc9fb52c3d9b502968e5de Mon Sep 17 00:00:00 2001 From: Anton Date: Sun, 22 Sep 2024 18:10:56 +0700 Subject: [PATCH 10/10] Add fullName attribute serialization logic Added the fullName attribute to language_spec.rb and language.rb to handle serialization and deserialization, ensuring that the full attribute details are included in the outputs and test cases. Also updated the descriptor to handle the presence of include_class correctly. --- examples/usual/example3/language.rb | 16 +++++ lib/datory/attributes/descriptor.rb | 5 +- spec/examples/usual/example3/language_spec.rb | 69 +++++++++++++------ 3 files changed, 67 insertions(+), 23 deletions(-) diff --git a/examples/usual/example3/language.rb b/examples/usual/example3/language.rb index 445dfbb..7c755ae 100644 --- a/examples/usual/example3/language.rb +++ b/examples/usual/example3/language.rb @@ -6,6 +6,7 @@ class Language < Datory::Base uuid! :id string! :name + string? :fullName, to: :full_name # TODO: Need to prepare this example: # { @@ -32,6 +33,21 @@ class Language < Datory::Base one? :lastEOLVersion, to: :last_eol, include: Version many? :previousVersions, to: :previous, include: Version + + # deserialize + getter :fullName do |**| + nil + end + + # serialize + setter :full_name do |attributes:| + language_name = attributes.fetch(:name) + current_version_name = attributes.dig(:current, :name) + + next language_name if current_version_name.blank? + + "#{language_name} (#{current_version_name})" + end end end end diff --git a/lib/datory/attributes/descriptor.rb b/lib/datory/attributes/descriptor.rb index ab43659..e085f43 100644 --- a/lib/datory/attributes/descriptor.rb +++ b/lib/datory/attributes/descriptor.rb @@ -26,7 +26,10 @@ def describe(service_class_name:, collection_of_attributes:) # rubocop:disable M row << attribute.from.type row << attribute.to.name row << attribute.to.type - row << (include_class if include_class <= Datory::Base) if collection_of_attributes.include_class_exist? + + if collection_of_attributes.include_class_exist? && !include_class.nil? + row << (include_class if include_class.respond_to?(:<=) && include_class <= Datory::Base) + end rows << row end diff --git a/spec/examples/usual/example3/language_spec.rb b/spec/examples/usual/example3/language_spec.rb index fb5da41..d85bfa9 100644 --- a/spec/examples/usual/example3/language_spec.rb +++ b/spec/examples/usual/example3/language_spec.rb @@ -137,6 +137,7 @@ { id: "73031620-be3b-4088-9a78-5589ff7e1f61", name: "Ruby", + fullName: "Ruby (3.3.1)", currentVersion: { name: "3.3.1", releasedAt: nil, @@ -161,6 +162,7 @@ { id: "73031620-be3b-4088-9a78-5589ff7e1f61", name: "Ruby", + fullName: "Ruby (3.3.1)", currentVersion: { name: "3.3.1", releasedAt: nil, @@ -374,17 +376,18 @@ expect { perform }.to( output( <<~TABLE - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | Usual::Example3::Language | - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | Attribute | From | To | As | Include | - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | id | String | id | String | | - | name | String | name | String | | - | currentVersion | [Usual::Example3::Version, Hash] | current | [Usual::Example3::Version, Hash] | Usual::Example3::Version | - | lastEOLVersion | [Usual::Example3::Version, Hash, NilClass] | last_eol | [Usual::Example3::Version, Hash, NilClass] | Usual::Example3::Version | - | previousVersions | Array | previous | Array | Usual::Example3::Version | - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | Usual::Example3::Language | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | Attribute | From | To | As | Include | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | id | String | id | String | | + | name | String | name | String | | + | fullName | [String, NilClass] | full_name | [String, NilClass] | | + | currentVersion | [Usual::Example3::Version, Hash] | current | [Usual::Example3::Version, Hash] | Usual::Example3::Version | + | lastEOLVersion | [Usual::Example3::Version, Hash, NilClass] | last_eol | [Usual::Example3::Version, Hash, NilClass] | Usual::Example3::Version | + | previousVersions | Array | previous | Array | Usual::Example3::Version | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TABLE ).to_stdout ) @@ -400,17 +403,18 @@ expect { perform }.to( output( <<~TABLE - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | Usual::Example3::Language | - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | Attribute | From | To | As | Include | - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - | id | String | id | String | | - | name | String | name | String | | - | currentVersion | [Usual::Example3::Version, Hash] | current | [Usual::Example3::Version, Hash] | Usual::Example3::Version | - | lastEOLVersion | [Usual::Example3::Version, Hash, NilClass] | last_eol | [Usual::Example3::Version, Hash, NilClass] | Usual::Example3::Version | - | previousVersions | Array | previous | Array | Usual::Example3::Version | - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | Usual::Example3::Language | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | Attribute | From | To | As | Include | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | id | String | id | String | | + | name | String | name | String | | + | fullName | [String, NilClass] | full_name | [String, NilClass] | | + | currentVersion | [Usual::Example3::Version, Hash] | current | [Usual::Example3::Version, Hash] | Usual::Example3::Version | + | lastEOLVersion | [Usual::Example3::Version, Hash, NilClass] | last_eol | [Usual::Example3::Version, Hash, NilClass] | Usual::Example3::Version | + | previousVersions | Array | previous | Array | Usual::Example3::Version | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TABLE ).to_stdout ) @@ -469,6 +473,27 @@ include: nil } }, + fullName: { + from: { + name: :fullName, + type: [String, NilClass], + min: nil, + max: nil, + consists_of: false, + format: nil + }, + to: { + name: :full_name, + type: [String, NilClass], + required: false, + default: nil, + min: nil, + max: nil, + consists_of: false, + format: nil, + include: nil + } + }, currentVersion: { from: { name: :currentVersion,