interfacets 0.1.0 → 0.9.9
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/LICENSE +21 -0
- data/Rakefile +4 -6
- data/lib/interfacets/client/actor.rb +20 -0
- data/lib/interfacets/client/assets.rb +209 -0
- data/lib/interfacets/client/bus.rb +69 -0
- data/lib/interfacets/client/channels/api.rb +95 -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/facet.rb +26 -0
- data/lib/interfacets/client/facet2.rb +15 -0
- data/lib/interfacets/client/facets/attributes/accessor.rb +28 -0
- data/lib/interfacets/client/facets/attributes/association.rb +50 -0
- data/lib/interfacets/client/facets/attributes/bind.rb +25 -0
- data/lib/interfacets/client/facets/attributes/collection.rb +47 -0
- data/lib/interfacets/client/facets/attributes/readonly.rb +19 -0
- data/lib/interfacets/client/facets/deserializer.rb +30 -0
- data/lib/interfacets/client/facets/schema/deserializer.rb +63 -0
- data/lib/interfacets/client/facets/schema.rb +63 -0
- data/lib/interfacets/client/facets/serializer.rb +18 -0
- data/lib/interfacets/client/registry.rb +84 -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 +102 -0
- data/lib/interfacets/client/utils/securerandom.rb +69 -0
- data/lib/interfacets/client/view.rb +47 -0
- data/lib/interfacets/client.rb +13 -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 +64 -0
- data/lib/interfacets/server/assets/facet.rb +61 -0
- data/lib/interfacets/server/assets.rb +210 -0
- data/lib/interfacets/server/basic_routable.rb +40 -0
- data/lib/interfacets/server/basic_router.rb +74 -0
- data/lib/interfacets/server/bus.rb +39 -0
- data/lib/interfacets/server/config.rb +87 -0
- data/lib/interfacets/server/facet.rb +51 -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 +212 -0
- data/lib/interfacets/shared/entities/bus.rb +218 -0
- data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
- data/lib/interfacets/shared/entities/specs/handlers.rb +117 -0
- data/lib/interfacets/shared/entities/specs.rb +124 -0
- data/lib/interfacets/shared/entity.rb +178 -0
- data/lib/interfacets/shared/entity_collection.rb +88 -0
- data/lib/interfacets/shared/generated_store.rb +145 -0
- data/lib/interfacets/shared/utils.rb +54 -0
- data/lib/interfacets/shared/validations.rb +71 -0
- data/lib/interfacets/test/browser.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +91 -0
- data/lib/interfacets/test/js/nodo_bus.rb +81 -0
- data/lib/interfacets/test/js/receivers/api.rb +37 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +167 -0
- data/lib/interfacets/test/js/receivers/react.rb +31 -0
- data/lib/interfacets/test/js/receivers/url.rb +55 -0
- data/lib/interfacets/test.rb +17 -0
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +26 -2
- metadata +103 -6
- data/README.md +0 -35
|
@@ -0,0 +1,117 @@
|
|
|
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 get
|
|
47
|
+
wrap(entity.instance_exec(&spec.getter))
|
|
48
|
+
.tap {
|
|
49
|
+
@cache = {}
|
|
50
|
+
@cache[_1&.store] = _1
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def set(val)
|
|
55
|
+
entity.instance_exec(unwrap(val), &spec.setter)
|
|
56
|
+
wrap(val)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build(**attributes)
|
|
60
|
+
value = entity.instance_exec(&spec.builder)
|
|
61
|
+
attributes.each do |name, val|
|
|
62
|
+
value.send("#{name}=", val)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# TODO: make sure this is right
|
|
66
|
+
# set(value)
|
|
67
|
+
|
|
68
|
+
wrap(value)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Collection < Handler
|
|
73
|
+
def get
|
|
74
|
+
values = entity.instance_exec(&spec.getter) || []
|
|
75
|
+
|
|
76
|
+
CollectionProxy.new(
|
|
77
|
+
values,
|
|
78
|
+
wrap: ->(val) { wrap(val) },
|
|
79
|
+
unwrap: ->(val) { unwrap(val) },
|
|
80
|
+
).tap { |entities|
|
|
81
|
+
@cache = entities.map { [_1.store, _1] }.to_h
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def set(val)
|
|
86
|
+
@cache ||= {}
|
|
87
|
+
val.each do |item|
|
|
88
|
+
if item.is_a?(Entity)
|
|
89
|
+
@cache[item.store] = item
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
entity.instance_exec(unwrap(val), &spec.setter)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def build(**attributes)
|
|
97
|
+
value = unwrap(entity.instance_exec(&spec.builder))
|
|
98
|
+
attributes.each do |name, val|
|
|
99
|
+
value.send("#{name}=", val)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
items = entity.instance_exec(&spec.getter)
|
|
103
|
+
|
|
104
|
+
if items.nil?
|
|
105
|
+
set([value])
|
|
106
|
+
elsif !items.include?(value)
|
|
107
|
+
items << value
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
wrap(value)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
)
|
|
76
|
+
def initialize(parent:, name:)
|
|
77
|
+
@parent = parent
|
|
78
|
+
@name = name
|
|
79
|
+
@accepted_by = Entities::Specs::ANY
|
|
80
|
+
@getter = -> { store.send(name) }
|
|
81
|
+
@setter = ->(val) { store.send("#{name}=", val) }
|
|
82
|
+
@builder = ->() { store.association(name).build }
|
|
83
|
+
@identifier = "id"
|
|
84
|
+
@klass = Class.new(Entity) do
|
|
85
|
+
define_singleton_method(:name) { "#{parent.name}.#{name}" }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def apply(
|
|
90
|
+
accepted_by: NOT_PASSED,
|
|
91
|
+
getter: NOT_PASSED,
|
|
92
|
+
setter: NOT_PASSED,
|
|
93
|
+
builder: NOT_PASSED,
|
|
94
|
+
identifier: NOT_PASSED,
|
|
95
|
+
&block
|
|
96
|
+
)
|
|
97
|
+
@accepted_by = Array(accepted_by).map(&:to_s) unless accepted_by == NOT_PASSED
|
|
98
|
+
@getter = getter unless getter == NOT_PASSED
|
|
99
|
+
@setter = setter unless setter == NOT_PASSED
|
|
100
|
+
@builder = builder unless builder == NOT_PASSED
|
|
101
|
+
@identifier = identifier.to_s unless identifier == NOT_PASSED
|
|
102
|
+
klass.class_exec(&block) if block_given?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class Reference < Association
|
|
107
|
+
def type = :reference
|
|
108
|
+
|
|
109
|
+
def handler(entity)
|
|
110
|
+
Handlers::Reference.new(entity:, spec: self)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class Collection < Association
|
|
115
|
+
def type = :collection
|
|
116
|
+
|
|
117
|
+
def handler(entity)
|
|
118
|
+
Handlers::Collection.new(entity:, spec: self)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
class Entity
|
|
6
|
+
include Shared::Validations
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :manifest
|
|
10
|
+
|
|
11
|
+
def role(name = nil)
|
|
12
|
+
@role = name.to_s if name
|
|
13
|
+
@role
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def accessor(
|
|
17
|
+
name,
|
|
18
|
+
getter: -> { store.send(name) },
|
|
19
|
+
setter: ->(val) { store.send("#{name}=", val) },
|
|
20
|
+
accepted_by: Entities::Specs::ANY
|
|
21
|
+
)
|
|
22
|
+
name = name.to_s
|
|
23
|
+
accessors[name] = Entities::Specs::Accessor.new(name:, accepted_by:)
|
|
24
|
+
|
|
25
|
+
define_method(name, &getter)
|
|
26
|
+
define_method("#{name}=", &setter)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def association(*args, type: :reference, **params, &block)
|
|
30
|
+
if type == :reference
|
|
31
|
+
reference(*args, **params, &block)
|
|
32
|
+
elsif type == :collection
|
|
33
|
+
collection(*args, **params, &block)
|
|
34
|
+
else
|
|
35
|
+
raise ArgumentError
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reference(
|
|
40
|
+
name,
|
|
41
|
+
**spec_params,
|
|
42
|
+
&block
|
|
43
|
+
)
|
|
44
|
+
name = name.to_s
|
|
45
|
+
spec = (associations[name] ||= Entities::Specs::Reference.new(parent: self, name: name))
|
|
46
|
+
spec.apply(**spec_params, &block)
|
|
47
|
+
|
|
48
|
+
define_method(name) do
|
|
49
|
+
association(name).get
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
define_method("#{name}=") do |val|
|
|
53
|
+
association(name).set(val)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def collection(
|
|
58
|
+
name,
|
|
59
|
+
**spec_params,
|
|
60
|
+
&block
|
|
61
|
+
)
|
|
62
|
+
name = name.to_s
|
|
63
|
+
|
|
64
|
+
spec = (associations[name] ||= Entities::Specs::Collection.new(parent: self, name: name))
|
|
65
|
+
spec.apply(**spec_params, &block)
|
|
66
|
+
|
|
67
|
+
define_method(name) do
|
|
68
|
+
association(name).get
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
define_method("#{name}=") do |val|
|
|
72
|
+
association(name).set(val)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def server_action(name, only_if_valid: true)
|
|
77
|
+
action(name, accepted_by: :server, only_if_valid:)
|
|
78
|
+
action("after_#{name}", accepted_by: :client)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def action(name, accepted_by: Entities::Specs::ANY, only_if_valid: false)
|
|
82
|
+
name = name.to_s
|
|
83
|
+
actions[name] = Entities::Specs::Action.new(name:, accepted_by:, only_if_valid:)
|
|
84
|
+
|
|
85
|
+
define_method(name) do
|
|
86
|
+
store.send(name)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def accessors
|
|
91
|
+
@accessors ||= {}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def associations
|
|
95
|
+
@associations ||= {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def actions
|
|
99
|
+
@actions ||= {}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def attributes
|
|
103
|
+
accessors.merge(associations)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def inherited(mod)
|
|
107
|
+
mod.action(:after_load, accepted_by: :client)
|
|
108
|
+
|
|
109
|
+
mod.accessor(
|
|
110
|
+
:internal_entity_id,
|
|
111
|
+
getter: -> {
|
|
112
|
+
if store.respond_to?(:internal_entity_id)
|
|
113
|
+
store.internal_entity_id
|
|
114
|
+
else
|
|
115
|
+
(@interfacets_id ||= SecureRandom.uuid)
|
|
116
|
+
end
|
|
117
|
+
},
|
|
118
|
+
setter: ->(val) {
|
|
119
|
+
if store.respond_to?(:internal_entity_id=)
|
|
120
|
+
store.internal_entity_id = val
|
|
121
|
+
else
|
|
122
|
+
@interfacets_id = val
|
|
123
|
+
end
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
attr_reader :store, :parent
|
|
130
|
+
def initialize(store:, nesting:, parent:)
|
|
131
|
+
@store = store
|
|
132
|
+
@parent = parent
|
|
133
|
+
@nesting = nesting
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def record
|
|
137
|
+
store
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def ==(other)
|
|
141
|
+
if other.is_a?(Entity)
|
|
142
|
+
store == other.store
|
|
143
|
+
else
|
|
144
|
+
store == other
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def role
|
|
149
|
+
if parent.nil?
|
|
150
|
+
self.class.role
|
|
151
|
+
else
|
|
152
|
+
parent.role
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def uid
|
|
157
|
+
[self.class.name, internal_entity_id].join("__")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def entity_nesting
|
|
161
|
+
@entity_nesting ||= (parent&.entity_nesting || []) + [[@nesting, internal_entity_id]]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def association(name)
|
|
165
|
+
# inlined to avoid polluting API
|
|
166
|
+
@association_handlers ||= (
|
|
167
|
+
self
|
|
168
|
+
.class
|
|
169
|
+
.associations
|
|
170
|
+
.map { |name, spec| [name, spec.handler(self) ] }
|
|
171
|
+
.to_h
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
@association_handlers.fetch(name.to_s)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Shared
|
|
5
|
+
class EntityCollection
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
attr_reader :items
|
|
9
|
+
def initialize(items, &block)
|
|
10
|
+
@items = items || []
|
|
11
|
+
@wrap = block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def each(&)
|
|
15
|
+
@items.each do |item|
|
|
16
|
+
yield(@wrap.call(item))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_a
|
|
21
|
+
@items.map { @wrap.call(_1) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def size
|
|
25
|
+
@items.size
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def length
|
|
29
|
+
@items.length
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def empty?
|
|
33
|
+
@items.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def include?(item)
|
|
37
|
+
@items.include?(to_record(item))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def [](index)
|
|
41
|
+
item = @items[index]
|
|
42
|
+
item ? @wrap.call(item) : nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def first
|
|
46
|
+
item = @items.first
|
|
47
|
+
item ? @wrap.call(item) : nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def last
|
|
51
|
+
item = @items.last
|
|
52
|
+
item ? @wrap.call(item) : nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def <<(item_to_add)
|
|
56
|
+
@items << to_record(item_to_add)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def push(*items_to_add)
|
|
61
|
+
@items.push(*items_to_add.map { to_record(_1) })
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def delete(item)
|
|
66
|
+
@items.delete(to_record(item))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def delete_at(index)
|
|
70
|
+
@items.delete_at(index)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def original
|
|
74
|
+
@items
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def inspect
|
|
78
|
+
"#<#{self.class.name} decorating=#{@items.inspect} with=#{@entity_class}>"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def to_record(item)
|
|
84
|
+
item.is_a?(Entity) ? item.record : item
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
|
28
|
+
Client::System.current_bus.channel("interfacets:api").builder.submit(name)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
klass
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Handler
|
|
37
|
+
attr_reader :store, :spec
|
|
38
|
+
def initialize(store:, spec:)
|
|
39
|
+
@store = store
|
|
40
|
+
@spec = spec
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build
|
|
44
|
+
if spec.type == :reference
|
|
45
|
+
value = spec.klass.new
|
|
46
|
+
store.send("#{spec.name}=", value)
|
|
47
|
+
value
|
|
48
|
+
else
|
|
49
|
+
value = spec.klass.new
|
|
50
|
+
items = store.send(spec.name)
|
|
51
|
+
if items.nil?
|
|
52
|
+
store.send("#{spec.name}=", [value])
|
|
53
|
+
else
|
|
54
|
+
items << value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
value
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
def construct(...)
|
|
65
|
+
Constructor.call(...)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Spec
|
|
69
|
+
attr_reader(:name, :klass, :type)
|
|
70
|
+
def initialize(name:, klass:, type:)
|
|
71
|
+
@name = name
|
|
72
|
+
@klass = klass
|
|
73
|
+
@type = type
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def build
|
|
77
|
+
spec.klass.new
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def accessor(name)
|
|
82
|
+
name = name.to_s
|
|
83
|
+
|
|
84
|
+
define_method(name) do
|
|
85
|
+
@attributes[name]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
define_method("#{name}=") do |val|
|
|
89
|
+
@attributes[name] = val
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def association(name, klass:, type: :reference)
|
|
94
|
+
name = name.to_s
|
|
95
|
+
|
|
96
|
+
associations[name] = Spec.new(
|
|
97
|
+
name:,
|
|
98
|
+
klass:,
|
|
99
|
+
type:,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
define_method(name) do
|
|
103
|
+
@attributes[name]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
define_method("#{name}=") do |val|
|
|
107
|
+
@attributes[name] = val
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def collection(*args, **params)
|
|
112
|
+
association(*args, **params, type: :collection)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def associations
|
|
116
|
+
@associations ||= {}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
attr_accessor :attributes
|
|
121
|
+
def initialize
|
|
122
|
+
@attributes = {
|
|
123
|
+
"internal_entity_id" => SecureRandom.uuid
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def ==(other)
|
|
128
|
+
attributes == other.attributes
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def association(name)
|
|
132
|
+
@association_handlers ||= (
|
|
133
|
+
self
|
|
134
|
+
.class
|
|
135
|
+
.associations
|
|
136
|
+
.map { |name, spec| [name, Handler.new(store: self, spec:)] }
|
|
137
|
+
.to_h
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@association_handlers.fetch(name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
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(/^\s*$/) 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
|