shale 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f880bc8d984c45e7e8a05bd155c1014df237e037263c7bf68a7ae82c0e49ea4
4
- data.tar.gz: ba7a2f12bed87e048dd53d43900f1c8203d90f5f89a43b3824475ddda39eb07b
3
+ metadata.gz: 4e3b4d27ab7e8cc9b4873dfa60b52bdbafe50c2885085c4ec77ac6887351aab1
4
+ data.tar.gz: 6ce71a10ebf53633930b6d5a92ebb5f736aab44c47d6910a25238209192a4b92
5
5
  SHA512:
6
- metadata.gz: 54023d4f621dae53537f73e0ad79d3736d5d52e605c1a7da2c4bdb06588b348feaae9066ce00b6b3aedc9e84ea23a425884eabf987717ffda6d3c22eccc6325f
7
- data.tar.gz: e82cad292dab557891cad4f210bfb4a97d2096a6d2151a5a8aef020358accb5986e527e32a141a9a2e3a01d594e9a3adb42c0f7e8d17464517731136f63289a2
6
+ metadata.gz: 23c3b182c72c0e1d5102eb667541caccfb56e836e636bd083027425a60a12ed39ffce1ab3540adc0659c6b1bcbcf5255d192bbdc2d39bfc10c762ee4bdaaaa1e
7
+ data.tar.gz: ef7481e6076cc8f426c1dd0fb002d984594cbf3d59743a31450e8989048ffdd6cdc8fc735e90b7529c1935d7251c6751e70f7c3734d5c3f7df4b8124cc4b8256
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## [1.0.0] - 2023-07-15
2
+
3
+ ### Added
4
+ - Support for Ruby 3.2
5
+ - Support for delegating fields to nested attributes
6
+ - JSON and XML schema namespace mapping support
7
+ - Allow to set render_nil defaults
8
+
9
+ ### Changed
10
+ - Use `ShaleError` as a base class for exceptions
11
+ - Use model instead of mapper names for schema types
12
+
13
+ ### Fixed
14
+ - Fix compilation error for bundled JSON schemas
15
+ - Fix type inference for XML schema when default namespace is used
16
+ - Fix XML schema handling with a period in the element name
17
+
1
18
  ## [0.9.0] - 2022-10-31
2
19
 
3
20
  ### Added
