isomorfeus-data 1.0.0.delta11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
1
+ module LucidArray
2
+ module Mixin
3
+ def self.included(base)
4
+ if RUBY_ENGINE != 'opal'
5
+ Isomorfeus.add_valid_array_class(base) unless base == LucidArray::Base
6
+ end
7
+
8
+ base.extend(Isomorfeus::Data::PropDeclaration)
9
+
10
+ def to_gid
11
+ [@class_name, @props_json]
12
+ end
13
+
14
+ base.instance_exec do
15
+ def on_load_block
16
+ @on_load_block
17
+ end
18
+
19
+ def query_block
20
+ @query_block
21
+ end
22
+ end
23
+
24
+ if RUBY_ENGINE == 'opal'
25
+ def initialize(store_path: nil, validated_props: nil)
26
+ @props = validated_props
27
+ @props_json = @props.to_json if @props
28
+ @class_name = self.class.name
29
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
30
+ @store_path = store_path ? store_path : [:data_state, :arrays, @class_name, @props_json]
31
+ end
32
+
33
+ def loaded?
34
+ Redux.fetch_by_path(*@store_path) ? true : false
35
+ end
36
+
37
+ def items
38
+ Redux.register_used_store_path(*@store_path)
39
+ raw_array = Redux.fetch_by_path(*@store_path)
40
+ raw_array ? raw_array : []
41
+ end
42
+
43
+ def method_missing(method_name, *args, &block)
44
+ Redux.register_used_store_path(*@store_path)
45
+ raw_array = Redux.fetch_by_path(*@store_path)
46
+ data_array = raw_array ? raw_array : []
47
+ data_array.send(method_name, *args, &block)
48
+ end
49
+
50
+ def to_transport(inline: false)
51
+ Redux.register_used_store_path(*@store_path)
52
+ raw_array = Redux.fetch_by_path(*@store_path)
53
+ if inline
54
+ { '_inline' => { @props_json => (raw_array ? raw_array : []) }}
55
+ else
56
+ { 'arrays' => { @class_name => { @props_json => (raw_array ? raw_array : []) }}}
57
+ end
58
+ end
59
+
60
+ base.instance_exec do
61
+ def load(props_hash = {})
62
+ validate_props(props_hash)
63
+ instance = self.new(validated_props: Isomorfeus::Data::Props.new(props_hash))
64
+ self.promise_load(props_hash, instance) unless instance.loaded?
65
+ instance
66
+ end
67
+
68
+ def on_load(&block)
69
+ end
70
+
71
+ def promise_load(props_hash = {}, instance = nil)
72
+ unless instance
73
+ validate_props(props_hash)
74
+ instance = self.new(validated_props: Isomorfeus::Data::Props.new(props_hash))
75
+ end
76
+
77
+ props_json = instance.instance_variable_get(:@props_json)
78
+
79
+ Redux.register_used_store_path(:data_state, :arrays, self.name, props_json)
80
+
81
+ Isomorfeus::Transport.promise_send_path('Isomorfeus::Data::Handler::ArrayLoadHandler', self.name, props_json).then do |response|
82
+ if response[:agent_response].key?(:error)
83
+ `console.error(#{response[:agent_response][:error].to_n})`
84
+ raise response[:agent_response][:error]
85
+ end
86
+ Isomorfeus.store.dispatch(type: 'DATA_LOAD', data: response[:full_response][:data])
87
+ instance
88
+ end
89
+ end
90
+
91
+ def query
92
+ nil
93
+ end
94
+ end
95
+ else # RUBY_ENGINE
96
+ unless base == LucidArray::Base
97
+ base.prop :pub_sub_client, default: nil
98
+ base.prop :session_id, default: nil
99
+ base.prop :current_user, default: nil
100
+ end
101
+
102
+ def initialize(store_path: nil, validated_props: nil)
103
+ @props = validated_props
104
+ @props_json = @props.to_json if @props
105
+ @loaded = false
106
+ @class_name = self.class.name
107
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
108
+ end
109
+
110
+ def loaded?
111
+ @loaded
112
+ end
113
+
114
+ def [](name)
115
+ @data_array[name]
116
+ end
117
+
118
+ def items
119
+ @data_array
120
+ end
121
+
122
+ def method_missing(method_name, *args, &block)
123
+ @data_array.send(method_name, *args, &block)
124
+ end
125
+
126
+ def to_transport(inline: false)
127
+ if inline
128
+ { '_inline' => { @props_json => @data_array }}
129
+ else
130
+ { 'arrays' => { @class_name => { @props_json => @data_array }}}
131
+ end
132
+ end
133
+
134
+ base.instance_exec do
135
+ def load(props_hash = {})
136
+ validate_props(props_hash)
137
+ instance = self.new(validated_props: Isomorfeus::Data::Props.new(props_hash))
138
+ instance.instance_exec do
139
+ @data_array = self.class.query_block.call(props_hash)
140
+ @loaded = true
141
+ end
142
+ instance
143
+ end
144
+
145
+ def on_load(&block)
146
+ @on_load_block = block
147
+ end
148
+
149
+ def promise_load(props_hash = {}, instance = nil)
150
+ instance = self.load(props_hash)
151
+ result_promise = Promise.new
152
+ result_promise.resolve(instance)
153
+ result_promise
154
+ end
155
+
156
+ def query(&block)
157
+ @query_block = block
158
+ end
159
+ end
160
+ end # RUBY_ENGINE
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,15 @@
1
+ module LucidCollection
2
+ class Base
3
+ include LucidCollection::Mixin
4
+
5
+ if RUBY_ENGINE != 'opal'
6
+ def self.inherited(base)
7
+ Isomorfeus.add_valid_collection_class(base)
8
+
9
+ base.prop :pub_sub_client, default: nil
10
+ base.prop :session_id, default: nil
11
+ base.prop :current_user, default: nil
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,241 @@
1
+ module LucidCollection
2
+ module Mixin
3
+ def self.included(base)
4
+ if RUBY_ENGINE != 'opal'
5
+ Isomorfeus.add_valid_collection_class(base) unless base == LucidCollection::Base
6
+ end
7
+
8
+ base.extend(Isomorfeus::Data::PropDeclaration)
9
+
10
+ def find_node(attribute_hash = nil, &block)
11
+ if block_given?
12
+ nodes.each do |node|
13
+ return node if block.call(node)
14
+ end
15
+ else
16
+ node_class = attribute_hash.delete(:class)
17
+ is_a_module = attribute_hash.delete(:is_a)
18
+ nodes.each do |node|
19
+ if node_class
20
+ next unless node.class == node_class
21
+ end
22
+ if is_a_module
23
+ next unless node.is_a?(is_a_module)
24
+ end
25
+ found = true
26
+ attribute_hash.each do |k,v|
27
+ found &&= (node[k] == v)
28
+ break unless found
29
+ end
30
+ return node if found
31
+ end
32
+ end
33
+ nil
34
+ end
35
+
36
+ def find_nodes(attribute_hash = nil, &block)
37
+ found_nodes = Set.new
38
+ if block_given?
39
+ nodes.each do |node|
40
+ found_nodes << node if block.call(node)
41
+ end
42
+ else
43
+ node_class = attribute_hash.delete(:class)
44
+ is_a_module = attribute_hash.delete(:is_a)
45
+ nodes.each do |node|
46
+ if node_class
47
+ next unless node.class == node_class
48
+ end
49
+ if is_a_module
50
+ next unless node.is_a?(is_a_module)
51
+ end
52
+ found = true
53
+ attribute_hash.each do |k,v|
54
+ found &&= (node[k] == v)
55
+ break unless found
56
+ end
57
+ found_nodes << node if found
58
+ end
59
+ end
60
+ found_nodes
61
+ end
62
+
63
+ def to_gid
64
+ [@class_name, @props_json]
65
+ end
66
+
67
+ def to_transport(inline: false)
68
+ if inline
69
+ { '_inline' => { @props_json => nodes_as_cids }}
70
+ else
71
+ { 'collections' => { @class_name => { @props_json => nodes_as_cids }}}
72
+ end
73
+ end
74
+
75
+ def included_items_to_transport
76
+ nodes_hash = {}
77
+ nodes.each do |node|
78
+ nodes_hash.deep_merge!(node.to_transport)
79
+ end
80
+ nodes_hash
81
+ end
82
+
83
+ base.instance_exec do
84
+ def on_load_block
85
+ @on_load_block
86
+ end
87
+
88
+ def query_block
89
+ @query_block
90
+ end
91
+ end
92
+
93
+ if RUBY_ENGINE == 'opal'
94
+ def initialize(store_path: nil, validated_props: nil)
95
+ @props = validated_props
96
+ @props_json = @props.to_json if @props
97
+ @store_path = store_path
98
+ @class_name = self.class.name
99
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
100
+ @store_path = store_path ? store_path : [:data_state, :collections, @class_name, @props_json]
101
+ end
102
+
103
+ def loaded?
104
+ Redux.fetch_by_path(*@store_path) ? true : false
105
+ end
106
+
107
+ def find_node_by_id(node_id)
108
+ Redux.register_used_store_path(*@store_path)
109
+ nodes_as_cids.each do |node_cid|
110
+ return LucidNode::Base.node_from_cid(node_cid) if node_cid[1] == node_id
111
+ end
112
+ nil
113
+ end
114
+
115
+ def nodes
116
+ # maybe use a node cache, maybe not:
117
+ # - pro node cache: maybe faster
118
+ # - contra node cache: js garbage collection fails because references are kept forever, memory usage just grows and grows
119
+ nodes_as_cids.map { |node_cid| LucidNode::Base.node_from_cid(node_cid) }
120
+ end
121
+
122
+ def nodes_as_cids
123
+ Redux.register_used_store_path(*@store_path)
124
+ node_cids = Redux.fetch_by_path(*@store_path)
125
+ node_cids ? node_cids : []
126
+ end
127
+
128
+ def method_missing(method_name, *args, &block)
129
+ if method_name.JS.startsWith('find_node_by_')
130
+ attribute = method_name[13..-1] # remove 'find_node_by_'
131
+ value = args[0]
132
+ attribute_hash = { attribute => value }
133
+ attribute_hash.merge!(args[1]) if args[1]
134
+ find_node(attribute_hash)
135
+ else
136
+ super
137
+ end
138
+ end
139
+
140
+ base.instance_exec do
141
+ def load(props_hash = {})
142
+ validate_props(props_hash)
143
+ instance = self.new(validated_props: Isomorfeus::Data::Props.new(props_hash))
144
+ self.promise_load(props_hash, instance) unless instance.loaded?
145
+ instance
146
+ end
147
+
148
+ def on_load(&block)
149
+ end
150
+
151
+ def promise_load(props_hash = {}, instance = nil)
152
+ unless instance
153
+ validate_props(props_hash)
154
+ instance = self.new(validated_props: Isomorfeus::Data::Props.new(props_hash))
155
+ end
156
+
157
+ props_json = instance.instance_variable_get(:@props_json)
158
+
159
+ Redux.register_used_store_path(:data_state, :collections, self.name, props_json)
160
+
161
+ Isomorfeus::Transport.promise_send_path('Isomorfeus::Data::Handler::CollectionLoadHandler', self.name, props_json).then do |response|
162
+ if response[:agent_response].key?(:error)
163
+ `console.error(#{response[:agent_response][:error].to_n})`
164
+ raise response[:agent_response][:error]
165
+ end
166
+ Isomorfeus.store.dispatch(type: 'DATA_LOAD', data: response[:full_response][:data])
167
+ instance
168
+ end
169
+ end
170
+
171
+ def query
172
+ nil
173
+ end
174
+ end
175
+ else # RUBY_ENGINE
176
+ unless base == LucidCollection::Base
177
+ base.prop :pub_sub_client, default: nil
178
+ base.prop :session_id, default: nil
179
+ base.prop :current_user, default: nil
180
+ end
181
+
182
+ def initialize(store_path: nil, validated_props: nil)
183
+ @props = validated_props
184
+ @props_json = @props.to_json if @props
185
+ @loaded = false
186
+ @class_name = self.class.name
187
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
188
+ end
189
+
190
+ def loaded?
191
+ @loaded
192
+ end
193
+
194
+ def method_missing(method_name, *args, &block)
195
+ if method_name.start_with?('find_node_by_')
196
+ attribute = method_name[13..-1] # remove 'find_node_by_'
197
+ value = args[0]
198
+ attribute_hash = { attribute => value }
199
+ attribute_hash.merge!(args[1]) if args[1]
200
+ find_node(attribute_hash)
201
+ else
202
+ super
203
+ end
204
+ end
205
+
206
+ def nodes_as_cids
207
+ nodes.map { |node| node.to_cid }
208
+ end
209
+
210
+ base.instance_exec do
211
+ attr_reader :nodes
212
+
213
+ def load(props_hash = {})
214
+ validate_props(props_hash)
215
+ instance = self.new(validated_props: Isomorfeus::Data::Props.new(props_hash))
216
+ instance.instance_exec do
217
+ @nodes = self.class.query_block.call(props_hash)
218
+ @loaded = true
219
+ end
220
+ instance
221
+ end
222
+
223
+ def on_load(&block)
224
+ @on_load_block = block
225
+ end
226
+
227
+ def promise_load(props_hash = {}, instance = nil)
228
+ instance = self.load(props_hash)
229
+ result_promise = Promise.new
230
+ result_promise.resolve(instance)
231
+ result_promise
232
+ end
233
+
234
+ def query(&block)
235
+ @query_block = block
236
+ end
237
+ end
238
+ end # RUBY_ENGINE
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,5 @@
1
+ module LucidEdge
2
+ class Base
3
+ include LucidEdge::Mixin
4
+ end
5
+ end
@@ -0,0 +1,243 @@
1
+ module LucidEdge
2
+ module Mixin
3
+ def self.included(base)
4
+ attr_reader :id
5
+
6
+ def ==(other_node)
7
+ eql?(other_node)
8
+ end
9
+
10
+ def eql?(other_node)
11
+ @id == other_node.id && @class_name == other_node.instance_variable_get(:@class_name)
12
+ end
13
+
14
+ def id=(new_id)
15
+ new_id = new_id.to_s
16
+ changed_attributes[:id] = new_id
17
+ @id = new_id
18
+ end
19
+
20
+ def changed_attributes
21
+ @changed_attributes ||= Isomorfeus::Data::Props.new({})
22
+ end
23
+
24
+ def changed?
25
+ changed_attributes.any?
26
+ end
27
+
28
+ def to=(node)
29
+ @changed_to_cid = node.to_cid
30
+ node
31
+ end
32
+
33
+ def to_cid
34
+ [@class_name, @id]
35
+ end
36
+
37
+ def valid_attribute?(attr, value)
38
+ begin
39
+ validate_attribute!(attr, value)
40
+ true
41
+ rescue
42
+ false
43
+ end
44
+ end
45
+
46
+ def validate_attribute!(attr, value)
47
+ attr_options = self.class.attribute_options[attr]
48
+
49
+ if attr_options.key?(:class)
50
+ raise "#{attr}: value class is not #{attr_options[:class]}!" unless value.class == attr_options[:class]
51
+ end
52
+
53
+ if attr_options.key?(:is_a)
54
+ raise "#{attr}: value is not a #{attr_options[:class]}!" unless value.is_a?(attr_options[:is_a])
55
+ end
56
+
57
+ if attr_options.key?(:validate)
58
+ raise "#{attr}: value failed validation!" unless attr_options[:validate].call(value)
59
+ end
60
+ end
61
+
62
+ base.instance_exec do
63
+ def attributes
64
+ attribute_options.keys
65
+ end
66
+
67
+ def attribute_options
68
+ @attribute_options ||= { id: {} }
69
+ end
70
+
71
+ def edge_from_cid(cid)
72
+ Isomorfeus.cached_edge_class(cid[0]).new({id: cid[1]})
73
+ end
74
+ end
75
+
76
+ if RUBY_ENGINE == 'opal'
77
+ def initialize(attributes_hash = nil)
78
+ attributes_hash = {} unless attributes_hash
79
+ self.class.attributes.each do |attr|
80
+ next if attr == :to || attr == :from
81
+ if attributes_hash.key?(attr)
82
+ validate_attribute!(attr, attributes_hash[attr])
83
+ changed_attributes[attr] = attributes_hash[attr]
84
+ elsif self.class.attribute_options[attr].key?(:default)
85
+ changed_attributes[attr] = self.class.attribute_options[attr][:default]
86
+ end
87
+ end
88
+ @id = attributes_hash[:id].to_s
89
+ @id = "new_#{object_id}" if @id.empty?
90
+ @changed_from_cid = attributes_hash[:from]&.to_cid
91
+ @changed_to_cid = attributes_hash[:to]&.to_cid
92
+ @class_name = self.class.name
93
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
94
+ end
95
+
96
+ def loaded?
97
+ Redux.fetch_by_path(:data_state, :edges, @class_name, @id) ? true : false
98
+ end
99
+
100
+ def from
101
+ cid = from_as_cid
102
+ cid ? LucidNode::Base.node_from_cid(cid) : nil
103
+ end
104
+
105
+ def from_as_cid
106
+ Redux.register_used_store_path(:data_state, :edges, @class_name, @id, :from)
107
+ return @changed_from_cid if @changed_from_cid
108
+ cid = Redux.fetch_by_path(:data_state, :edges, @class_name, @id, :from)
109
+ cid ? cid : nil
110
+ end
111
+
112
+ def from=(node)
113
+ @changed_from_cid = node.to_cid
114
+ node
115
+ end
116
+
117
+ def to
118
+ cid = to_as_cid
119
+ cid ? LucidNode::Base.node_from_cid(cid) : nil
120
+ end
121
+
122
+ def to_as_cid
123
+ Redux.register_used_store_path(:data_state, :edges, @class_name, @id, :to)
124
+ return @changed_to_cid if @changed_to_cid
125
+ cid = Redux.fetch_by_path(:data_state, :edges, @class_name, @id, :to)
126
+ cid ? cid : nil
127
+ end
128
+
129
+ def to_transport(*args)
130
+ Redux.register_used_store_path(:data_state, :edges, @class_name, @id)
131
+ final_attributes = {}
132
+ self.class.attributes.each do |attr|
133
+ next if attr == :id
134
+ final_attributes[attr] = send(attr)
135
+ end
136
+ { 'edges' => { @class_name => { @id => { from: from_as_cid, to: to_as_cid, attributes: final_attributes }}}}
137
+ end
138
+
139
+ base.instance_exec do
140
+ def attribute(name, options = {})
141
+ attribute_options[name] = options
142
+
143
+ define_method(name) do
144
+ Redux.register_used_store_path(:data_state, :edges, @class_name, @id, :attributes, name)
145
+ if changed_attributes.key?(name)
146
+ changed_attributes[name]
147
+ else
148
+ Redux.fetch_by_path(:data_state, :edges, @class_name, @id, :attributes, name)
149
+ end
150
+ end
151
+
152
+ define_method("#{name}=") do |arg|
153
+ validate_attribute!(name, arg)
154
+ Redux.register_used_store_path(:data_state, :edges, @class_name, @id, :attributes, name)
155
+ changed_attributes.set(name, arg)
156
+ end
157
+ end
158
+ end
159
+ else # RUBY_ENGINE
160
+ def initialize(attributes_hash = nil)
161
+ attributes_hash = {} unless attributes_hash
162
+ given_attributes = Isomorfeus::Data::Props.new(attributes_hash)
163
+ valid_attributes_hash = {}
164
+ self.class.attributes.each do |attr|
165
+ next if attr == :to || attr == :from
166
+ if given_attributes.key?(attr)
167
+ validate_attribute!(attr, given_attributes[attr])
168
+ valid_attributes_hash[attr] = given_attributes[attr]
169
+ elsif self.class.attribute_options[attr].key?(:default)
170
+ valid_attributes_hash[attr] = self.class.attribute_options[attr][:default]
171
+ end
172
+ end
173
+ @attributes = Isomorfeus::Data::Props.new(valid_attributes_hash)
174
+ @id = @attributes[:id].to_s
175
+ @id = "new_#{object_id}" if @id.empty?
176
+ @from_cid = given_attributes[:from]&.to_cid
177
+ @changed_from_cid = nil
178
+ @to_cid = given_attributes[:to]&.to_cid
179
+ @changed_to_cid = nil
180
+ @class_name = self.class.name
181
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
182
+ end
183
+
184
+ def loaded?
185
+ true
186
+ end
187
+
188
+ def from
189
+ from_cid = from_as_cid
190
+ from_cid ? LucidNode::Base.node_from_cid(from_cid) : nil
191
+ end
192
+
193
+ def from_as_cid
194
+ @changed_from_cid ? @changed_from_cid : @from_cid
195
+ end
196
+
197
+ def from=(node)
198
+ @changed_from_cid = node.to_cid
199
+ node
200
+ end
201
+
202
+ def to
203
+ to_cid = to_as_cid
204
+ to_cid ? LucidNode::Base.node_from_cid(to_cid) : nil
205
+ end
206
+
207
+ def to_as_cid
208
+ @changed_to_cid ? @changed_to_cid : @to_cid
209
+ end
210
+
211
+ def to_transport(*args)
212
+ final_attributes = {}
213
+ self.class.attributes.each do |attr|
214
+ next if attr == :id
215
+ include_attribute = @attributes.key?(attr)
216
+ include_attribute = !self.class.attribute_options[attr][:server_only] if self.class.attribute_options[attr].key?(:server_only)
217
+ final_attributes[attr.to_s] = @attributes[attr] if include_attribute
218
+ end
219
+ { 'edges' => { @class_name => { @id => { 'from' => from_as_cid, 'to' => to_as_cid, 'attributes' => final_attributes }}}}
220
+ end
221
+
222
+ base.instance_exec do
223
+ def attribute(name, options = {})
224
+ attribute_options[name] = options
225
+
226
+ define_method(name) do
227
+ if changed_attributes.key?(name)
228
+ changed_attributes[name]
229
+ else
230
+ @attributes[name]
231
+ end
232
+ end
233
+
234
+ define_method("#{name}=") do |arg|
235
+ validate_attribute!(name, arg)
236
+ changed_attributes.set(name, arg)
237
+ end
238
+ end
239
+ end
240
+ end # RUBY_ENGINE
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,15 @@
1
+ module LucidGraph
2
+ class Base
3
+ include LucidGraph::Mixin
4
+
5
+ if RUBY_ENGINE != 'opal'
6
+ def self.inherited(base)
7
+ Isomorfeus.add_valid_graph_class(base)
8
+
9
+ base.prop :pub_sub_client, default: nil
10
+ base.prop :session_id, default: nil
11
+ base.prop :current_user, default: nil
12
+ end
13
+ end
14
+ end
15
+ end