diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8380a4..5c1eecc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: CI: true strategy: matrix: - ruby-version: ['2.7', '3.0', '3.1', '3.2'] + ruby-version: ['3.0', '3.1', '3.2'] steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 9106b2a..ac66b17 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ /pkg/ /spec/reports/ /tmp/ +.byebug_history +ignore.* diff --git a/.rubocop.yml b/.rubocop.yml index 81c0e1f..920bc72 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,4 +2,4 @@ inherit_gem: rubocop-espago: rubocop.yml AllCops: - TargetRubyVersion: 2.7 + TargetRubyVersion: 3.0 diff --git a/Gemfile b/Gemfile index c563c88..553f723 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ source 'https://rubygems.org' # Specify your gem's dependencies in diggable.gemspec gemspec +gem 'byebug', '~> 11.1' # debugger gem 'minitest', '~> 5.0' # test framework gem 'rake', '~> 13.0' # automation tasks gem 'rubocop-espago', '~> 1.0' # ruby linter diff --git a/Gemfile.lock b/Gemfile.lock index 850cfd1..1d77d8b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,6 +10,7 @@ GEM ast (2.4.2) backport (1.2.0) benchmark (0.2.1) + byebug (11.1.3) diff-lcs (1.5.0) e2mmap (0.1.0) jaro_winkler (1.5.4) @@ -77,6 +78,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + byebug (~> 11.1) minitest (~> 5.0) rake (~> 13.0) rubocop-espago (~> 1.0) diff --git a/README.md b/README.md index 05b94db..92c5c36 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ class PaymentInstrument < Shale::Mapper attribute :expiration_month, ::Shale::Type::Integer end -class Transaction < ::Shale::Mapper +class Transaction < Shale::Mapper include Shale::Builder attribute :cvv_code, Shale::Type::String @@ -142,6 +142,52 @@ non-primitive types have been overridden to accept blocks. When a block is given to such a getter, it instantiates an empty object of its type and yields it to the block. +### Collections + +Whenever you call a getter with a block for a collection attribute, the built object will be appended to the array. + +Let's define a schema like this. + +```rb +class Client < Shale::Mapper + include Shale::Builder + + attribute :first_name, Shale::Type::String + attribute :last_name, Shale::Type::String + attribute :email, Shale::Type::String +end + +class Transaction < Shale::Mapper + include Shale::Builder + + attribute :clients, Client, collection: true +end +``` + +You can easily build add new clients to the collection like so: + +```rb +transaction = Transaction.build do |t| + # this will be added as the first element of the collection + t.clients do |c| + c.first_name = 'Foo' + c.last_name = 'Bar' + end + + # this will be added as the second element of the collection + t.clients do |c| + c.first_name = 'Grant' + c.last_name = 'Taylor' + end +end + +p transaction.clients +# [ +# #, +# # +# ] +``` + ### Conditional building This DSL makes it extremely easy to build nested diff --git a/lib/shale/builder.rb b/lib/shale/builder.rb index fa929eb..13d33dd 100644 --- a/lib/shale/builder.rb +++ b/lib/shale/builder.rb @@ -89,10 +89,25 @@ def build # @param name [String, Symbol] # @param type [Class] # @return [void] - def attribute(name, type, *args, **kwargs, &block) + def attribute(name, type, *args, collection: false, **kwargs, &block) super return unless type < ::Shale::Mapper + if collection + @builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def #{name} # def clients + return super unless block_given? # return super unless block_given? + # + arr = self.#{name} ||= [] # arr = self.clients ||= [] + object = #{type}.new # object = Client.new + yield(object) # yield(object) + arr << object # arr << object + object # object + end # end + RUBY + return + end + @builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{name} # def amount return super unless block_given? # return super unless block_given? diff --git a/shale-builder.gemspec b/shale-builder.gemspec index e7c96b7..a6dd973 100644 --- a/shale-builder.gemspec +++ b/shale-builder.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.description = spec.summary spec.homepage = 'https://github.com/Verseth/ruby-shale-builder' spec.license = 'MIT' - spec.required_ruby_version = '>= 2.7.0' + spec.required_ruby_version = '>= 3.0.0' spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage diff --git a/test/shale/builder_test.rb b/test/shale/builder_test.rb index 3002f9f..fc89ca8 100644 --- a/test/shale/builder_test.rb +++ b/test/shale/builder_test.rb @@ -41,6 +41,10 @@ class TestEnhancedTransactionType < TestTransactionType attribute :client_data, TestClientDataType end + class TestMultipleClientTransactionType < TestTransactionType + attribute :clients, TestClientDataType, collection: true + end + context 'inheritance' do should 'correctly set up a class after inheriting' do mod_parent = TestTransactionType.builder_methods_module @@ -152,6 +156,44 @@ class TestEnhancedTransactionType < TestTransactionType assert_equal 'USD', obj.amount.currency end + should 'build an object with a collection attribute' do + obj = TestMultipleClientTransactionType.build do |t| + t.cvv_code = '321' + t.amount do |a| + a.value = 45.0 + a.currency = 'USD' + end + t.clients do |c| + c.first_name = 'Mateusz' + c.last_name = 'Gobbins' + c.email = 'mat.gobbins@example.com' + end + t.clients do |c| + c.first_name = 'Michal' + c.last_name = 'Zapow' + c.email = 'mich.zapow@example.com' + end + end + + assert obj.is_a?(TestTransactionType) + assert_equal '321', obj.cvv_code + assert obj.amount.is_a?(TestAmountType) + assert_equal 45.0, obj.amount.value + assert_equal 'USD', obj.amount.currency + assert obj.clients.is_a?(::Array), "Should be and Array, got: #{obj.clients.class.inspect}" + assert_equal 2, obj.clients.length + + cli = obj.clients.first + assert_equal 'Mateusz', cli.first_name + assert_equal 'Gobbins', cli.last_name + assert_equal 'mat.gobbins@example.com', cli.email + + cli = obj.clients.last + assert_equal 'Michal', cli.first_name + assert_equal 'Zapow', cli.last_name + assert_equal 'mich.zapow@example.com', cli.email + end + should 'build an object through the alt DSL' do obj = TestTransactionType.build do |t| t.cvv_code = '321' diff --git a/test/test_helper.rb b/test/test_helper.rb index 30b756b..e3afacd 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,3 +5,4 @@ require 'minitest/autorun' require 'shoulda-context' +require 'byebug'