Skip to content

Commit eca0f64

Browse files
authored
Merge pull request #1 from Verseth/feature/collections
Add support for collections
2 parents dbd4d45 + 3618432 commit eca0f64

File tree

10 files changed

+114
-5
lines changed

10 files changed

+114
-5
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
CI: true
1414
strategy:
1515
matrix:
16-
ruby-version: ['2.7', '3.0', '3.1', '3.2']
16+
ruby-version: ['3.0', '3.1', '3.2']
1717
steps:
1818
- name: Checkout code
1919
uses: actions/checkout@v3

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
/pkg/
77
/spec/reports/
88
/tmp/
9+
.byebug_history
10+
ignore.*

.rubocop.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ inherit_gem:
22
rubocop-espago: rubocop.yml
33

44
AllCops:
5-
TargetRubyVersion: 2.7
5+
TargetRubyVersion: 3.0

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ source 'https://rubygems.org'
55
# Specify your gem's dependencies in diggable.gemspec
66
gemspec
77

8+
gem 'byebug', '~> 11.1' # debugger
89
gem 'minitest', '~> 5.0' # test framework
910
gem 'rake', '~> 13.0' # automation tasks
1011
gem 'rubocop-espago', '~> 1.0' # ruby linter

Gemfile.lock

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ GEM
1010
ast (2.4.2)
1111
backport (1.2.0)
1212
benchmark (0.2.1)
13+
byebug (11.1.3)
1314
diff-lcs (1.5.0)
1415
e2mmap (0.1.0)
1516
jaro_winkler (1.5.4)
@@ -77,6 +78,7 @@ PLATFORMS
7778
x86_64-linux
7879

7980
DEPENDENCIES
81+
byebug (~> 11.1)
8082
minitest (~> 5.0)
8183
rake (~> 13.0)
8284
rubocop-espago (~> 1.0)

README.md

+47-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class PaymentInstrument < Shale::Mapper
9090
attribute :expiration_month, ::Shale::Type::Integer
9191
end
9292

93-
class Transaction < ::Shale::Mapper
93+
class Transaction < Shale::Mapper
9494
include Shale::Builder
9595

9696
attribute :cvv_code, Shale::Type::String
@@ -142,6 +142,52 @@ non-primitive types have been overridden to accept blocks.
142142
When a block is given to such a getter, it instantiates an empty object
143143
of its type and yields it to the block.
144144

145+
### Collections
146+
147+
Whenever you call a getter with a block for a collection attribute, the built object will be appended to the array.
148+
149+
Let's define a schema like this.
150+
151+
```rb
152+
class Client < Shale::Mapper
153+
include Shale::Builder
154+
155+
attribute :first_name, Shale::Type::String
156+
attribute :last_name, Shale::Type::String
157+
attribute :email, Shale::Type::String
158+
end
159+
160+
class Transaction < Shale::Mapper
161+
include Shale::Builder
162+
163+
attribute :clients, Client, collection: true
164+
end
165+
```
166+
167+
You can easily build add new clients to the collection like so:
168+
169+
```rb
170+
transaction = Transaction.build do |t|
171+
# this will be added as the first element of the collection
172+
t.clients do |c|
173+
c.first_name = 'Foo'
174+
c.last_name = 'Bar'
175+
end
176+
177+
# this will be added as the second element of the collection
178+
t.clients do |c|
179+
c.first_name = 'Grant'
180+
c.last_name = 'Taylor'
181+
end
182+
end
183+
184+
p transaction.clients
185+
# [
186+
# #<Client:0x00000001066c2828 @first_name="Foo", @last_name="Bar", @email=nil>,
187+
# #<Client:0x00000001066c24b8 @first_name="Grant", @last_name="Taylor", @email=nil>
188+
# ]
189+
```
190+
145191
### Conditional building
146192

147193
This DSL makes it extremely easy to build nested

lib/shale/builder.rb

+16-1
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,25 @@ def build
8989
# @param name [String, Symbol]
9090
# @param type [Class]
9191
# @return [void]
92-
def attribute(name, type, *args, **kwargs, &block)
92+
def attribute(name, type, *args, collection: false, **kwargs, &block)
9393
super
9494
return unless type < ::Shale::Mapper
9595

96+
if collection
97+
@builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
98+
def #{name} # def clients
99+
return super unless block_given? # return super unless block_given?
100+
#
101+
arr = self.#{name} ||= [] # arr = self.clients ||= []
102+
object = #{type}.new # object = Client.new
103+
yield(object) # yield(object)
104+
arr << object # arr << object
105+
object # object
106+
end # end
107+
RUBY
108+
return
109+
end
110+
96111
@builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
97112
def #{name} # def amount
98113
return super unless block_given? # return super unless block_given?

shale-builder.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
1212
spec.description = spec.summary
1313
spec.homepage = 'https://github.com/Verseth/ruby-shale-builder'
1414
spec.license = 'MIT'
15-
spec.required_ruby_version = '>= 2.7.0'
15+
spec.required_ruby_version = '>= 3.0.0'
1616

1717
spec.metadata['homepage_uri'] = spec.homepage
1818
spec.metadata['source_code_uri'] = spec.homepage

test/shale/builder_test.rb

+42
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ class TestEnhancedTransactionType < TestTransactionType
4141
attribute :client_data, TestClientDataType
4242
end
4343

44+
class TestMultipleClientTransactionType < TestTransactionType
45+
attribute :clients, TestClientDataType, collection: true
46+
end
47+
4448
context 'inheritance' do
4549
should 'correctly set up a class after inheriting' do
4650
mod_parent = TestTransactionType.builder_methods_module
@@ -152,6 +156,44 @@ class TestEnhancedTransactionType < TestTransactionType
152156
assert_equal 'USD', obj.amount.currency
153157
end
154158

159+
should 'build an object with a collection attribute' do
160+
obj = TestMultipleClientTransactionType.build do |t|
161+
t.cvv_code = '321'
162+
t.amount do |a|
163+
a.value = 45.0
164+
a.currency = 'USD'
165+
end
166+
t.clients do |c|
167+
c.first_name = 'Mateusz'
168+
c.last_name = 'Gobbins'
169+
c.email = '[email protected]'
170+
end
171+
t.clients do |c|
172+
c.first_name = 'Michal'
173+
c.last_name = 'Zapow'
174+
c.email = '[email protected]'
175+
end
176+
end
177+
178+
assert obj.is_a?(TestTransactionType)
179+
assert_equal '321', obj.cvv_code
180+
assert obj.amount.is_a?(TestAmountType)
181+
assert_equal 45.0, obj.amount.value
182+
assert_equal 'USD', obj.amount.currency
183+
assert obj.clients.is_a?(::Array), "Should be and Array, got: #{obj.clients.class.inspect}"
184+
assert_equal 2, obj.clients.length
185+
186+
cli = obj.clients.first
187+
assert_equal 'Mateusz', cli.first_name
188+
assert_equal 'Gobbins', cli.last_name
189+
assert_equal '[email protected]', cli.email
190+
191+
cli = obj.clients.last
192+
assert_equal 'Michal', cli.first_name
193+
assert_equal 'Zapow', cli.last_name
194+
assert_equal '[email protected]', cli.email
195+
end
196+
155197
should 'build an object through the alt DSL' do
156198
obj = TestTransactionType.build do |t|
157199
t.cvv_code = '321'

test/test_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
require 'minitest/autorun'
77
require 'shoulda-context'
8+
require 'byebug'

0 commit comments

Comments
 (0)