interfacets 0.9.9 → 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.
- checksums.yaml +4 -4
- data/.tmp +5 -0
- data/Rakefile +4 -0
- data/lib/interfacets/client/assets.rb +1 -0
- data/lib/interfacets/client/bus.rb +5 -1
- data/lib/interfacets/client/channels/api.rb +10 -3
- data/lib/interfacets/client/registry.rb +9 -70
- data/lib/interfacets/client/utils/mruby_patches.rb +1 -1
- data/lib/interfacets/client/utils/open_struct.rb +90 -77
- data/lib/interfacets/component_registry.rb +115 -0
- data/lib/interfacets/component_schema_parser.rb +84 -0
- data/lib/interfacets/server/api.rb +5 -25
- data/lib/interfacets/server/assets/facet.rb +5 -3
- data/lib/interfacets/server/assets.rb +13 -7
- data/lib/interfacets/server/basic_router.rb +15 -10
- data/lib/interfacets/server/bus.rb +4 -9
- data/lib/interfacets/server/config.rb +1 -1
- data/lib/interfacets/server/registry.rb +23 -184
- data/lib/interfacets/shared/basic_routable.rb +45 -0
- data/lib/interfacets/shared/entities/bus.rb +41 -29
- data/lib/interfacets/shared/entities/specs/handlers.rb +16 -0
- data/lib/interfacets/shared/entities/specs.rb +46 -9
- data/lib/interfacets/shared/entity.rb +23 -99
- data/lib/interfacets/shared/entity_dsl.rb +154 -0
- data/lib/interfacets/shared/facet.rb +200 -0
- data/lib/interfacets/shared/generated_store.rb +6 -2
- data/lib/interfacets/shared/utils.rb +1 -1
- data/lib/interfacets/shared/validations.rb +5 -1
- data/lib/interfacets/{client → shared}/view.rb +33 -6
- data/lib/interfacets/test/component_registry.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +21 -12
- data/lib/interfacets/test/js/nodo_bus.rb +18 -1
- data/lib/interfacets/test/js/receivers/api.rb +14 -3
- data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +29 -63
- data/lib/interfacets/test/js/receivers/react.rb +4 -3
- data/lib/interfacets/test/js/receivers/timer.rb +77 -0
- data/lib/interfacets/test/js/receivers/url.rb +5 -0
- data/lib/interfacets/test/standard_elements.yml +173 -0
- data/lib/interfacets/test/{browser.rb → ui_simulator.rb} +18 -6
- data/lib/interfacets/test/validation_engine.rb +151 -0
- data/lib/interfacets/test.rb +0 -4
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +3 -0
- metadata +29 -18
- data/lib/interfacets/client/facet.rb +0 -26
- data/lib/interfacets/client/facet2.rb +0 -15
- data/lib/interfacets/client/facets/attributes/accessor.rb +0 -28
- data/lib/interfacets/client/facets/attributes/association.rb +0 -50
- data/lib/interfacets/client/facets/attributes/bind.rb +0 -25
- data/lib/interfacets/client/facets/attributes/collection.rb +0 -47
- data/lib/interfacets/client/facets/attributes/readonly.rb +0 -19
- data/lib/interfacets/client/facets/deserializer.rb +0 -30
- data/lib/interfacets/client/facets/schema/deserializer.rb +0 -63
- data/lib/interfacets/client/facets/schema.rb +0 -63
- data/lib/interfacets/client/facets/serializer.rb +0 -18
- data/lib/interfacets/server/basic_routable.rb +0 -40
- data/lib/interfacets/server/facet.rb +0 -51
- data/lib/interfacets/shared/entity_collection.rb +0 -88
|
@@ -118,8 +118,8 @@ module Interfacets
|
|
|
118
118
|
|
|
119
119
|
serialized[const.full_name] = true
|
|
120
120
|
|
|
121
|
-
const.nesting.each do |
|
|
122
|
-
push_bootstrapper(
|
|
121
|
+
const.nesting.each do |nesting_const|
|
|
122
|
+
push_bootstrapper(nesting_const, results, serialized)
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
parent = (
|
|
@@ -145,7 +145,7 @@ module Interfacets
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
def resolve_const_name(nesting, name)
|
|
148
|
-
full_name = [nesting, name].compact.join("::")
|
|
148
|
+
full_name = [nesting, name].compact.reject(&:empty?).join("::")
|
|
149
149
|
return consts_by_name[full_name] if consts_by_name.key?(full_name)
|
|
150
150
|
|
|
151
151
|
# maybe a constant that we don't handle loading for (eg, StandardError)
|
|
@@ -159,7 +159,7 @@ module Interfacets
|
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def fs_map
|
|
162
|
-
paths.map { [_1, file.read(_1)] }.to_h
|
|
162
|
+
@fs_map ||= paths.map { [_1, file.read(_1)] }.to_h
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
def consts
|
|
@@ -181,11 +181,17 @@ module Interfacets
|
|
|
181
181
|
].freeze
|
|
182
182
|
|
|
183
183
|
class << self
|
|
184
|
-
def bundle(dirs:, registry:)
|
|
184
|
+
def bundle(dirs:, registry:, only_facets: false)
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
target_dirs = (
|
|
187
|
+
if only_facets
|
|
188
|
+
dirs
|
|
189
|
+
else
|
|
190
|
+
GEM_DIRS + dirs
|
|
191
|
+
end
|
|
192
|
+
)
|
|
187
193
|
|
|
188
|
-
|
|
194
|
+
target_dirs
|
|
189
195
|
.flat_map { paths(_1) }
|
|
190
196
|
.flatten
|
|
191
197
|
.map(&:to_s)
|
|
@@ -10,10 +10,8 @@ module Interfacets
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def call(klass:, id:, query:, resolved_path:)
|
|
13
|
-
klass.
|
|
14
|
-
|
|
15
|
-
.find
|
|
16
|
-
.call(id, query:)
|
|
13
|
+
klass.server_entity_class
|
|
14
|
+
.find_for_basic_routing(id, query:, bus: bus)
|
|
17
15
|
.tap { _1.entity.api_path = resolved_path }
|
|
18
16
|
end
|
|
19
17
|
end
|
|
@@ -26,13 +24,13 @@ module Interfacets
|
|
|
26
24
|
end
|
|
27
25
|
|
|
28
26
|
def call(url, query: {})
|
|
29
|
-
|
|
27
|
+
path = normalize(url)
|
|
28
|
+
|
|
29
|
+
if path.nil? && default
|
|
30
30
|
klass = klass_for(default)
|
|
31
|
-
evaluator.call(klass:, id: nil, query:, resolved_path: default)
|
|
31
|
+
return evaluator.call(klass:, id: nil, query:, resolved_path: default)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
path = url.sub(/^#{bus.root_url}/, "/").sub(/^/, "/").sub(%r{^/*}, "/")
|
|
35
|
-
|
|
36
34
|
if normalized_paths.key?(path)
|
|
37
35
|
klass = klass_for(path)
|
|
38
36
|
|
|
@@ -58,7 +56,7 @@ module Interfacets
|
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
def klass_for(path)
|
|
61
|
-
val = normalized_paths.fetch(path)
|
|
59
|
+
val = normalized_paths.fetch(normalize(path))
|
|
62
60
|
|
|
63
61
|
(val.is_a?(String) ? Object.const_get(val) : val)
|
|
64
62
|
end
|
|
@@ -66,9 +64,16 @@ module Interfacets
|
|
|
66
64
|
def normalized_paths
|
|
67
65
|
@normalized_paths ||= (
|
|
68
66
|
paths
|
|
69
|
-
.transform_keys { _1
|
|
67
|
+
.transform_keys { normalize(_1) }
|
|
70
68
|
)
|
|
71
69
|
end
|
|
70
|
+
|
|
71
|
+
def normalize(path)
|
|
72
|
+
path
|
|
73
|
+
&.sub(/^#{bus.root_url}/, "/")
|
|
74
|
+
&.sub(/^/, "/")
|
|
75
|
+
&.sub(%r{^/*}, "/")
|
|
76
|
+
end
|
|
72
77
|
end
|
|
73
78
|
end
|
|
74
79
|
end
|
|
@@ -9,20 +9,15 @@ module Interfacets
|
|
|
9
9
|
:registry,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
def initialize(
|
|
13
|
-
|
|
14
|
-
asset_paths:,
|
|
15
|
-
facets:,
|
|
16
|
-
build_dir:
|
|
17
|
-
)
|
|
18
|
-
@registry = Registry.new(facets:, build_dir:)
|
|
12
|
+
def initialize(root_url:, asset_paths:)
|
|
13
|
+
@registry = Registry.new
|
|
19
14
|
@root_url = root_url
|
|
20
15
|
@asset_paths = asset_paths
|
|
21
16
|
end
|
|
22
17
|
|
|
23
|
-
def client_system_json
|
|
18
|
+
def client_system_json(only_facets: false)
|
|
24
19
|
{
|
|
25
|
-
assets: Assets.bundle(dirs: asset_paths, registry:),
|
|
20
|
+
assets: Assets.bundle(dirs: asset_paths, registry:, only_facets:),
|
|
26
21
|
root_url:
|
|
27
22
|
}
|
|
28
23
|
end
|
|
@@ -1,212 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/all"
|
|
4
|
+
require "ostruct"
|
|
4
5
|
|
|
5
6
|
module Interfacets
|
|
6
7
|
module Server
|
|
7
8
|
class Registry
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def register
|
|
15
|
-
specs
|
|
16
|
-
.flat_map { _1.respond_to?(:call) ? _1.call : _1 }
|
|
17
|
-
.each do |klass_or_name|
|
|
18
|
-
|
|
19
|
-
klass = (
|
|
20
|
-
if klass_or_name.is_a?(String)
|
|
21
|
-
if registry.key?(klass_or_name)
|
|
22
|
-
next
|
|
23
|
-
else
|
|
24
|
-
Object.const_get(klass_or_name)
|
|
25
|
-
end
|
|
26
|
-
else
|
|
27
|
-
klass_or_name
|
|
28
|
-
end
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
next if registry.key?(klass.name)
|
|
32
|
-
|
|
33
|
-
shared = Class.new(Shared::Entity) do
|
|
34
|
-
klass.shareds.each { class_exec(&_1) }
|
|
35
|
-
end
|
|
9
|
+
class Channel
|
|
10
|
+
def initialize(registry:)
|
|
11
|
+
@registry = registry
|
|
12
|
+
end
|
|
36
13
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
14
|
+
def build(...)
|
|
15
|
+
@registry.build(...)
|
|
16
|
+
end
|
|
40
17
|
|
|
41
|
-
|
|
42
|
-
|
|
18
|
+
def render_facet
|
|
19
|
+
@facet.render
|
|
20
|
+
end
|
|
43
21
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
48
|
-
end
|
|
22
|
+
def rendered?
|
|
23
|
+
@facet
|
|
24
|
+
end
|
|
49
25
|
|
|
50
|
-
|
|
51
|
-
|
|
26
|
+
def render(klass, store)
|
|
27
|
+
@facet = @registry.build(klass, store)
|
|
28
|
+
end
|
|
52
29
|
end
|
|
53
30
|
|
|
54
31
|
def build(name, store)
|
|
55
|
-
|
|
56
|
-
entry = registry.fetch(name.is_a?(Class) ? name.name : name)
|
|
57
|
-
|
|
58
|
-
|
|
32
|
+
facet = name.is_a?(Module) ? name : Object.const_get(name)
|
|
59
33
|
|
|
60
34
|
Api.new(
|
|
61
35
|
registry: self,
|
|
62
36
|
name:,
|
|
63
37
|
entity: (
|
|
64
|
-
|
|
65
|
-
.
|
|
38
|
+
facet
|
|
39
|
+
.server_entity_class
|
|
66
40
|
.new(
|
|
67
41
|
store: store.is_a?(Hash) ? OpenStruct.new(store) : store,
|
|
68
|
-
nesting:
|
|
69
|
-
parent: nil
|
|
42
|
+
nesting: "root",
|
|
43
|
+
parent: nil,
|
|
44
|
+
channel: Channel.new(registry: self)
|
|
70
45
|
)
|
|
71
46
|
)
|
|
72
47
|
)
|
|
73
48
|
end
|
|
74
|
-
|
|
75
|
-
def serialize
|
|
76
|
-
register
|
|
77
|
-
FileUtils.mkdir_p(build_dir)
|
|
78
|
-
|
|
79
|
-
registry.each do |name, entry|
|
|
80
|
-
base_name = "#{name}::Client"
|
|
81
|
-
|
|
82
|
-
header = ""
|
|
83
|
-
footer = ""
|
|
84
|
-
nesting = []
|
|
85
|
-
name.split("::").each do |mod|
|
|
86
|
-
nesting << mod
|
|
87
|
-
footer += "\nend"
|
|
88
|
-
header +=(
|
|
89
|
-
if nesting.join("::").constantize.is_a?(Class)
|
|
90
|
-
"\nclass #{mod}"
|
|
91
|
-
else
|
|
92
|
-
"\nmodule #{mod}"
|
|
93
|
-
end
|
|
94
|
-
)
|
|
95
|
-
end
|
|
96
|
-
header += "\nmodule Client"
|
|
97
|
-
footer += "\nend"
|
|
98
|
-
|
|
99
|
-
File.write(
|
|
100
|
-
File.join(build_dir, path_for("#{base_name}::Shared")),
|
|
101
|
-
<<~TXT
|
|
102
|
-
#{header}
|
|
103
|
-
class Shared < Interfacets::Shared::Entity
|
|
104
|
-
#{
|
|
105
|
-
entry
|
|
106
|
-
.fetch(:klass)
|
|
107
|
-
.shareds
|
|
108
|
-
.map { write_source(_1) }
|
|
109
|
-
.map { "class_exec(&#{_1})" }
|
|
110
|
-
.join("\n")
|
|
111
|
-
}
|
|
112
|
-
end
|
|
113
|
-
#{footer}
|
|
114
|
-
TXT
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
File.write(
|
|
118
|
-
File.join(build_dir, path_for("#{base_name}::View")),
|
|
119
|
-
<<~TXT
|
|
120
|
-
#{header}
|
|
121
|
-
class View < Interfacets::Client::View
|
|
122
|
-
#{
|
|
123
|
-
entry
|
|
124
|
-
.fetch(:klass)
|
|
125
|
-
.views
|
|
126
|
-
.map { write_source(_1) }
|
|
127
|
-
.map { "view(&#{_1})"}
|
|
128
|
-
.join("\n")
|
|
129
|
-
}
|
|
130
|
-
end
|
|
131
|
-
#{footer}
|
|
132
|
-
TXT
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
File.write(
|
|
136
|
-
File.join(build_dir, path_for("#{base_name}::Entity")),
|
|
137
|
-
<<~TXT
|
|
138
|
-
#{header}
|
|
139
|
-
class Entity < Interfacets::Shared::Entity
|
|
140
|
-
class << self
|
|
141
|
-
def view
|
|
142
|
-
#{base_name}::View
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def store
|
|
146
|
-
@store ||= (
|
|
147
|
-
::Interfacets::Shared::GeneratedStore.construct(self)
|
|
148
|
-
.tap { self.const_set("Store", _1) }
|
|
149
|
-
)
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
role("client")
|
|
154
|
-
|
|
155
|
-
#{
|
|
156
|
-
entry
|
|
157
|
-
.fetch(:klass)
|
|
158
|
-
.shareds
|
|
159
|
-
.map { write_source(_1) }
|
|
160
|
-
.map { "class_exec(&#{_1})" }
|
|
161
|
-
.join("\n")
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
#{
|
|
165
|
-
entry
|
|
166
|
-
.fetch(:klass)
|
|
167
|
-
.clients
|
|
168
|
-
.map { write_source(_1) }
|
|
169
|
-
.map { "class_exec(&#{_1})" }
|
|
170
|
-
.join("\n")
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
self.manifest = Shared
|
|
174
|
-
|
|
175
|
-
actions.each do |name, spec|
|
|
176
|
-
if name.start_with?("after_")
|
|
177
|
-
define_method(name) {}
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def channel(name)
|
|
182
|
-
Interfacets::Client::System.current_bus.channel(name).builder
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
#{footer}
|
|
186
|
-
TXT
|
|
187
|
-
)
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def registry
|
|
192
|
-
@registry ||= {}
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
private
|
|
196
|
-
|
|
197
|
-
def path_for(class_name)
|
|
198
|
-
"#{class_name.gsub("::", "-")}.rb"
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
def write_source(block)
|
|
202
|
-
return unless block
|
|
203
|
-
|
|
204
|
-
RubyVM::AbstractSyntaxTree
|
|
205
|
-
.of(block, keep_script_lines: true)
|
|
206
|
-
.source
|
|
207
|
-
.then { block.is_a?(UnboundMethod) ? _1 : "lambda #{_1}" }
|
|
208
|
-
.strip
|
|
209
|
-
end
|
|
210
49
|
end
|
|
211
50
|
end
|
|
212
51
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
module BasicRoutable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :bus
|
|
11
|
+
|
|
12
|
+
def build(klass, store)
|
|
13
|
+
bus.registry.build(klass, store)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# This is unfortunate, but in order to be able to do:
|
|
18
|
+
|
|
19
|
+
# build(self, ...)
|
|
20
|
+
# we need the self to be the facet.
|
|
21
|
+
# We could also update the registry to handle being passed a server
|
|
22
|
+
# entity, but I'm currently tweaking the registry. so this is a TODO.
|
|
23
|
+
facet = self
|
|
24
|
+
|
|
25
|
+
entity_base do
|
|
26
|
+
accessor(:api_path, accepted_by: :client)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
server_entity(unshift: true) do
|
|
30
|
+
attr_accessor :api_path
|
|
31
|
+
|
|
32
|
+
define_singleton_method(:find) do |&block|
|
|
33
|
+
@find = block if block
|
|
34
|
+
@find
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
define_singleton_method(:find_for_basic_routing) do |id, query:, bus:|
|
|
38
|
+
facet.bus = bus
|
|
39
|
+
facet.instance_exec(id, query:, &find)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -39,39 +39,26 @@ class Bus
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def handle(event:)
|
|
42
|
+
action = event.fetch("action")
|
|
43
|
+
|
|
42
44
|
assert_valid_receiver(to: event.fetch("to"))
|
|
43
45
|
assert_valid_action(
|
|
44
|
-
action
|
|
46
|
+
action:,
|
|
45
47
|
to: entity.role,
|
|
46
48
|
nesting: event.fetch("nesting"),
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
attributes = event.fetch("payload").fetch("attributes", {})
|
|
50
52
|
|
|
51
|
-
merge(entity:, manifest:, attributes:)
|
|
52
|
-
|
|
53
|
-
target_entity = entity
|
|
54
|
-
event.fetch("nesting")[1..-1].each do |assoc_name, id|
|
|
55
|
-
break if target_entity.nil?
|
|
56
|
-
|
|
57
|
-
spec = entity.association(assoc_name)
|
|
58
|
-
|
|
59
|
-
target_entity =(
|
|
60
|
-
if spec.type == :reference
|
|
61
|
-
target_entity = spec.get
|
|
62
|
-
else
|
|
63
|
-
spec.get.find { _1.internal_entity_id == id }
|
|
64
|
-
end
|
|
65
|
-
)
|
|
66
|
-
end
|
|
53
|
+
merge(entity:, manifest:, attributes:, action:)
|
|
67
54
|
|
|
68
|
-
|
|
55
|
+
target_entity = entity.entity_at(event.fetch("nesting"))
|
|
69
56
|
|
|
70
57
|
target_entity
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
&.class
|
|
59
|
+
&.actions
|
|
60
|
+
&.fetch(event.fetch("action"))
|
|
61
|
+
&.dispatch(target_entity)
|
|
75
62
|
end
|
|
76
63
|
|
|
77
64
|
private
|
|
@@ -81,6 +68,8 @@ class Bus
|
|
|
81
68
|
|
|
82
69
|
data = {}
|
|
83
70
|
|
|
71
|
+
data[:errors] = entity.errors.to_h if entity.respond_to?(:errors)
|
|
72
|
+
|
|
84
73
|
manifest
|
|
85
74
|
.accessors
|
|
86
75
|
.each do |name, spec|
|
|
@@ -110,7 +99,14 @@ class Bus
|
|
|
110
99
|
data
|
|
111
100
|
end
|
|
112
101
|
|
|
113
|
-
def merge(manifest:, entity:, attributes:)
|
|
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
|
+
|
|
114
110
|
manifest
|
|
115
111
|
.accessors
|
|
116
112
|
.values
|
|
@@ -118,7 +114,19 @@ class Bus
|
|
|
118
114
|
.each do |attribute|
|
|
119
115
|
next unless attributes.key?(attribute.name)
|
|
120
116
|
|
|
121
|
-
|
|
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])
|
|
122
130
|
end
|
|
123
131
|
|
|
124
132
|
manifest
|
|
@@ -140,11 +148,12 @@ class Bus
|
|
|
140
148
|
.association(association.name)
|
|
141
149
|
.get
|
|
142
150
|
.then { _1 || entity.association(association.name).build }
|
|
143
|
-
.tap {
|
|
151
|
+
.tap { |nested_entity|
|
|
144
152
|
merge(
|
|
145
|
-
entity:
|
|
153
|
+
entity: nested_entity,
|
|
146
154
|
manifest: association.klass,
|
|
147
|
-
attributes: value
|
|
155
|
+
attributes: value,
|
|
156
|
+
action:
|
|
148
157
|
)
|
|
149
158
|
}
|
|
150
159
|
.then { entity.association(association.name).set(_1) }
|
|
@@ -175,7 +184,8 @@ class Bus
|
|
|
175
184
|
merge(
|
|
176
185
|
entity: r,
|
|
177
186
|
manifest: collection.klass,
|
|
178
|
-
attributes: val
|
|
187
|
+
attributes: val,
|
|
188
|
+
action:
|
|
179
189
|
)
|
|
180
190
|
}
|
|
181
191
|
else
|
|
@@ -186,7 +196,8 @@ class Bus
|
|
|
186
196
|
merge(
|
|
187
197
|
entity: r,
|
|
188
198
|
manifest: collection.klass,
|
|
189
|
-
attributes: val
|
|
199
|
+
attributes: val,
|
|
200
|
+
action:
|
|
190
201
|
)
|
|
191
202
|
}
|
|
192
203
|
end
|
|
@@ -198,6 +209,7 @@ class Bus
|
|
|
198
209
|
|
|
199
210
|
def assert_valid_action(action:, to:, nesting:)
|
|
200
211
|
nested_manifest = manifest
|
|
212
|
+
|
|
201
213
|
nesting[1..-1].each do |assoc_name, _id|
|
|
202
214
|
nested_manifest = manifest.associations.fetch(assoc_name).klass
|
|
203
215
|
end
|
|
@@ -43,6 +43,14 @@ module Interfacets
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
class Reference < Handler
|
|
46
|
+
def collection?
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reference?
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
def get
|
|
47
55
|
wrap(entity.instance_exec(&spec.getter))
|
|
48
56
|
.tap {
|
|
@@ -70,6 +78,14 @@ module Interfacets
|
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
class Collection < Handler
|
|
81
|
+
def collection?
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def reference?
|
|
86
|
+
false
|
|
87
|
+
end
|
|
88
|
+
|
|
73
89
|
def get
|
|
74
90
|
values = entity.instance_exec(&spec.getter) || []
|
|
75
91
|
|
|
@@ -72,6 +72,7 @@ module Interfacets
|
|
|
72
72
|
:builder,
|
|
73
73
|
:parent,
|
|
74
74
|
:identifier,
|
|
75
|
+
:type,
|
|
75
76
|
)
|
|
76
77
|
def initialize(parent:, name:)
|
|
77
78
|
@parent = parent
|
|
@@ -81,6 +82,7 @@ module Interfacets
|
|
|
81
82
|
@setter = ->(val) { store.send("#{name}=", val) }
|
|
82
83
|
@builder = ->() { store.association(name).build }
|
|
83
84
|
@identifier = "id"
|
|
85
|
+
|
|
84
86
|
@klass = Class.new(Entity) do
|
|
85
87
|
define_singleton_method(:name) { "#{parent.name}.#{name}" }
|
|
86
88
|
end
|
|
@@ -92,6 +94,8 @@ module Interfacets
|
|
|
92
94
|
setter: NOT_PASSED,
|
|
93
95
|
builder: NOT_PASSED,
|
|
94
96
|
identifier: NOT_PASSED,
|
|
97
|
+
mod: nil,
|
|
98
|
+
type: NOT_PASSED,
|
|
95
99
|
&block
|
|
96
100
|
)
|
|
97
101
|
@accepted_by = Array(accepted_by).map(&:to_s) unless accepted_by == NOT_PASSED
|
|
@@ -99,23 +103,56 @@ module Interfacets
|
|
|
99
103
|
@setter = setter unless setter == NOT_PASSED
|
|
100
104
|
@builder = builder unless builder == NOT_PASSED
|
|
101
105
|
@identifier = identifier.to_s unless identifier == NOT_PASSED
|
|
102
|
-
|
|
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?
|
|
103
123
|
end
|
|
104
|
-
end
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
108
131
|
|
|
109
132
|
def handler(entity)
|
|
110
|
-
|
|
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)
|
|
111
144
|
end
|
|
112
145
|
end
|
|
113
146
|
|
|
114
|
-
class
|
|
115
|
-
|
|
147
|
+
class Merger
|
|
148
|
+
attr_reader(:name, :block)
|
|
149
|
+
def initialize(name:, block:)
|
|
150
|
+
@name = name
|
|
151
|
+
@block = block
|
|
152
|
+
end
|
|
116
153
|
|
|
117
|
-
def
|
|
118
|
-
|
|
154
|
+
def call(entity, value)
|
|
155
|
+
block.call(entity, value)
|
|
119
156
|
end
|
|
120
157
|
end
|
|
121
158
|
end
|