data/README.md CHANGED
@@ -63,6 +63,7 @@ $ gem install shale
63
63
  * [Using XML namespaces](#using-xml-namespaces)
64
64
  * [Rendering nil values](#rendering-nil-values)
65
65
  * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
66
+ * [Delegating fields to child attributes](#delegating-fields-to-child-attributes)
66
67
  * [Additional options](#additional-options)
67
68
  * [Using custom models](#using-custom-models)
68
69
  * [Supported types](#supported-types)
@@ -692,6 +693,49 @@ puts person.to_xml(pretty: true)
692
693
  # </person>
693
694
  ```
694
695
 
696
+ If you want to change how nil values are rendered for all mappings you can use `render_nil` method:
697
+
698
+ ```ruby
699
+ class Base < Shale::Mapper
700
+ json do
701
+ # change render_nil default for all JSON mappings inheriting from Base class
702
+ render_nil true
703
+ end
704
+ end
705
+
706
+ class Person < Base
707
+ attribute :first_name, Shale::Type::String
708
+ attribute :last_name, Shale::Type::String
709
+ attribute :age, Shale::Type::Integer
710
+
711
+ json do
712
+ # override default from Base class
713
+ render_nil false
714
+
715
+ map 'first_name', to: :first_name
716
+ map 'last_name', to: :last_name
717
+ map 'age', to: :age, render_nil: true # override default
718
+ end
719
+ end
720
+ ```
721
+
722
+ :warning: The default affects only the mappings declared after setting the default value e.g.
723
+
724
+ ```ruby
725
+ class Person < Base
726
+ attribute :first_name, Shale::Type::String
727
+ attribute :last_name, Shale::Type::String
728
+
729
+ json do
730
+ render_nil false
731
+ map 'first_name', to: :first_name # render_nil will be false for this mapping
732
+
733
+ render_nil true
734
+ map 'last_name', to: :last_name # render_nil will be true for this mapping
735
+ end
736
+ end
737
+ ```
738
+
695
739
  ### Using methods to extract and generate data
696
740
 
697
741
  If you need full controll over extracting and generating data from/to document,
@@ -865,6 +909,42 @@ DATA
865
909
  # => #<Person:0x00007f9bc3086d60 @name="John Doe">
866
910
  ```
867
911
 
912
+ ### Delegating fields to child attributes
913
+
914
+ To delegate fields to child complex types you can use `receiver: :child` declaration:
915
+
916
+ ```ruby
917
+ class Address < Shale::Mapper
918
+ attribute :city, Shale::Type::String
919
+ attribute :street, Shale::Type::String
920
+ end
921
+
922
+ class Person < Shale::Mapper
923
+ attribute :name, Shale::Type::String
924
+ attribute :address, Address
925
+
926
+ json do
927
+ map 'name', to: :name
928
+ map 'city', to: :city, receiver: :address
929
+ map 'street', to: :street, receiver: :address
930
+ end
931
+ end
932
+
933
+ person = Person.from_json(<<~DATA)
934
+ {
935
+ "name": "John Doe",
936
+ "city": "London",
937
+ "street": "Oxford Street"
938
+ }
939
+ DATA
940
+
941
+ # =>
942
+ #
943
+ # #<Person:0x00007f9bc3086d60
944
+ # @name="John Doe",
945
+ # @address=#<Address:0x0000000102cbd218 @city="London", @street="Oxford Street">>
946
+ ```
947
+
868
948
  ### Additional options
869
949
 
870
950
  You can control which attributes to render and parse by
@@ -1212,7 +1292,9 @@ Shale::Schema::JSONGenerator.register_json_type(MyEmailType, MyEmailJSONType)
1212
1292
 
1213
1293
  :warning: Only **[Draft 2020-12](https://json-schema.org/draft/2020-12/schema)** JSON Schema is supported
1214
1294
 
1215
- To generate Shale data model from JSON Schema use:
1295
+ To generate Shale data model from JSON Schema use `Shale::Schema.from_json`.
1296
+ You can pass `root_name: 'Foobar'` to change the name of the root type and
1297
+ `namespace_mapping: {}` to map schemas to Ruby modules:
1216
1298
 
1217
1299
  ```ruby
1218
1300
  require 'shale/schema'
@@ -1223,7 +1305,11 @@ schema = <<~SCHEMA
1223
1305
  "properties": {
1224
1306
  "firstName": { "type": "string" },
1225
1307
  "lastName": { "type": "string" },
1226
- "address": {
1308
+ "address": { "$ref": "http://bar.com" }
1309
+ },
1310
+ "$defs": {
1311
+ "Address": {
1312
+ "$id": "http://bar.com",
1227
1313
  "type": "object",
1228
1314
  "properties": {
1229
1315
  "street": { "type": "string" },
@@ -1234,38 +1320,53 @@ schema = <<~SCHEMA
1234
1320
  }
1235
1321
  SCHEMA
1236
1322
 
1237
- Shale::Schema.from_json([schema], root_name: 'Person')
1323
+ Shale::Schema.from_json(
1324
+ [schema],
1325
+ root_name: 'Person',
1326
+ namespace_mapping: {
1327
+ nil => 'Api::Foo', # default schema (without ID)
1328
+ 'http://bar.com' => 'Api::Bar',
1329
+ }
1330
+ )
1238
1331
 
1239
1332
  # =>
1240
1333
  #
1241
1334
  # {
1242
- # "address" => "
1335
+ # "api/bar/address" => "
1243
1336
  # require 'shale'
1244
1337
  #
1245
- # class Address < Shale::Mapper
1246
- # attribute :street, Shale::Type::String
1247
- # attribute :city, Shale::Type::String
1338
+ # module Api
1339
+ # module Bar
1340
+ # class Address < Shale::Mapper
1341
+ # attribute :street, Shale::Type::String
1342
+ # attribute :city, Shale::Type::String
1248
1343
  #
1249
- # json do
1250
- # map 'street', to: :street
1251
- # map 'city', to: :city
1344
+ # json do
1345
+ # map 'street', to: :street
1346
+ # map 'city', to: :city
1347
+ # end
1348
+ # end
1252
1349
  # end
1253
1350
  # end
1254
1351
  # ",
1255
- # "person" => "
1352
+ # "api/foo/person" => "
1256
1353
  # require 'shale'
1257
1354
  #
1258
- # require_relative 'address'
1355
+ # require_relative '../bar/address'
1259
1356
  #
1260
- # class Person < Shale::Mapper
1261
- # attribute :first_name, Shale::Type::String
1262
- # attribute :last_name, Shale::Type::String
1263
- # attribute :address, Address
1357
+ # module Api
1358
+ # module Foo
1359
+ # class Person < Shale::Mapper
1360
+ # attribute :first_name, Shale::Type::String
1361
+ # attribute :last_name, Shale::Type::String
1362
+ # attribute :address, Api::Bar::Address
1264
1363
  #
1265
- # json do
1266
- # map 'firstName', to: :first_name
1267
- # map 'lastName', to: :last_name
1268
- # map 'address', to: :address
1364
+ # json do
1365
+ # map 'firstName', to: :first_name
1366
+ # map 'lastName', to: :last_name
1367
+ # map 'address', to: :address
1368
+ # end
1369
+ # end
1269
1370
  # end
1270
1371
  # end
1271
1372
  # "
@@ -1275,7 +1376,7 @@ Shale::Schema.from_json([schema], root_name: 'Person')
1275
1376
  You can also use a command line tool to do it:
1276
1377
 
1277
1378
  ```
1278
- $ shaleb -c -i schema.json -r Person
1379
+ $ shaleb -c -i schema.json -r Person -m http://bar.com=Api::Bar,=Api::Foo
1279
1380
  ```
1280
1381
 
1281
1382
  ### Generating XML Schema
@@ -1346,22 +1447,39 @@ Shale::Schema::XMLGenerator.register_xml_type(MyEmailType, 'myEmailXMLType')
1346
1447
 
1347
1448
  ### Compiling XML Schema into Shale model
1348
1449
 
1349
- To generate Shale data model from XML Schema use:
1450
+ To generate Shale data model from XML Schema use `Shale::Schema.from_xml`.
1451
+ You can pass `namespace_mapping: {}` to map XML namespaces to Ruby modules:
1350
1452
 
1351
1453
  ```ruby
1352
1454
  require 'shale/schema'
1353
1455
 
1354
- schema = <<~SCHEMA
1355
- <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1456
+ schema1 = <<~SCHEMA
1457
+ <xs:schema
1458
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
1459
+ xmlns:bar="http://bar.com"
1460
+ elementFormDefault="qualified"
1461
+ >
1462
+ <xs:import namespace="http://bar.com" />
1463
+
1356
1464
  <xs:element name="Person" type="Person" />
1357
1465
 
1358
1466
  <xs:complexType name="Person">
1359
1467
  <xs:sequence>
1360
- <xs:element name="FirstName" type="xs:string" />
1361
- <xs:element name="LastName" type="xs:string" />
1362
- <xs:element name="Address" type="Address" />
1468
+ <xs:element name="Name" type="xs:string" />
1469
+ <xs:element ref="bar:Address" />
1363
1470
  </xs:sequence>
1364
1471
  </xs:complexType>
1472
+ </xs:schema>
1473
+ SCHEMA
1474
+
1475
+ schema2 = <<~SCHEMA
1476
+ <xs:schema
1477
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
1478
+ xmlns:bar="http://bar.com"
1479
+ targetNamespace="http://bar.com"
1480
+ elementFormDefault="qualified"
1481
+ >
1482
+ <xs:element name="Address" type="bar:Address" />
1365
1483
 
1366
1484
  <xs:complexType name="Address">
1367
1485
  <xs:sequence>
@@ -1372,42 +1490,55 @@ schema = <<~SCHEMA
1372
1490
  </xs:schema>
1373
1491
  SCHEMA
1374
1492
 
1375
- Shale::Schema.from_xml([schema])
1493
+ Shale::Schema.from_xml(
1494
+ [schema1, schema2],
1495
+ namespace_mapping: {
1496
+ nil => 'Api::Foo', # no namespace
1497
+ 'http://bar.com' => 'Api::Bar',
1498
+ }
1499
+ )
1376
1500
 
1377
1501
  # =>
1378
1502
  #
1379
1503
  # {
1380
- # "address" => "
1504
+ # "api/bar/address" => "
1381
1505
  # require 'shale'
1382
1506
  #
1383
- # class Address < Shale::Mapper
1384
- # attribute :street, Shale::Type::String
1385
- # attribute :city, Shale::Type::String
1507
+ # module Api
1508
+ # module Bar
1509
+ # class Address < Shale::Mapper
1510
+ # attribute :street, Shale::Type::String
1511
+ # attribute :city, Shale::Type::String
1386
1512
  #
1387
- # xml do
1388
- # root 'Address'
1513
+ # xml do
1514
+ # root 'Address'
1515
+ # namespace 'http://bar.com', 'bar'
1389
1516
  #
1390
- # map_element 'Street', to: :street
1391
- # map_element 'City', to: :city
1517
+ # map_element 'Street', to: :street
1518
+ # map_element 'City', to: :city
1519
+ # end
1520
+ # end
1392
1521
  # end
1393
1522
  # end
1394
1523
  # ",
1395
- # "person" => "
1524
+ # "api/foo/person" => "
1396
1525
  # require 'shale'
1397
1526
  #
1398
- # require_relative 'address'
1527
+ # require_relative '../bar/address'
1399
1528
  #
1400
- # class Person < Shale::Mapper
1401
- # attribute :first_name, Shale::Type::String
1402
- # attribute :last_name, Shale::Type::String
1403
- # attribute :address, Address
1529
+ # module Api
1530
+ # module Foo
1531
+ # class Person < Shale::Mapper
1532
+ # attribute :name, Shale::Type::String
1533
+ # attribute :address, Api::Bar::Address
1404
1534
  #
1405
- # xml do
1406
- # root 'Person'
1535
+ # xml do
1536
+ # root 'Person'
1407
1537
  #
1408
- # map_element 'FirstName', to: :first_name
1409
- # map_element 'LastName', to: :last_name
1410
- # map_element 'Address', to: :address
1538
+ # map_element 'Name', to: :name
1539
+ # map_element 'Address', to: :address, prefix: 'bar', namespace: 'http://bar.com'
1540
+ # end
1541
+ # end
1411
1542
  # end
1412
1543
  # end
1413
1544
  # "
@@ -1417,7 +1548,7 @@ Shale::Schema.from_xml([schema])
1417
1548
  You can also use a command line tool to do it:
1418
1549
 
1419
1550
  ```
1420
- $ shaleb -c -f xml -i schema.xml
1551
+ $ shaleb -c -f xml -i schema.xml -m http://bar.com=Api::Bar,=Api::Foo
1421
1552
  ```
1422
1553
 
1423
1554
  ## Contributing
data/exe/shaleb CHANGED
@@ -36,8 +36,8 @@ ARGV << '-h' if ARGV.empty?
36
36
  OptionParser.new do |opts|
37
37
  opts.banner = <<~BANNER
38
38
  Usage: shaleb [options]
39
- example generate schema from Shale model: shaleb -g -i data_model.rb -c MyRoot
40
- example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -c MyRoot
39
+ example generate schema from Shale model: shaleb -g -i data_model.rb -r MyRoot
40
+ example generate Shale model from schema: shaleb -c -i schema1.json,schema2.json -r MyRoot
41
41
  BANNER
42
42
 
43
43
  opts.on('-g', '--generate', 'generate schema from Shale model')
@@ -47,6 +47,7 @@ OptionParser.new do |opts|
47
47
  opts.on('-r ROOT', '--root ROOT', 'Shale model class name')
48
48
  opts.on('-f FORMAT', '--format FORMAT', 'Schema format: JSON (default), XML')
49
49
  opts.on('-p', '--pretty', 'Pretty print generated schema')
50
+ opts.on('-m MAPPING', '--mapping', Array, 'Namespace mapping')
50
51
 
51
52
  opts.on('-v', '--version', 'Show version') do
52
53
  puts "shaleb version #{Shale::VERSION}"
@@ -71,11 +72,24 @@ if params[:compile]
71
72
  end
72
73
  end
73
74
 
75
+ if params[:mapping]
76
+ namespace_mapping = params[:mapping]
77
+ .to_h { |e| [*e.split('='), nil][0, 2] }
78
+ .transform_keys { |key| key.empty? ? nil : key }
79
+ end
80
+
74
81
  if params[:format] == 'xml'
75
82
  load_xml_parser
76
- models = Shale::Schema.from_xml(schemas)
83
+ models = Shale::Schema.from_xml(
84
+ schemas,
85
+ namespace_mapping: namespace_mapping
86
+ )
77
87
  else
78
- models = Shale::Schema.from_json(schemas, root_name: params[:root])
88
+ models = Shale::Schema.from_json(
89
+ schemas,
90
+ root_name: params[:root],
91
+ namespace_mapping: namespace_mapping
92
+ )
79
93
  end
80
94
 
81
95
  if params[:output]
@@ -84,6 +98,7 @@ if params[:compile]
84
98
 
85
99
  models.each do |name, model|
86
100
  output_path = File.join(dir, "#{name}.rb")
101
+ FileUtils.mkdir_p(File.dirname(output_path))
87
102
  File.write(output_path, model)
88
103
  end
89
104
  else
data/lib/shale/error.rb CHANGED
@@ -53,10 +53,16 @@ module Shale
53
53
  end
54
54
  end
55
55
 
56
+ # Shale base error class
57
+ #
58
+ # @api private
59
+ class ShaleError < StandardError
60
+ end
61
+
56
62
  # Error for trying to assign not callable object as an attribute's default
57
63
  #
58
64
  # @api private
59
- class DefaultNotCallableError < StandardError
65
+ class DefaultNotCallableError < ShaleError
60
66
  # Initialize error object
61
67
  #
62
68
  # @param [String] record
@@ -71,36 +77,42 @@ module Shale
71
77
  # Error for passing incorrect model type
72
78
  #
73
79
  # @api private
74
- class IncorrectModelError < StandardError
80
+ class IncorrectModelError < ShaleError
75
81
  end
76
82
 
77
83
  # Error for passing incorrect arguments to map functions
78
84
  #
79
85
  # @api private
80
- class IncorrectMappingArgumentsError < StandardError
86
+ class IncorrectMappingArgumentsError < ShaleError
87
+ end
88
+
89
+ # Error for using incorrect type
90
+ #
91
+ # @api private
92
+ class NotAShaleMapperError < ShaleError
81
93
  end
82
94
 
83
- # Error for passing incorrect arguments to schema generation function
95
+ # Raised when receiver attribute is not defined
84
96
  #
85
97
  # @api private
86
- class NotAShaleMapperError < StandardError
98
+ class AttributeNotDefinedError < ShaleError
87
99
  end
88
100
 
89
101
  # Schema compilation error
90
102
  #
91
103
  # @api private
92
- class SchemaError < StandardError
104
+ class SchemaError < ShaleError
93
105
  end
94
106
 
95
107
  # Parsing error
96
108
  #
97
109
  # @api private
98
- class ParseError < StandardError
110
+ class ParseError < ShaleError
99
111
  end
100
112
 
101
113
  # Adapter error
102
114
  #
103
115
  # @api private
104
- class AdapterError < StandardError
116
+ class AdapterError < ShaleError
105
117
  end
106
118
  end
data/lib/shale/mapper.rb CHANGED
@@ -151,7 +151,7 @@ module Shale
151
151
  # @raise [DefaultNotCallableError] when attribute's default is not callable
152
152
  #
153
153
  # @example
154
- # calss Person < Shale::Mapper
154
+ # class Person < Shale::Mapper
155
155
  # attribute :first_name, Shale::Type::String
156
156
  # attribute :last_name, Shale::Type::String
157
157
  # attribute :age, Shale::Type::Integer, default: -> { 1 }
@@ -200,7 +200,7 @@ module Shale
200
200
  # @param [Proc] block
201
201
  #
202
202
  # @example
203
- # calss Person < Shale::Mapper
203
+ # class Person < Shale::Mapper
204
204
  # attribute :first_name, Shale::Type::String
205
205
  # attribute :last_name, Shale::Type::String
206
206
  # attribute :age, Shale::Type::Integer
@@ -224,7 +224,7 @@ module Shale
224
224
  # @param [Proc] block
225
225
  #
226
226
  # @example
227
- # calss Person < Shale::Mapper
227
+ # class Person < Shale::Mapper
228
228
  # attribute :first_name, Shale::Type::String
229
229
  # attribute :last_name, Shale::Type::String
230
230
  # attribute :age, Shale::Type::Integer
@@ -248,7 +248,7 @@ module Shale
248
248
  # @param [Proc] block
249
249
  #
250
250
  # @example
251
- # calss Person < Shale::Mapper
251
+ # class Person < Shale::Mapper
252
252
  # attribute :first_name, Shale::Type::String
253
253
  # attribute :last_name, Shale::Type::String
254
254
  # attribute :age, Shale::Type::Integer
@@ -272,7 +272,7 @@ module Shale
272
272
  # @param [Proc] block
273
273
  #
274
274
  # @example
275
- # calss Person < Shale::Mapper
275
+ # class Person < Shale::Mapper
276
276
  # attribute :first_name, Shale::Type::String
277
277
  # attribute :last_name, Shale::Type::String
278
278
  # attribute :age, Shale::Type::Integer
@@ -296,7 +296,7 @@ module Shale
296
296
  # @param [Proc] block
297
297
  #
298
298
  # @example
299
- # calss Person < Shale::Mapper
299
+ # class Person < Shale::Mapper
300
300
  # attribute :first_name, Shale::Type::String
301
301
  # attribute :last_name, Shale::Type::String
302
302
  # attribute :age, Shale::Type::Integer
@@ -320,7 +320,7 @@ module Shale
320
320
  # @param [Proc] block
321
321
  #
322
322
  # @example
323
- # calss Person < Shale::Mapper
323
+ # class Person < Shale::Mapper
324
324
  # attribute :first_name, Shale::Type::String
325
325
  # attribute :last_name, Shale::Type::String
326
326
  # attribute :age, Shale::Type::Integer
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Mapping
5
+ # Class for handling attribute delegation
6
+ #
7
+ # @api private
8
+ class Delegates
9
+ # Class representing individual delegation
10
+ #
11
+ # @api private
12
+ class Delegate
13
+ # Return receiver_attribute
14
+ #
15
+ # @return [Shale::Attribute]
16
+ #
17
+ # @api private
18
+ attr_reader :receiver_attribute
19
+
20
+ # Return attribute setter on a delegate
21
+ #
22
+ # @return [String]
23
+ #
24
+ # @api private
25
+ attr_reader :setter
26
+
27
+ # Return value to set on a delegate
28
+ #
29
+ # @return [any]
30
+ #
31
+ # @api private
32
+ attr_reader :value
33
+
34
+ # Initialize instance
35
+ #
36
+ # @param [Shale::Attribute] receiver_attribute
37
+ # @param [String] setter
38
+ # @param [any] value
39
+ #
40
+ # @api private
41
+ def initialize(receiver_attribute, setter, value)
42
+ @receiver_attribute = receiver_attribute
43
+ @setter = setter
44
+ @value = value
45
+ end
46
+ end
47
+
48
+ # Initialize instance
49
+ #
50
+ # @api private
51
+ def initialize
52
+ @delegates = []
53
+ end
54
+
55
+ # Add single value to delegate
56
+ #
57
+ # @param [Shale::Attribute] receiver_attribute
58
+ # @param [String] setter
59
+ # @param [any] value
60
+ #
61
+ # @api private
62
+ def add(receiver_attribute, setter, value)
63
+ @delegates << Delegate.new(receiver_attribute, setter, value)
64
+ end
65
+
66
+ # Add collection to delegate
67
+ #
68
+ # @param [Shale::Attribute] receiver_attribute
69
+ # @param [String] setter
70
+ # @param [any] value
71
+ #
72
+ # @api private
73
+ def add_collection(receiver_attribute, setter, value)
74
+ delegate = @delegates.find do |e|
75
+ e.receiver_attribute == receiver_attribute && e.setter == setter
76
+ end
77
+
78
+ if delegate
79
+ delegate.value << value
80
+ else
81
+ @delegates << Delegate.new(receiver_attribute, setter, [value])
82
+ end
83
+ end
84
+
85
+ # Iterate over delegates and yield a block
86
+ #
87
+ # @param [Proc] block
88
+ #
89
+ # @api private
90
+ def each(&block)
91
+ @delegates.each(&block)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -21,6 +21,13 @@ module Shale
21
21
  # @api private
22
22
  attr_reader :attribute
23
23
 
24
+ # Return receiver name
25
+ #
26
+ # @return [Symbol]
27
+ #
28
+ # @api private
29
+ attr_reader :receiver
30
+
24
31
  # Return method symbol
25
32
  #
26
33
  # @return [Symbol]
@@ -46,14 +53,16 @@ module Shale
46
53
  #
47
54
  # @param [String] name
48
55
  # @param [Symbol, nil] attribute
56
+ # @param [Symbol, nil] receiver
49
57
  # @param [Hash, nil] methods
50
58
  # @param [String, nil] group
51
59
  # @param [true, false] render_nil
52
60
  #
53
61
  # @api private
54
- def initialize(name:, attribute:, methods:, group:, render_nil:)
62
+ def initialize(name:, attribute:, receiver:, methods:, group:, render_nil:)
55
63
  @name = name
56
64
  @attribute = attribute
65
+ @receiver = receiver
57
66
  @group = group
58
67
  @render_nil = render_nil
59
68