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.
- checksums.yaml +4 -4
- data/.rubocop.yml +173 -1
- data/.tmp +5 -0
- data/LICENSE +21 -0
- data/Rakefile +9 -7
- data/lib/interfacets/client/actor.rb +20 -0
- data/lib/interfacets/client/assets.rb +210 -0
- data/lib/interfacets/client/bus.rb +73 -0
- data/lib/interfacets/client/channels/api.rb +102 -0
- data/lib/interfacets/client/channels/audio.rb +101 -0
- data/lib/interfacets/client/channels/base.rb +28 -0
- data/lib/interfacets/client/channels/page_visibility.rb +21 -0
- data/lib/interfacets/client/channels/react/builder.rb +203 -0
- data/lib/interfacets/client/channels/react/channel.rb +61 -0
- data/lib/interfacets/client/channels/react/dom.rb +91 -0
- data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
- data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
- data/lib/interfacets/client/channels/timer.rb +51 -0
- data/lib/interfacets/client/channels/url.rb +52 -0
- data/lib/interfacets/client/config.rb +22 -0
- data/lib/interfacets/client/delegator.rb +37 -0
- data/lib/interfacets/client/registry.rb +23 -0
- data/lib/interfacets/client/system.rb +88 -0
- data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
- data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
- data/lib/interfacets/client/utils/open_struct.rb +115 -0
- data/lib/interfacets/client/utils/securerandom.rb +69 -0
- data/lib/interfacets/client.rb +13 -0
- data/lib/interfacets/component_registry.rb +115 -0
- data/lib/interfacets/component_schema_parser.rb +84 -0
- data/lib/interfacets/mruby/build.dockerfile +66 -0
- data/lib/interfacets/mruby/build_config.rb +20 -0
- data/lib/interfacets/mruby/entrypoint.rb +23 -0
- data/lib/interfacets/mruby/init.c +66 -0
- data/lib/interfacets/server/api.rb +44 -0
- data/lib/interfacets/server/assets/facet.rb +63 -0
- data/lib/interfacets/server/assets.rb +216 -0
- data/lib/interfacets/server/basic_router.rb +79 -0
- data/lib/interfacets/server/bus.rb +34 -0
- data/lib/interfacets/server/config.rb +87 -0
- data/lib/interfacets/server/facets/deserializer.rb +25 -0
- data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
- data/lib/interfacets/server/facets/serializer.rb +50 -0
- data/lib/interfacets/server/registry.rb +51 -0
- data/lib/interfacets/shared/basic_routable.rb +45 -0
- data/lib/interfacets/shared/entities/bus.rb +230 -0
- data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
- data/lib/interfacets/shared/entities/specs/handlers.rb +133 -0
- data/lib/interfacets/shared/entities/specs.rb +161 -0
- data/lib/interfacets/shared/entity.rb +102 -0
- data/lib/interfacets/shared/entity_dsl.rb +154 -0
- data/lib/interfacets/shared/facet.rb +200 -0
- data/lib/interfacets/shared/generated_store.rb +149 -0
- data/lib/interfacets/shared/utils.rb +54 -0
- data/lib/interfacets/shared/validations.rb +75 -0
- data/lib/interfacets/shared/view.rb +74 -0
- data/lib/interfacets/test/component_registry.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +100 -0
- data/lib/interfacets/test/js/nodo_bus.rb +98 -0
- data/lib/interfacets/test/js/receivers/api.rb +48 -0
- data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +133 -0
- data/lib/interfacets/test/js/receivers/react.rb +32 -0
- data/lib/interfacets/test/js/receivers/timer.rb +77 -0
- data/lib/interfacets/test/js/receivers/url.rb +60 -0
- data/lib/interfacets/test/standard_elements.yml +173 -0
- data/lib/interfacets/test/ui_simulator.rb +75 -0
- data/lib/interfacets/test/validation_engine.rb +151 -0
- data/lib/interfacets/test.rb +13 -0
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +29 -2
- metadata +114 -6
- data/README.md +0 -35
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
class Entity
|
|
6
|
+
include Shared::Validations
|
|
7
|
+
include EntityDsl
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def inherited(subclass)
|
|
11
|
+
subclass.inherit_attributes(self)
|
|
12
|
+
|
|
13
|
+
subclass.action(:after_load, accepted_by: :client)
|
|
14
|
+
|
|
15
|
+
subclass.accessor(
|
|
16
|
+
:internal_entity_id,
|
|
17
|
+
getter: -> {
|
|
18
|
+
if store.respond_to?(:internal_entity_id)
|
|
19
|
+
store.internal_entity_id
|
|
20
|
+
else
|
|
21
|
+
(@interfacets_id ||= SecureRandom.uuid)
|
|
22
|
+
end
|
|
23
|
+
},
|
|
24
|
+
setter: ->(val) {
|
|
25
|
+
if store.respond_to?(:internal_entity_id=)
|
|
26
|
+
store.internal_entity_id = val
|
|
27
|
+
else
|
|
28
|
+
@interfacets_id = val
|
|
29
|
+
end
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :store, :parent
|
|
36
|
+
def initialize(store:, nesting:, parent:)
|
|
37
|
+
@store = store
|
|
38
|
+
@parent = parent
|
|
39
|
+
@nesting = nesting
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def record
|
|
43
|
+
store
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ==(other)
|
|
47
|
+
if other.is_a?(Entity)
|
|
48
|
+
store == other.store
|
|
49
|
+
else
|
|
50
|
+
store == other
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def role
|
|
55
|
+
if parent.nil?
|
|
56
|
+
self.class.role
|
|
57
|
+
else
|
|
58
|
+
parent.role
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def uid
|
|
63
|
+
[self.class.name, internal_entity_id].join("__")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def entity_nesting
|
|
67
|
+
@entity_nesting ||= (parent&.entity_nesting || []) + [[@nesting, internal_entity_id]]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def entity_at(nesting)
|
|
71
|
+
# ignore first entry, cause it's self
|
|
72
|
+
real_nesting = nesting[1..-1]
|
|
73
|
+
|
|
74
|
+
if real_nesting.count == 0
|
|
75
|
+
self
|
|
76
|
+
else
|
|
77
|
+
assoc, internal_entity_id = real_nesting[0]
|
|
78
|
+
|
|
79
|
+
if association(assoc).collection?
|
|
80
|
+
association(assoc).get.find { _1.internal_entity_id == internal_entity_id }
|
|
81
|
+
else
|
|
82
|
+
value = association(assoc).get
|
|
83
|
+
value.internal_entity_id == internal_entity_id ? value : nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def association(name)
|
|
89
|
+
# inlined to avoid polluting API
|
|
90
|
+
@association_handlers ||= (
|
|
91
|
+
self
|
|
92
|
+
.class
|
|
93
|
+
.associations
|
|
94
|
+
.map { |name, spec| [name, spec.handler(self) ] }
|
|
95
|
+
.to_h
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@association_handlers.fetch(name.to_s)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
module EntityDsl
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do |base|
|
|
9
|
+
if base.respond_to?(:inherit_attributes)
|
|
10
|
+
base.inherit_attributes(self)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class_methods do
|
|
15
|
+
attr_accessor :manifest
|
|
16
|
+
|
|
17
|
+
def role(name = nil)
|
|
18
|
+
if name
|
|
19
|
+
@role = name.to_s
|
|
20
|
+
else
|
|
21
|
+
@role
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def inheritable_attributes
|
|
26
|
+
{
|
|
27
|
+
accessors:,
|
|
28
|
+
actions:,
|
|
29
|
+
mergers:,
|
|
30
|
+
role: @role,
|
|
31
|
+
associations:,
|
|
32
|
+
validators:
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def inherit_attributes(parent)
|
|
37
|
+
return unless parent.respond_to?(:inheritable_attributes)
|
|
38
|
+
attrs = parent.inheritable_attributes
|
|
39
|
+
@accessors = attrs[:accessors].dup.merge(accessors)
|
|
40
|
+
@actions = attrs[:actions].dup.merge(actions)
|
|
41
|
+
@mergers = attrs[:mergers].dup.merge(mergers)
|
|
42
|
+
@role ||= attrs[:role]
|
|
43
|
+
|
|
44
|
+
attrs[:associations].each do |name, spec|
|
|
45
|
+
associations[name] ||= spec.dup_for(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@validators = (attrs[:validators].dup + validators).uniq
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def merge(name, *events, &block)
|
|
52
|
+
spec = Entities::Specs::Merger.new(
|
|
53
|
+
name:,
|
|
54
|
+
block:
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
target = (mergers[name.to_s] ||= {})
|
|
58
|
+
|
|
59
|
+
if events.empty?
|
|
60
|
+
target[:default] = spec
|
|
61
|
+
else
|
|
62
|
+
events.each do |event|
|
|
63
|
+
target[event.to_s] = spec
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def accessor(
|
|
69
|
+
name,
|
|
70
|
+
getter: -> { store.send(name) },
|
|
71
|
+
setter: ->(val) { store.send("#{name}=", val) },
|
|
72
|
+
accepted_by: Entities::Specs::ANY
|
|
73
|
+
)
|
|
74
|
+
name = name.to_s
|
|
75
|
+
accessors[name] = Entities::Specs::Accessor.new(name:, accepted_by:)
|
|
76
|
+
|
|
77
|
+
define_method(name, &getter)
|
|
78
|
+
define_method("#{name}=", &setter)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def association(name, mod = nil, **spec_params, &block)
|
|
82
|
+
name = name.to_s
|
|
83
|
+
associations[name] ||= Entities::Specs::Association.new(parent: self, name: name)
|
|
84
|
+
associations[name].apply(mod:, **spec_params, &block)
|
|
85
|
+
|
|
86
|
+
define_method(name) do
|
|
87
|
+
association(name).get
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
define_method("#{name}=") do |val|
|
|
91
|
+
association(name).set(val)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def reference(*a, **p, &b)
|
|
96
|
+
association(*a, **p, type: :reference, &b)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def collection(*a, **p, &b)
|
|
100
|
+
association(*a, **p, type: :collection, &b)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def server_action(name, only_if_valid: true)
|
|
104
|
+
action(name, accepted_by: :server, only_if_valid:)
|
|
105
|
+
action("after_#{name}", accepted_by: :client)
|
|
106
|
+
|
|
107
|
+
define_method(name) do
|
|
108
|
+
store.send(name, entity: self)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def action(name, accepted_by: Entities::Specs::ANY, only_if_valid: false)
|
|
113
|
+
name = name.to_s
|
|
114
|
+
actions[name] = Entities::Specs::Action.new(name:, accepted_by:, only_if_valid:)
|
|
115
|
+
|
|
116
|
+
define_method(name) {}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def accessors
|
|
120
|
+
@accessors ||= {}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def associations
|
|
124
|
+
@associations ||= {}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def actions
|
|
128
|
+
@actions ||= {}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def mergers
|
|
132
|
+
@mergers ||= Hash.new { |h, k|
|
|
133
|
+
h[k] = {
|
|
134
|
+
default: Entities::Specs::Merger.new(
|
|
135
|
+
name: k,
|
|
136
|
+
block: ->(entity, value) {
|
|
137
|
+
entity.send("#{k}=", value)
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def attributes
|
|
145
|
+
accessors.merge(associations)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def validators
|
|
149
|
+
@validators ||= []
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
module Facet
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
attr_accessor :shared, :entity, :store
|
|
10
|
+
|
|
11
|
+
def mount(facet, as:, type: nil, &block)
|
|
12
|
+
if block_given?
|
|
13
|
+
parent_facet = facet
|
|
14
|
+
facet = Module.new do
|
|
15
|
+
include Interfacets::Shared::Facet
|
|
16
|
+
|
|
17
|
+
server_entity do
|
|
18
|
+
include parent_facet.server_entity_module
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
entity_base do
|
|
22
|
+
include parent_facet.entity_base_module
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
client_entity do
|
|
26
|
+
include parent_facet.client_entity_module
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
view_class.view(&parent_facet.view_class.view) if parent_facet.view_class.view
|
|
30
|
+
|
|
31
|
+
class_exec(&block)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
server_entity do
|
|
36
|
+
association(as, facet.server_entity_module, type:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
client_entity do
|
|
40
|
+
association(as, facet.client_entity_module, type:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
entity_base do
|
|
44
|
+
association(as, facet.entity_base_module, type:)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def view(&block)
|
|
49
|
+
view_class.view(&block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def view_class
|
|
53
|
+
@view_class ||= Class.new(Interfacets::Shared::View)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def client_entity(&block)
|
|
57
|
+
clients << block
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def clients
|
|
61
|
+
@clients ||= []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def client_entity_module
|
|
65
|
+
@client_entity_module ||= (
|
|
66
|
+
facet = self
|
|
67
|
+
Module.new do
|
|
68
|
+
extend ActiveSupport::Concern
|
|
69
|
+
include facet.entity_base_module
|
|
70
|
+
|
|
71
|
+
included do
|
|
72
|
+
define_singleton_method(:view) do
|
|
73
|
+
facet.view_class
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
role("client")
|
|
77
|
+
facet.clients.each { class_exec(&_1) }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def client_entity_class
|
|
84
|
+
@client_entity_class ||= (
|
|
85
|
+
facet = self
|
|
86
|
+
Class
|
|
87
|
+
.new(entity_base_class)
|
|
88
|
+
.tap do |k|
|
|
89
|
+
k.class_exec do
|
|
90
|
+
include facet.client_entity_module
|
|
91
|
+
|
|
92
|
+
define_singleton_method(:store) do
|
|
93
|
+
@store ||= (
|
|
94
|
+
::Interfacets::Shared::GeneratedStore.construct(self)
|
|
95
|
+
.tap { self.const_set("Store", _1) }
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
self.manifest = facet.entity_base_class
|
|
100
|
+
|
|
101
|
+
def channel(name)
|
|
102
|
+
Interfacets::Client::System.current_bus.channel(name).builder
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def entity_base(&block)
|
|
110
|
+
bases << block
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def bases
|
|
114
|
+
@bases ||= []
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def entity_base_module
|
|
118
|
+
@entity_base_module ||= (
|
|
119
|
+
facet = self
|
|
120
|
+
Module.new do
|
|
121
|
+
extend ActiveSupport::Concern
|
|
122
|
+
include Interfacets::Shared::EntityDsl
|
|
123
|
+
included do
|
|
124
|
+
facet.bases.each { class_exec(&_1) }
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def entity_base_class
|
|
131
|
+
@entity_base_class ||= (
|
|
132
|
+
facet = self
|
|
133
|
+
|
|
134
|
+
Class.new(Shared::Entity)
|
|
135
|
+
.tap { |k| k.include(facet.entity_base_module) }
|
|
136
|
+
.tap { facet.const_set("EntityBase", _1) }
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def server_entity_module
|
|
141
|
+
@server_entity_module ||= (
|
|
142
|
+
facet = self
|
|
143
|
+
Module.new do
|
|
144
|
+
extend ActiveSupport::Concern
|
|
145
|
+
include facet.entity_base_module
|
|
146
|
+
included do
|
|
147
|
+
role("server")
|
|
148
|
+
facet.servers.each { class_exec(&_1) }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def server_entity_class
|
|
155
|
+
@server_entity_class ||= (
|
|
156
|
+
facet = self
|
|
157
|
+
|
|
158
|
+
Class.new(entity_base_class) {
|
|
159
|
+
include facet.server_entity_module
|
|
160
|
+
|
|
161
|
+
attr_reader :channel
|
|
162
|
+
def initialize(*a, channel:, **p, &b)
|
|
163
|
+
@channel = channel
|
|
164
|
+
super(*a, **p, &b)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def build_entity(...)
|
|
168
|
+
channel.build(...)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
self.manifest = facet.entity_base_class
|
|
172
|
+
}
|
|
173
|
+
).tap { facet.const_set("ServerBase", _1) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def server_entity(unshift: false, &block)
|
|
177
|
+
if unshift
|
|
178
|
+
servers.unshift(block)
|
|
179
|
+
else
|
|
180
|
+
servers << block
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def servers
|
|
185
|
+
@servers ||= []
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
attr_reader :entity, :view
|
|
190
|
+
def initialize(entity)
|
|
191
|
+
@entity = entity
|
|
192
|
+
@view = self.class.view.new if self.class.view.is_a?(Class)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def render(channels)
|
|
196
|
+
view.render(entity:, channels:)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
class GeneratedStore
|
|
6
|
+
class Constructor
|
|
7
|
+
def self.call(entity_class)
|
|
8
|
+
klass = Class.new(GeneratedStore) do
|
|
9
|
+
define_singleton_method(:name) { "#{entity_class.name}.GeneratedStore" }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
entity_class.accessors.each do |name, spec|
|
|
13
|
+
klass.accessor(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
entity_class.associations.each do |name, spec|
|
|
17
|
+
sub_class = call(spec.klass)
|
|
18
|
+
|
|
19
|
+
if spec.type == :reference
|
|
20
|
+
klass.association(name, klass: sub_class)
|
|
21
|
+
else
|
|
22
|
+
klass.collection(name, klass: sub_class)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
entity_class.actions.each do |name, spec|
|
|
27
|
+
klass.define_method(name) do |entity:|
|
|
28
|
+
Client::System
|
|
29
|
+
.current_bus
|
|
30
|
+
.channel("interfacets:api")
|
|
31
|
+
.builder
|
|
32
|
+
.submit(name, nesting: entity.entity_nesting)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
klass
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Handler
|
|
41
|
+
attr_reader :store, :spec
|
|
42
|
+
def initialize(store:, spec:)
|
|
43
|
+
@store = store
|
|
44
|
+
@spec = spec
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build
|
|
48
|
+
if spec.type == :reference
|
|
49
|
+
value = spec.klass.new
|
|
50
|
+
store.send("#{spec.name}=", value)
|
|
51
|
+
value
|
|
52
|
+
else
|
|
53
|
+
value = spec.klass.new
|
|
54
|
+
items = store.send(spec.name)
|
|
55
|
+
if items.nil?
|
|
56
|
+
store.send("#{spec.name}=", [value])
|
|
57
|
+
else
|
|
58
|
+
items << value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class << self
|
|
68
|
+
def construct(...)
|
|
69
|
+
Constructor.call(...)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Spec
|
|
73
|
+
attr_reader(:name, :klass, :type)
|
|
74
|
+
def initialize(name:, klass:, type:)
|
|
75
|
+
@name = name
|
|
76
|
+
@klass = klass
|
|
77
|
+
@type = type
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def build
|
|
81
|
+
spec.klass.new
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def accessor(name)
|
|
86
|
+
name = name.to_s
|
|
87
|
+
|
|
88
|
+
define_method(name) do
|
|
89
|
+
@attributes[name]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
define_method("#{name}=") do |val|
|
|
93
|
+
@attributes[name] = val
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def association(name, klass:, type: :reference)
|
|
98
|
+
name = name.to_s
|
|
99
|
+
|
|
100
|
+
associations[name] = Spec.new(
|
|
101
|
+
name:,
|
|
102
|
+
klass:,
|
|
103
|
+
type:,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
define_method(name) do
|
|
107
|
+
@attributes[name]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
define_method("#{name}=") do |val|
|
|
111
|
+
@attributes[name] = val
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def collection(*args, **params)
|
|
116
|
+
association(*args, **params, type: :collection)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def associations
|
|
120
|
+
@associations ||= {}
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
attr_accessor :attributes
|
|
125
|
+
def initialize
|
|
126
|
+
@attributes = {
|
|
127
|
+
"internal_entity_id" => SecureRandom.uuid
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def ==(other)
|
|
132
|
+
attributes == other.attributes
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def association(name)
|
|
136
|
+
@association_handlers ||= (
|
|
137
|
+
self
|
|
138
|
+
.class
|
|
139
|
+
.associations
|
|
140
|
+
.map { |name, spec| [name, Handler.new(store: self, spec:)] }
|
|
141
|
+
.to_h
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@association_handlers.fetch(name)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
module Utils
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def permissive_exec(receiver, *, &lambda)
|
|
9
|
+
if lambda.arity.zero?
|
|
10
|
+
receiver.instance_exec(&lambda)
|
|
11
|
+
else
|
|
12
|
+
receiver.instance_exec(*, &lambda)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def permissive_call(lambda, ...)
|
|
17
|
+
if lambda.arity.zero?
|
|
18
|
+
lambda.call
|
|
19
|
+
else
|
|
20
|
+
lambda.call(...)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def facet_id_to_path(config:, facet:, id:)
|
|
25
|
+
[config.mount_point, facet, id].join("/")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def path_to_facet_id(config:, path:)
|
|
29
|
+
parts = path.sub(/.*#{config.mount_point}/, "").split("/")
|
|
30
|
+
id = parts.pop
|
|
31
|
+
facet = parts.join("/")
|
|
32
|
+
|
|
33
|
+
[facet, id]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# ActiveSupport?
|
|
37
|
+
def blank?(str)
|
|
38
|
+
return true if str.nil?
|
|
39
|
+
return str.empty? if str.is_a?(Array)
|
|
40
|
+
return str.match(/\A\s*\z/) if str.is_a?(String)
|
|
41
|
+
|
|
42
|
+
raise("unsupported blank check")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def present?(str)
|
|
46
|
+
!blank?(str)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def presence(obj)
|
|
50
|
+
present?(obj) ? obj : nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|