interfacets 0.1.0 → 0.9.99

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +173 -1
  3. data/.tmp +5 -0
  4. data/LICENSE +21 -0
  5. data/Rakefile +9 -7
  6. data/lib/interfacets/client/actor.rb +20 -0
  7. data/lib/interfacets/client/assets.rb +210 -0
  8. data/lib/interfacets/client/bus.rb +73 -0
  9. data/lib/interfacets/client/channels/api.rb +102 -0
  10. data/lib/interfacets/client/channels/audio.rb +101 -0
  11. data/lib/interfacets/client/channels/base.rb +28 -0
  12. data/lib/interfacets/client/channels/page_visibility.rb +21 -0
  13. data/lib/interfacets/client/channels/react/builder.rb +203 -0
  14. data/lib/interfacets/client/channels/react/channel.rb +61 -0
  15. data/lib/interfacets/client/channels/react/dom.rb +91 -0
  16. data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
  17. data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
  18. data/lib/interfacets/client/channels/timer.rb +51 -0
  19. data/lib/interfacets/client/channels/url.rb +52 -0
  20. data/lib/interfacets/client/config.rb +22 -0
  21. data/lib/interfacets/client/delegator.rb +37 -0
  22. data/lib/interfacets/client/registry.rb +23 -0
  23. data/lib/interfacets/client/system.rb +88 -0
  24. data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
  25. data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
  26. data/lib/interfacets/client/utils/open_struct.rb +115 -0
  27. data/lib/interfacets/client/utils/securerandom.rb +69 -0
  28. data/lib/interfacets/client.rb +13 -0
  29. data/lib/interfacets/component_registry.rb +115 -0
  30. data/lib/interfacets/component_schema_parser.rb +84 -0
  31. data/lib/interfacets/mruby/build.dockerfile +66 -0
  32. data/lib/interfacets/mruby/build_config.rb +20 -0
  33. data/lib/interfacets/mruby/entrypoint.rb +23 -0
  34. data/lib/interfacets/mruby/init.c +66 -0
  35. data/lib/interfacets/server/api.rb +44 -0
  36. data/lib/interfacets/server/assets/facet.rb +63 -0
  37. data/lib/interfacets/server/assets.rb +216 -0
  38. data/lib/interfacets/server/basic_router.rb +79 -0
  39. data/lib/interfacets/server/bus.rb +34 -0
  40. data/lib/interfacets/server/config.rb +87 -0
  41. data/lib/interfacets/server/facets/deserializer.rb +25 -0
  42. data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
  43. data/lib/interfacets/server/facets/serializer.rb +50 -0
  44. data/lib/interfacets/server/registry.rb +51 -0
  45. data/lib/interfacets/shared/basic_routable.rb +45 -0
  46. data/lib/interfacets/shared/entities/bus.rb +230 -0
  47. data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
  48. data/lib/interfacets/shared/entities/specs/handlers.rb +133 -0
  49. data/lib/interfacets/shared/entities/specs.rb +161 -0
  50. data/lib/interfacets/shared/entity.rb +102 -0
  51. data/lib/interfacets/shared/entity_dsl.rb +154 -0
  52. data/lib/interfacets/shared/facet.rb +200 -0
  53. data/lib/interfacets/shared/generated_store.rb +149 -0
  54. data/lib/interfacets/shared/utils.rb +54 -0
  55. data/lib/interfacets/shared/validations.rb +75 -0
  56. data/lib/interfacets/shared/view.rb +74 -0
  57. data/lib/interfacets/test/component_registry.rb +63 -0
  58. data/lib/interfacets/test/js/inline_bus.rb +100 -0
  59. data/lib/interfacets/test/js/nodo_bus.rb +98 -0
  60. data/lib/interfacets/test/js/receivers/api.rb +48 -0
  61. data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
  62. data/lib/interfacets/test/js/receivers/react/node.rb +133 -0
  63. data/lib/interfacets/test/js/receivers/react.rb +32 -0
  64. data/lib/interfacets/test/js/receivers/timer.rb +77 -0
  65. data/lib/interfacets/test/js/receivers/url.rb +60 -0
  66. data/lib/interfacets/test/standard_elements.yml +173 -0
  67. data/lib/interfacets/test/ui_simulator.rb +75 -0
  68. data/lib/interfacets/test/validation_engine.rb +151 -0
  69. data/lib/interfacets/test.rb +13 -0
  70. data/lib/interfacets/version.rb +1 -1
  71. data/lib/interfacets.rb +29 -2
  72. metadata +114 -6
  73. data/README.md +0 -35
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Entities
6
+ class Bus
7
+ InvalidAction = Class.new(StandardError)
8
+ InvalidReceiver = Class.new(StandardError)
9
+
10
+ # event schema:
11
+ # {
12
+ # from: (role),
13
+ # to: (role),
14
+ # action:,
15
+ # payload: {
16
+
17
+ # }
18
+ # }
19
+
20
+ attr_reader :manifest, :entity
21
+ def initialize(entity:)
22
+ @manifest = entity.class.manifest
23
+ @entity = entity
24
+ end
25
+
26
+ def serialize(to:, action:, nesting:)
27
+ assert_valid_action(action:, to:, nesting:)
28
+
29
+ {
30
+ id: SecureRandom.uuid,
31
+ from: entity.role,
32
+ to: to,
33
+ nesting:,
34
+ action:,
35
+ payload: {
36
+ attributes: serialize_attributes(manifest:, entity:, to:)
37
+ }
38
+ }
39
+ end
40
+
41
+ def handle(event:)
42
+ action = event.fetch("action")
43
+
44
+ assert_valid_receiver(to: event.fetch("to"))
45
+ assert_valid_action(
46
+ action:,
47
+ to: entity.role,
48
+ nesting: event.fetch("nesting"),
49
+ )
50
+
51
+ attributes = event.fetch("payload").fetch("attributes", {})
52
+
53
+ merge(entity:, manifest:, attributes:, action:)
54
+
55
+ target_entity = entity.entity_at(event.fetch("nesting"))
56
+
57
+ target_entity
58
+ &.class
59
+ &.actions
60
+ &.fetch(event.fetch("action"))
61
+ &.dispatch(target_entity)
62
+ end
63
+
64
+ private
65
+
66
+ def serialize_attributes(manifest:, entity:, to:)
67
+ return if entity.nil?
68
+
69
+ data = {}
70
+
71
+ data[:errors] = entity.errors.to_h if entity.respond_to?(:errors)
72
+
73
+ manifest
74
+ .accessors
75
+ .each do |name, spec|
76
+ data[name] = entity.send(name)
77
+ end
78
+
79
+ manifest
80
+ .associations
81
+ .each do |name, spec|
82
+ if spec.type == :reference
83
+ data[name] = serialize_attributes(
84
+ manifest: spec.klass,
85
+ entity: entity.send(name),
86
+ to:
87
+ )
88
+ else
89
+ data[name] = (entity.send(name) || []).map {
90
+ serialize_attributes(
91
+ manifest: spec.klass,
92
+ entity: _1,
93
+ to:
94
+ )
95
+ }
96
+ end
97
+ end
98
+
99
+ data
100
+ end
101
+
102
+ def merge(manifest:, entity:, attributes:, action:)
103
+ if (errs = attributes[:errors] || attributes["errors"])
104
+ entity.errors.clear if entity.errors.respond_to?(:clear)
105
+ errs.each do |k, vs|
106
+ Array(vs).each { |v| entity.errors.add(k.to_sym, v) }
107
+ end
108
+ end
109
+
110
+ manifest
111
+ .accessors
112
+ .values
113
+ .select { _1.accepted_by?(entity.role) }
114
+ .each do |attribute|
115
+ next unless attributes.key?(attribute.name)
116
+
117
+ attr_mergers = (
118
+ entity.class.mergers[attribute.name]
119
+ )
120
+
121
+ merger = (
122
+ if attr_mergers.key?(action)
123
+ attr_mergers.fetch(action)
124
+ else
125
+ attr_mergers.fetch(:default)
126
+ end
127
+ )
128
+
129
+ merger.call(entity, attributes[attribute.name])
130
+ end
131
+
132
+ manifest
133
+ .associations
134
+ .values
135
+ .select { _1.type == :reference }
136
+ .select { _1.accepted_by?(entity.role) }
137
+ .each do |association|
138
+ next unless attributes.key?(association.name)
139
+
140
+ value = attributes.fetch(association.name)
141
+
142
+ if value.nil?
143
+ entity.association(association.name).set(nil)
144
+ next
145
+ end
146
+
147
+ entity
148
+ .association(association.name)
149
+ .get
150
+ .then { _1 || entity.association(association.name).build }
151
+ .tap { |nested_entity|
152
+ merge(
153
+ entity: nested_entity,
154
+ manifest: association.klass,
155
+ attributes: value,
156
+ action:
157
+ )
158
+ }
159
+ .then { entity.association(association.name).set(_1) }
160
+ end
161
+
162
+ manifest
163
+ .associations
164
+ .values
165
+ .select { _1.type == :collection }
166
+ .select { _1.accepted_by?(entity.role) }
167
+ .each do |collection|
168
+ next unless attributes.key?(collection.name)
169
+
170
+ identifier = collection.identifier
171
+
172
+ incoming_coll = attributes.fetch(collection.name) || []
173
+ existing_coll = entity.send(collection.name) || []
174
+
175
+ items = (
176
+ incoming_coll.map do |val|
177
+ found_item = (
178
+ val.fetch(identifier) &&
179
+ existing_coll.find { _1.send(identifier) == val.fetch(identifier) }
180
+ )
181
+
182
+ if found_item
183
+ found_item.tap { |r|
184
+ merge(
185
+ entity: r,
186
+ manifest: collection.klass,
187
+ attributes: val,
188
+ action:
189
+ )
190
+ }
191
+ else
192
+ entity
193
+ .association(collection.name)
194
+ .build
195
+ .tap { |r|
196
+ merge(
197
+ entity: r,
198
+ manifest: collection.klass,
199
+ attributes: val,
200
+ action:
201
+ )
202
+ }
203
+ end
204
+ end
205
+ )
206
+ entity.association(collection.name).set(items)
207
+ end
208
+ end
209
+
210
+ def assert_valid_action(action:, to:, nesting:)
211
+ nested_manifest = manifest
212
+
213
+ nesting[1..-1].each do |assoc_name, _id|
214
+ nested_manifest = manifest.associations.fetch(assoc_name).klass
215
+ end
216
+
217
+ unless nested_manifest.actions[action]&.accepted_by?(to.to_s)
218
+ raise InvalidAction.new("invalid action: #{action}")
219
+ end
220
+ end
221
+
222
+ def assert_valid_receiver(to:)
223
+ if to != entity.role
224
+ raise InvalidReceiver.new("invalid receiver role: to: #{to}, entity: #{entity.role.inspect}")
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Entities
6
+ class CollectionProxy
7
+ include Enumerable
8
+
9
+ attr_reader :wrap, :unwrap
10
+ def initialize(collection, wrap:, unwrap:)
11
+ @collection = collection
12
+ @wrap = wrap
13
+ @unwrap = unwrap
14
+ end
15
+
16
+ # Read operations - wrap items when accessing
17
+ def first
18
+ val = @collection.first
19
+ val.nil? ? nil : @wrap.call(val)
20
+ end
21
+
22
+ def last
23
+ val = @collection.last
24
+ val.nil? ? nil : @wrap.call(val)
25
+ end
26
+
27
+ def [](index)
28
+ val = @collection[index]
29
+ val.nil? ? nil : @wrap.call(val)
30
+ end
31
+
32
+ def at(index)
33
+ val = @collection.at(index)
34
+ val.nil? ? nil : @wrap.call(val)
35
+ end
36
+
37
+ def each(&block)
38
+ @collection.each { |item| block.call(@wrap.call(item)) }
39
+ end
40
+
41
+ def size
42
+ @collection.size
43
+ end
44
+ alias_method :length, :size
45
+ alias_method :count, :size
46
+
47
+ def empty?
48
+ @collection.empty?
49
+ end
50
+
51
+ # Mutation operations - unwrap items and delegate to underlying collection
52
+ def <<(item)
53
+ @collection << @unwrap.call(item)
54
+ self
55
+ end
56
+
57
+ def push(*items)
58
+ @collection.push(*items.map { |item| @unwrap.call(item) })
59
+ self
60
+ end
61
+
62
+ def pop
63
+ val = @collection.pop
64
+ val.nil? ? nil : @wrap.call(val)
65
+ end
66
+
67
+ def shift
68
+ val = @collection.shift
69
+ val.nil? ? nil : @wrap.call(val)
70
+ end
71
+
72
+ def unshift(*items)
73
+ @collection.unshift(*items.map { |item| @unwrap.call(item) })
74
+ self
75
+ end
76
+
77
+ def []=(index, item)
78
+ @collection[index] = @unwrap.call(item)
79
+ end
80
+
81
+ def delete(item)
82
+ unwrapped = @unwrap.call(item)
83
+ val = @collection.delete(unwrapped)
84
+ val.nil? ? nil : @wrap.call(val)
85
+ end
86
+
87
+ def delete_at(index)
88
+ val = @collection.delete_at(index)
89
+ val.nil? ? nil : @wrap.call(val)
90
+ end
91
+
92
+ def clear
93
+ @collection.clear
94
+ self
95
+ end
96
+
97
+ def insert(index, *items)
98
+ @collection.insert(index, *items.map { |item| @unwrap.call(item) })
99
+ self
100
+ end
101
+
102
+ def concat(other_array)
103
+ @collection.concat(other_array.map { |item| @unwrap.call(item) })
104
+ self
105
+ end
106
+
107
+ def replace(other_array)
108
+ @collection.replace(other_array.map { |item| @unwrap.call(item) })
109
+ self
110
+ end
111
+
112
+ # Enumerable methods that return arrays should return CollectionProxy
113
+ def select(&block)
114
+ selected = @collection.select { |item| block.call(@wrap.call(item)) }
115
+ CollectionProxy.new(selected, wrap: @wrap, unwrap: @unwrap)
116
+ end
117
+ alias_method :filter, :select
118
+
119
+ def reject(&block)
120
+ rejected = @collection.reject { |item| block.call(@wrap.call(item)) }
121
+ CollectionProxy.new(rejected, wrap: @wrap, unwrap: @unwrap)
122
+ end
123
+
124
+ def take(n)
125
+ taken = @collection.take(n)
126
+ CollectionProxy.new(taken, wrap: @wrap, unwrap: @unwrap)
127
+ end
128
+
129
+ def take_while(&block)
130
+ taken = @collection.take_while { |item| block.call(@wrap.call(item)) }
131
+ CollectionProxy.new(taken, wrap: @wrap, unwrap: @unwrap)
132
+ end
133
+
134
+ def drop(n)
135
+ dropped = @collection.drop(n)
136
+ CollectionProxy.new(dropped, wrap: @wrap, unwrap: @unwrap)
137
+ end
138
+
139
+ def drop_while(&block)
140
+ dropped = @collection.drop_while { |item| block.call(@wrap.call(item)) }
141
+ CollectionProxy.new(dropped, wrap: @wrap, unwrap: @unwrap)
142
+ end
143
+
144
+ def reverse
145
+ reversed = @collection.reverse
146
+ CollectionProxy.new(reversed, wrap: @wrap, unwrap: @unwrap)
147
+ end
148
+
149
+ def sort(&block)
150
+ if block
151
+ sorted = @collection.sort { |a, b| block.call(@wrap.call(a), @wrap.call(b)) }
152
+ else
153
+ sorted = @collection.sort
154
+ end
155
+ CollectionProxy.new(sorted, wrap: @wrap, unwrap: @unwrap)
156
+ end
157
+
158
+ def sort_by(&block)
159
+ sorted = @collection.sort_by { |item| block.call(@wrap.call(item)) }
160
+ CollectionProxy.new(sorted, wrap: @wrap, unwrap: @unwrap)
161
+ end
162
+
163
+ def uniq(&block)
164
+ if block
165
+ uniqued = @collection.uniq { |item| block.call(@wrap.call(item)) }
166
+ else
167
+ uniqued = @collection.uniq
168
+ end
169
+ CollectionProxy.new(uniqued, wrap: @wrap, unwrap: @unwrap)
170
+ end
171
+
172
+ def compact
173
+ compacted = @collection.compact
174
+ CollectionProxy.new(compacted, wrap: @wrap, unwrap: @unwrap)
175
+ end
176
+
177
+ def slice(*args)
178
+ sliced = @collection.slice(*args)
179
+ if sliced.is_a?(Array)
180
+ CollectionProxy.new(sliced, wrap: @wrap, unwrap: @unwrap)
181
+ else
182
+ # Single element access returns wrapped item
183
+ sliced.nil? ? nil : @wrap.call(sliced)
184
+ end
185
+ end
186
+
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Entities
6
+ module Specs
7
+ module Handlers
8
+ class Handler
9
+ attr_reader :entity, :spec, :cache
10
+ def initialize(entity:, spec:)
11
+ @entity = entity
12
+ @spec = spec
13
+ @cache = {}
14
+ end
15
+
16
+ def type
17
+ spec.type
18
+ end
19
+
20
+ def unwrap(value)
21
+ if value.is_a?(Entity)
22
+ value.store
23
+ elsif value.is_a?(Array)
24
+ value.map { unwrap(_1) }
25
+ else
26
+ value
27
+ end
28
+ end
29
+
30
+ def wrap(value)
31
+ if value.is_a?(Entity)
32
+ value
33
+ elsif cache[value]
34
+ cache[value]
35
+ elsif value
36
+ @cache[value] = spec.klass.new(
37
+ store: value,
38
+ nesting: spec.name,
39
+ parent: entity,
40
+ )
41
+ end
42
+ end
43
+ end
44
+
45
+ class Reference < Handler
46
+ def collection?
47
+ false
48
+ end
49
+
50
+ def reference?
51
+ true
52
+ end
53
+
54
+ def get
55
+ wrap(entity.instance_exec(&spec.getter))
56
+ .tap {
57
+ @cache = {}
58
+ @cache[_1&.store] = _1
59
+ }
60
+ end
61
+
62
+ def set(val)
63
+ entity.instance_exec(unwrap(val), &spec.setter)
64
+ wrap(val)
65
+ end
66
+
67
+ def build(**attributes)
68
+ value = entity.instance_exec(&spec.builder)
69
+ attributes.each do |name, val|
70
+ value.send("#{name}=", val)
71
+ end
72
+
73
+ # TODO: make sure this is right
74
+ # set(value)
75
+
76
+ wrap(value)
77
+ end
78
+ end
79
+
80
+ class Collection < Handler
81
+ def collection?
82
+ true
83
+ end
84
+
85
+ def reference?
86
+ false
87
+ end
88
+
89
+ def get
90
+ values = entity.instance_exec(&spec.getter) || []
91
+
92
+ CollectionProxy.new(
93
+ values,
94
+ wrap: ->(val) { wrap(val) },
95
+ unwrap: ->(val) { unwrap(val) },
96
+ ).tap { |entities|
97
+ @cache = entities.map { [_1.store, _1] }.to_h
98
+ }
99
+ end
100
+
101
+ def set(val)
102
+ @cache ||= {}
103
+ val.each do |item|
104
+ if item.is_a?(Entity)
105
+ @cache[item.store] = item
106
+ end
107
+ end
108
+
109
+ entity.instance_exec(unwrap(val), &spec.setter)
110
+ end
111
+
112
+ def build(**attributes)
113
+ value = unwrap(entity.instance_exec(&spec.builder))
114
+ attributes.each do |name, val|
115
+ value.send("#{name}=", val)
116
+ end
117
+
118
+ items = entity.instance_exec(&spec.getter)
119
+
120
+ if items.nil?
121
+ set([value])
122
+ elsif !items.include?(value)
123
+ items << value
124
+ end
125
+
126
+ wrap(value)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Shared
5
+ module Entities
6
+ module Specs
7
+
8
+ # I would like to use Object.new for object equality here, however,
9
+ # in the intergration tests, our shared code gets evaled on the frontend
10
+ # and again on the backend (for now). For that reason, we cannot use
11
+ # object equality because the constant will be overridden.
12
+ NOT_PASSED = :interfacets__not_passed_flag
13
+ ANY = :interfacets__any_flag
14
+
15
+ module Acceptable
16
+ def accepted_by?(role)
17
+ standardized_accepted_by == ANY || standardized_accepted_by.include?(role)
18
+ end
19
+
20
+ def standardized_accepted_by
21
+ @standardized_accepted_by ||= (
22
+ if accepted_by == ANY
23
+ ANY
24
+ else
25
+ Array(accepted_by).map(&:to_s)
26
+ end
27
+ )
28
+ end
29
+ end
30
+
31
+ class Action
32
+ include Acceptable
33
+
34
+ attr_reader :name, :accepted_by, :only_if_valid
35
+ def initialize(name:, accepted_by:, only_if_valid:)
36
+ @name = name
37
+ @accepted_by = accepted_by
38
+ @only_if_valid = only_if_valid
39
+ end
40
+
41
+
42
+ def type = :action
43
+
44
+ def dispatch(entity)
45
+ if !only_if_valid || entity.valid?
46
+ entity.send(name)
47
+ end
48
+ end
49
+ end
50
+
51
+ class Accessor
52
+ include Acceptable
53
+
54
+ attr_reader :name, :accepted_by
55
+ def initialize(name:, accepted_by:)
56
+ @name = name
57
+ @accepted_by = accepted_by
58
+ end
59
+
60
+ def type = :accessor
61
+ end
62
+
63
+ class Association
64
+ include Acceptable
65
+
66
+ attr_reader(
67
+ :name,
68
+ :accepted_by,
69
+ :klass,
70
+ :getter,
71
+ :setter,
72
+ :builder,
73
+ :parent,
74
+ :identifier,
75
+ :type,
76
+ )
77
+ def initialize(parent:, name:)
78
+ @parent = parent
79
+ @name = name
80
+ @accepted_by = Entities::Specs::ANY
81
+ @getter = -> { store.send(name) }
82
+ @setter = ->(val) { store.send("#{name}=", val) }
83
+ @builder = ->() { store.association(name).build }
84
+ @identifier = "id"
85
+
86
+ @klass = Class.new(Entity) do
87
+ define_singleton_method(:name) { "#{parent.name}.#{name}" }
88
+ end
89
+ end
90
+
91
+ def apply(
92
+ accepted_by: NOT_PASSED,
93
+ getter: NOT_PASSED,
94
+ setter: NOT_PASSED,
95
+ builder: NOT_PASSED,
96
+ identifier: NOT_PASSED,
97
+ mod: nil,
98
+ type: NOT_PASSED,
99
+ &block
100
+ )
101
+ @accepted_by = Array(accepted_by).map(&:to_s) unless accepted_by == NOT_PASSED
102
+ @getter = getter unless getter == NOT_PASSED
103
+ @setter = setter unless setter == NOT_PASSED
104
+ @builder = builder unless builder == NOT_PASSED
105
+ @identifier = identifier.to_s unless identifier == NOT_PASSED
106
+
107
+ if type && type != NOT_PASSED
108
+ type = type.to_sym
109
+ unless [:reference, :collection].include?(type)
110
+ raise ArgumentError.new("invalid association type: #{type.inspect}")
111
+ end
112
+
113
+ if @type && type != @type
114
+ raise ArgumentError.new("invalid association cannot change type")
115
+ end
116
+
117
+ @type = type
118
+ end
119
+
120
+ @klass.include(mod) if mod
121
+
122
+ @klass.class_exec(&block) if block_given?
123
+ end
124
+
125
+ def dup_for(new_parent)
126
+ dup.tap do |new_spec|
127
+ new_spec.instance_variable_set(:@parent, new_parent)
128
+ new_spec.instance_variable_set(:@klass, Class.new(@klass))
129
+ end
130
+ end
131
+
132
+ def handler(entity)
133
+ klass = (
134
+ if type == :reference
135
+ Handlers::Reference
136
+ elsif type == :collection
137
+ Handlers::Collection
138
+ else
139
+ raise "Type was never set for association #{parent.name}.#{name}"
140
+ end
141
+ )
142
+
143
+ klass.new(entity:, spec: self)
144
+ end
145
+ end
146
+
147
+ class Merger
148
+ attr_reader(:name, :block)
149
+ def initialize(name:, block:)
150
+ @name = name
151
+ @block = block
152
+ end
153
+
154
+ def call(entity, value)
155
+ block.call(entity, value)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end