Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for collections #1

Merged
merged 3 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
/pkg/
/spec/reports/
/tmp/
.byebug_history
ignore.*
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ inherit_gem:
rubocop-espago: rubocop.yml

AllCops:
TargetRubyVersion: 2.7
TargetRubyVersion: 3.0
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -77,6 +78,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
byebug (~> 11.1)
minitest (~> 5.0)
rake (~> 13.0)
rubocop-espago (~> 1.0)
Expand Down
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
# [
# #<Client:0x00000001066c2828 @first_name="Foo", @last_name="Bar", @email=nil>,
# #<Client:0x00000001066c24b8 @first_name="Grant", @last_name="Taylor", @email=nil>
# ]
```

### Conditional building

This DSL makes it extremely easy to build nested
Expand Down
17 changes: 16 additions & 1 deletion lib/shale/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
2 changes: 1 addition & 1 deletion shale-builder.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions test/shale/builder_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = '[email protected]'
end
t.clients do |c|
c.first_name = 'Michal'
c.last_name = 'Zapow'
c.email = '[email protected]'
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 '[email protected]', cli.email

cli = obj.clients.last
assert_equal 'Michal', cli.first_name
assert_equal 'Zapow', cli.last_name
assert_equal '[email protected]', cli.email
end

should 'build an object through the alt DSL' do
obj = TestTransactionType.build do |t|
t.cvv_code = '321'
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

require 'minitest/autorun'
require 'shoulda-context'
require 'byebug'