shale 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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