hyper-model 1.0.alpha1.3 → 1.0.alpha1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/Rakefile +17 -5
- data/hyper-model.gemspec +1 -1
- data/lib/active_record_base.rb +32 -15
- data/lib/hyper_model/version.rb +1 -1
- data/lib/reactive_record/active_record/base.rb +17 -0
- data/lib/reactive_record/active_record/class_methods.rb +22 -26
- data/lib/reactive_record/active_record/instance_methods.rb +3 -1
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +18 -1
- data/lib/reactive_record/active_record/reactive_record/base.rb +35 -20
- data/lib/reactive_record/active_record/reactive_record/collection.rb +70 -20
- data/lib/reactive_record/active_record/reactive_record/getters.rb +11 -2
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +18 -5
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -6
- data/lib/reactive_record/broadcast.rb +21 -8
- data/lib/reactive_record/permissions.rb +1 -1
- data/lib/reactive_record/server_data_cache.rb +16 -7
- metadata +10 -11
- data/Gemfile.lock +0 -440
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0583cd6276013eed8abad6c7e12454455c6915e9773eb195b51977c8d748217f'
|
4
|
+
data.tar.gz: a3e3f3e0aa5065281103566c6178bd2e4045a09863e1697d7587a5d2321f17dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d21d5c7def5a3217d00757c611042da17efcaf639eba5fd5c021f79ffaa7e9f98334186d51c5c5353188c56b528286b91cc3d48ed4e8ecadf73cdae40cae04e
|
7
|
+
data.tar.gz: 5aeaf368b37b2f4224ec0d7d3af83f44708f9093e97f7261d49d65e78af6ac0e5078c055d4fb974b4c2041491911087175e12e5988fc6873bec17ed388dee8ff
|
data/.gitignore
CHANGED
@@ -11,7 +11,6 @@ spec/test_app/tmp/
|
|
11
11
|
spec/test_app/db/test.sqlite3
|
12
12
|
spec/test_app/log/test.log
|
13
13
|
spec/test_app/log/development.log
|
14
|
-
spec/test_app/Gemfile.lock
|
15
14
|
/synchromesh-simple-poller-store
|
16
15
|
/synchromesh-pusher-channel-store
|
17
16
|
/examples/action-cable/rails_cache_dir/
|
@@ -34,3 +33,7 @@ public/assets/*
|
|
34
33
|
# ingore Idea
|
35
34
|
.idea
|
36
35
|
.vscode
|
36
|
+
|
37
|
+
# ignore Gemfile.locks https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
38
|
+
/spec/test_app/Gemfile.lock
|
39
|
+
/Gemfile.lock
|
data/Rakefile
CHANGED
@@ -1,20 +1,33 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
+
def run_batches(batches)
|
5
|
+
failed = false
|
6
|
+
batches.each do |batch|
|
7
|
+
begin
|
8
|
+
Rake::Task["spec:batch#{batch}"].invoke
|
9
|
+
rescue SystemExit
|
10
|
+
failed = true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
exit 1 if failed
|
14
|
+
end
|
15
|
+
|
16
|
+
|
4
17
|
task :part1 do
|
5
|
-
(1..2)
|
18
|
+
run_batches(1..2)
|
6
19
|
end
|
7
20
|
|
8
21
|
task :part2 do
|
9
|
-
(3..4)
|
22
|
+
run_batches(3..4)
|
10
23
|
end
|
11
24
|
|
12
25
|
task :part3 do
|
13
|
-
(5..7)
|
26
|
+
run_batches(5..7)
|
14
27
|
end
|
15
28
|
|
16
29
|
task :spec do
|
17
|
-
(1..7)
|
30
|
+
run_batches(1..7)
|
18
31
|
end
|
19
32
|
|
20
33
|
namespace :spec do
|
@@ -23,7 +36,6 @@ namespace :spec do
|
|
23
36
|
end
|
24
37
|
(1..7).each do |batch|
|
25
38
|
RSpec::Core::RakeTask.new(:"batch#{batch}") do |t|
|
26
|
-
t.fail_on_error = false unless batch == 7
|
27
39
|
t.pattern = "spec/batch#{batch}/**/*_spec.rb"
|
28
40
|
end
|
29
41
|
end
|
data/hyper-model.gemspec
CHANGED
@@ -63,7 +63,7 @@ Gem::Specification.new do |spec|
|
|
63
63
|
spec.add_development_dependency 'shoulda'
|
64
64
|
spec.add_development_dependency 'shoulda-matchers'
|
65
65
|
spec.add_development_dependency 'spring-commands-rspec'
|
66
|
-
spec.add_development_dependency 'sqlite3'
|
66
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6'
|
67
67
|
spec.add_development_dependency 'timecop', '~> 0.8.1'
|
68
68
|
spec.add_development_dependency 'unparser'
|
69
69
|
end
|
data/lib/active_record_base.rb
CHANGED
@@ -87,7 +87,7 @@ module ActiveRecord
|
|
87
87
|
this.acting_user = acting_user
|
88
88
|
# returns a PsuedoRelationArray which will respond to the
|
89
89
|
# __secure_collection_check method
|
90
|
-
ReactiveRecordPsuedoRelationArray.new([this.instance_exec(*args, &block)])
|
90
|
+
ReactiveRecordPsuedoRelationArray.new([*this.instance_exec(*args, &block)])
|
91
91
|
ensure
|
92
92
|
this.acting_user = old
|
93
93
|
end
|
@@ -255,20 +255,6 @@ module ActiveRecord
|
|
255
255
|
pre_syncromesh_has_many name, *args, opts.except(:regulate), &block
|
256
256
|
end
|
257
257
|
|
258
|
-
# add secure access for find, find_by, and belongs_to and has_one relations.
|
259
|
-
# No explicit security checks are needed here, as the data returned by these objects
|
260
|
-
# will be further processedand checked before returning. I.e. it is not possible to
|
261
|
-
# simply return `find(1)` but if you try returning `find(1).name` the permission system
|
262
|
-
# will check to see if the name attribute can be legally sent to the current acting user.
|
263
|
-
|
264
|
-
def __secure_remote_access_to_find(_self, _acting_user, *args)
|
265
|
-
find(*args)
|
266
|
-
end
|
267
|
-
|
268
|
-
def __secure_remote_access_to_find_by(_self, _acting_user, *args)
|
269
|
-
find_by(*args)
|
270
|
-
end
|
271
|
-
|
272
258
|
%i[belongs_to has_one].each do |macro|
|
273
259
|
alias_method :"pre_syncromesh_#{macro}", macro
|
274
260
|
define_method(macro) do |name, *aargs, &block|
|
@@ -329,6 +315,37 @@ module ActiveRecord
|
|
329
315
|
%i[limit offset].each do |scope|
|
330
316
|
regulate_scope(scope) {}
|
331
317
|
end
|
318
|
+
|
319
|
+
finder_method :__hyperstack_internal_scoped_last do
|
320
|
+
last
|
321
|
+
end
|
322
|
+
|
323
|
+
scope :__hyperstack_internal_scoped_last_n, ->(n) { last(n) }
|
324
|
+
|
325
|
+
# implements find_by inside of scopes. For security reasons we return nil
|
326
|
+
# if we cannot view at least the id of found record. Otherwise a hacker
|
327
|
+
# could tell if a record exists depending on whether an access violation
|
328
|
+
# (i.e. it exists) or nil (it doesn't exist is returned.) Note that
|
329
|
+
# view of id is permitted as long as any attribute of the record is
|
330
|
+
# accessible.
|
331
|
+
finder_method :__hyperstack_internal_scoped_find_by do |attrs|
|
332
|
+
begin
|
333
|
+
found = find_by(attrs)
|
334
|
+
found && found.check_permission_with_acting_user(acting_user, :view_permitted?, :id)
|
335
|
+
rescue Hyperstack::AccessViolation => e
|
336
|
+
message = []
|
337
|
+
message << Pastel.new.red("\n\nHYPERSTACK Access violation during find_by operation.")
|
338
|
+
message << Pastel.new.red("Access to the found record's id is not permitted. nil will be returned")
|
339
|
+
message << " #{self.name}.find_by("
|
340
|
+
message << attrs.collect do |attr, value|
|
341
|
+
" #{attr}: '#{value.inspect.truncate(120, separator: '...')}'"
|
342
|
+
end.join(",\n")
|
343
|
+
message << " )"
|
344
|
+
message << "\n#{e.details}\n"
|
345
|
+
Hyperstack.on_error('find_by', self, attrs, message.join("\n"))
|
346
|
+
nil
|
347
|
+
end
|
348
|
+
end
|
332
349
|
end
|
333
350
|
end
|
334
351
|
|
data/lib/hyper_model/version.rb
CHANGED
@@ -6,5 +6,22 @@ module ActiveRecord
|
|
6
6
|
|
7
7
|
scope :limit, ->() {}
|
8
8
|
scope :offset, ->() {}
|
9
|
+
|
10
|
+
finder_method :__hyperstack_internal_scoped_last
|
11
|
+
scope :__hyperstack_internal_scoped_last_n, ->(n) { last(n) }
|
12
|
+
|
13
|
+
ReactiveRecord::ScopeDescription.new(
|
14
|
+
self, :___hyperstack_internal_scoped_find_by,
|
15
|
+
client: ->(attrs) { !attrs.detect { |attr, value| attributes[attr] != value } }
|
16
|
+
)
|
17
|
+
|
18
|
+
def self.__hyperstack_internal_scoped_find_by(attrs)
|
19
|
+
collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs)
|
20
|
+
if !collection.collection
|
21
|
+
collection._find_by_initializer(self, attrs)
|
22
|
+
else
|
23
|
+
collection.first
|
24
|
+
end
|
25
|
+
end
|
9
26
|
end
|
10
27
|
end
|
@@ -51,14 +51,24 @@ module ActiveRecord
|
|
51
51
|
@model_name ||= ActiveModel::Name.new(self)
|
52
52
|
end
|
53
53
|
|
54
|
+
def __hyperstack_preprocess_attrs(attrs)
|
55
|
+
if inheritance_column && self < base_class && !attrs.key?(inheritance_column)
|
56
|
+
attrs = attrs.merge(inheritance_column => model_name.to_s)
|
57
|
+
end
|
58
|
+
dealiased_attrs = {}
|
59
|
+
attrs.each { |attr, value| dealiased_attrs[_dealias_attribute(attr)] = value }
|
60
|
+
end
|
61
|
+
|
54
62
|
def find(id)
|
55
|
-
|
63
|
+
find_by(primary_key => id)
|
56
64
|
end
|
57
65
|
|
58
|
-
def find_by(
|
59
|
-
|
60
|
-
|
61
|
-
|
66
|
+
def find_by(attrs = {})
|
67
|
+
attrs = __hyperstack_preprocess_attrs(attrs)
|
68
|
+
# r = ReactiveRecord::Base.find_locally(self, attrs, new_only: true)
|
69
|
+
# return r.ar_instance if r
|
70
|
+
(r = __hyperstack_internal_scoped_find_by(attrs)) || return
|
71
|
+
r.backing_record.sync_attributes(attrs).set_ar_instance!
|
62
72
|
end
|
63
73
|
|
64
74
|
def enum(*args)
|
@@ -176,7 +186,7 @@ module ActiveRecord
|
|
176
186
|
|
177
187
|
def method_missing(name, *args, &block)
|
178
188
|
if args.count == 1 && name.start_with?("find_by_") && !block
|
179
|
-
find_by(
|
189
|
+
find_by(name.sub(/^find_by_/, '') => args[0])
|
180
190
|
elsif [].respond_to?(name)
|
181
191
|
all.send(name, *args, &block)
|
182
192
|
elsif name.end_with?('!')
|
@@ -193,16 +203,6 @@ module ActiveRecord
|
|
193
203
|
# Any method ending with ! just means apply the method after forcing a reload
|
194
204
|
# from the DB.
|
195
205
|
|
196
|
-
# alias pre_synchromesh_method_missing method_missing
|
197
|
-
#
|
198
|
-
# def method_missing(name, *args, &block)
|
199
|
-
# return all.send(name, *args, &block) if [].respond_to?(name)
|
200
|
-
# if name.end_with?('!')
|
201
|
-
# return send(name.chop, *args, &block).send(:reload_from_db) rescue nil
|
202
|
-
# end
|
203
|
-
# pre_synchromesh_method_missing(name, *args, &block)
|
204
|
-
# end
|
205
|
-
|
206
206
|
def create(*args, &block)
|
207
207
|
new(*args).save(&block)
|
208
208
|
end
|
@@ -213,9 +213,6 @@ module ActiveRecord
|
|
213
213
|
singleton_class.send(:define_method, name) do |*vargs|
|
214
214
|
all.build_child_scope(scope_description, *name, *vargs)
|
215
215
|
end
|
216
|
-
# singleton_class.send(:define_method, "#{name}=") do |_collection|
|
217
|
-
# raise 'NO LONGER IMPLEMENTED - DOESNT PLAY WELL WITH SYNCHROMESH'
|
218
|
-
# end
|
219
216
|
end
|
220
217
|
|
221
218
|
def default_scope(*args, &block)
|
@@ -249,10 +246,6 @@ module ActiveRecord
|
|
249
246
|
end
|
250
247
|
end
|
251
248
|
|
252
|
-
# def all=(_collection)
|
253
|
-
# raise "NO LONGER IMPLEMENTED DOESNT PLAY WELL WITH SYNCHROMESH"
|
254
|
-
# end
|
255
|
-
|
256
249
|
def unscoped
|
257
250
|
ReactiveRecord::Base.unscoped[self] ||=
|
258
251
|
ReactiveRecord::Collection
|
@@ -261,10 +254,11 @@ module ActiveRecord
|
|
261
254
|
end
|
262
255
|
|
263
256
|
def finder_method(name)
|
264
|
-
ReactiveRecord::ScopeDescription.new(self, "_#{name}", {})
|
257
|
+
ReactiveRecord::ScopeDescription.new(self, "_#{name}", {})
|
265
258
|
[name, "#{name}!"].each do |method|
|
266
259
|
singleton_class.send(:define_method, method) do |*vargs|
|
267
|
-
all.apply_scope("_#{method}", *vargs)
|
260
|
+
collection = all.apply_scope("_#{method}", *vargs)
|
261
|
+
collection.first
|
268
262
|
end
|
269
263
|
end
|
270
264
|
end
|
@@ -370,7 +364,9 @@ module ActiveRecord
|
|
370
364
|
# TODO: changed values as changes while just updating the synced values.
|
371
365
|
target =
|
372
366
|
if param[primary_key]
|
373
|
-
find(param[primary_key])
|
367
|
+
ReactiveRecord::Base.find(self, primary_key => param[primary_key]).tap do |r|
|
368
|
+
r.backing_record.loaded_id = param[primary_key]
|
369
|
+
end
|
374
370
|
else
|
375
371
|
new
|
376
372
|
end
|
@@ -144,10 +144,12 @@ module ActiveRecord
|
|
144
144
|
@backing_record.destroyed
|
145
145
|
end
|
146
146
|
|
147
|
-
def
|
147
|
+
def new_record?
|
148
148
|
@backing_record.new?
|
149
149
|
end
|
150
150
|
|
151
|
+
alias new? new_record?
|
152
|
+
|
151
153
|
def errors
|
152
154
|
Hyperstack::Internal::State::Variable.get(@backing_record, @backing_record)
|
153
155
|
@backing_record.errors
|
@@ -26,11 +26,28 @@ module ReactiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def loading_details
|
29
|
-
"[loading #{
|
29
|
+
"[loading #{pretty_vector}]"
|
30
30
|
end
|
31
31
|
|
32
32
|
def dirty_details
|
33
33
|
"[changed id: #{id} #{changes}]"
|
34
34
|
end
|
35
|
+
|
36
|
+
def pretty_vector
|
37
|
+
v = []
|
38
|
+
i = 0
|
39
|
+
while i < vector.length
|
40
|
+
if vector[i] == 'all' && vector[i + 1].is_a?(Array) &&
|
41
|
+
vector[i + 1][0] == '___hyperstack_internal_scoped_find_by' &&
|
42
|
+
vector[i + 2] == '*0'
|
43
|
+
v << ['find_by', vector[i + 1][1]]
|
44
|
+
i += 3
|
45
|
+
else
|
46
|
+
v << vector[i]
|
47
|
+
i += 1
|
48
|
+
end
|
49
|
+
end
|
50
|
+
v
|
51
|
+
end
|
35
52
|
end
|
36
53
|
end
|
@@ -67,30 +67,35 @@ module ReactiveRecord
|
|
67
67
|
load_data { ServerDataCache.load_from_json(json, target) }
|
68
68
|
end
|
69
69
|
|
70
|
+
def self.find_locally(model, attrs, new_only: nil)
|
71
|
+
if (id_to_find = attrs[model.primary_key])
|
72
|
+
!new_only && lookup_by_id(model, id_to_find)
|
73
|
+
else
|
74
|
+
@records[model].detect do |r|
|
75
|
+
(r.new? || !new_only) &&
|
76
|
+
!attrs.detect { |attr, value| r.synced_attributes[attr] != value }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.find_by_id(model, id)
|
82
|
+
find(model, model.primary_key => id)
|
83
|
+
end
|
84
|
+
|
70
85
|
def self.find(model, attrs)
|
71
86
|
# will return the unique record with this attribute-value pair
|
72
87
|
# value cannot be an association or aggregation
|
73
88
|
|
74
89
|
# add the inheritance column if this is an STI subclass
|
75
90
|
|
76
|
-
|
77
|
-
if inher_col && model < model.base_class && !attrs.key?(inher_col)
|
78
|
-
attrs = attrs.merge(inher_col => model.model_name.to_s)
|
79
|
-
end
|
91
|
+
attrs = model.__hyperstack_preprocess_attrs(attrs)
|
80
92
|
|
81
93
|
model = model.base_class
|
82
94
|
primary_key = model.primary_key
|
83
95
|
|
84
96
|
# already have a record with these attribute-value pairs?
|
85
97
|
|
86
|
-
record =
|
87
|
-
if (id_to_find = attrs[primary_key])
|
88
|
-
lookup_by_id(model, id_to_find)
|
89
|
-
else
|
90
|
-
@records[model].detect do |r|
|
91
|
-
!attrs.detect { |attr, value| r.synced_attributes[attr] != value }
|
92
|
-
end
|
93
|
-
end
|
98
|
+
record = find_locally(model, attrs)
|
94
99
|
|
95
100
|
unless record
|
96
101
|
# if not, and then the record may be loaded, but not have this attribute set yet,
|
@@ -102,10 +107,8 @@ module ReactiveRecord
|
|
102
107
|
attrs = attrs.merge primary_key => id
|
103
108
|
end
|
104
109
|
# if we don't have a record then create one
|
105
|
-
|
106
|
-
record
|
107
|
-
# and set the values
|
108
|
-
attrs.each { |attr, value| record.sync_attribute(attr, value) }
|
110
|
+
record ||= set_vector_lookup(new(model), [model, *find_by_vector(attrs)])
|
111
|
+
record.sync_attributes(attrs)
|
109
112
|
end
|
110
113
|
# finally initialize and return the ar_instance
|
111
114
|
record.set_ar_instance!
|
@@ -122,7 +125,6 @@ module ReactiveRecord
|
|
122
125
|
# record = @records[model].detect { |record| record.vector == vector }
|
123
126
|
record = lookup_by_vector(vector)
|
124
127
|
unless record
|
125
|
-
|
126
128
|
record = new model
|
127
129
|
set_vector_lookup(record, vector)
|
128
130
|
end
|
@@ -175,6 +177,7 @@ module ReactiveRecord
|
|
175
177
|
@ar_instance.instance_variable_set(:@backing_record, existing_record)
|
176
178
|
existing_record.attributes.merge!(attributes) { |key, v1, v2| v1 }
|
177
179
|
end
|
180
|
+
@id = value
|
178
181
|
value
|
179
182
|
end
|
180
183
|
|
@@ -211,7 +214,7 @@ module ReactiveRecord
|
|
211
214
|
|
212
215
|
def initialize_collections
|
213
216
|
if (!vector || vector.empty?) && id && id != ''
|
214
|
-
Base.set_vector_lookup(self, [@model,
|
217
|
+
Base.set_vector_lookup(self, [@model, *find_by_vector(@model.primary_key => id)])
|
215
218
|
end
|
216
219
|
Base.load_data do
|
217
220
|
@model.reflect_on_all_associations.each do |assoc|
|
@@ -251,8 +254,12 @@ module ReactiveRecord
|
|
251
254
|
@synced_with_unscoped = !@synced_with_unscoped
|
252
255
|
end
|
253
256
|
|
254
|
-
def
|
257
|
+
def sync_attributes(attrs)
|
258
|
+
attrs.each { |attr, value| sync_attribute(attr, value) }
|
259
|
+
self
|
260
|
+
end
|
255
261
|
|
262
|
+
def sync_attribute(attribute, value)
|
256
263
|
@synced_attributes[attribute] = @attributes[attribute] = value
|
257
264
|
Base.set_id_lookup(self) if attribute == primary_key
|
258
265
|
|
@@ -260,7 +267,7 @@ module ReactiveRecord
|
|
260
267
|
|
261
268
|
if value.is_a? Collection
|
262
269
|
@synced_attributes[attribute] = value.dup_for_sync
|
263
|
-
elsif aggregation = model.reflect_on_aggregation(attribute)
|
270
|
+
elsif (aggregation = model.reflect_on_aggregation(attribute)) && (aggregation.klass < ActiveRecord::Base)
|
264
271
|
value.backing_record.sync!
|
265
272
|
elsif aggregation
|
266
273
|
@synced_attributes[attribute] = aggregation.deserialize(aggregation.serialize(value))
|
@@ -278,6 +285,14 @@ module ReactiveRecord
|
|
278
285
|
Base.lookup_by_id(model, id)
|
279
286
|
end
|
280
287
|
|
288
|
+
def id_loaded?
|
289
|
+
@id
|
290
|
+
end
|
291
|
+
|
292
|
+
def loaded_id=(id)
|
293
|
+
@id = id
|
294
|
+
end
|
295
|
+
|
281
296
|
def revert
|
282
297
|
@changed_attributes.dup.each do |attribute|
|
283
298
|
@ar_instance.send("#{attribute}=", @synced_attributes[attribute])
|
@@ -29,7 +29,7 @@ module ReactiveRecord
|
|
29
29
|
@owner = owner # can be nil if this is an outer most scope
|
30
30
|
@association = association
|
31
31
|
@target_klass = target_klass
|
32
|
-
if owner
|
32
|
+
if owner && !owner.id && vector.length <= 1
|
33
33
|
@collection = []
|
34
34
|
elsif vector.length > 0
|
35
35
|
@vector = vector
|
@@ -59,10 +59,12 @@ module ReactiveRecord
|
|
59
59
|
@collection = []
|
60
60
|
if ids = ReactiveRecord::Base.fetch_from_db([*@vector, "*all"])
|
61
61
|
ids.each do |id|
|
62
|
-
@collection <<
|
62
|
+
@collection << ReactiveRecord::Base.find_by_id(@target_klass, id)
|
63
63
|
end
|
64
64
|
else
|
65
65
|
@dummy_collection = ReactiveRecord::Base.load_from_db(nil, *@vector, "*all")
|
66
|
+
# this calls back to all now that the collection is initialized,
|
67
|
+
# so it has the side effect of creating a dummy value in collection[0]
|
66
68
|
@dummy_record = self[0]
|
67
69
|
end
|
68
70
|
end
|
@@ -103,7 +105,7 @@ module ReactiveRecord
|
|
103
105
|
end
|
104
106
|
# todo move following to a separate module related to scope updates ******************
|
105
107
|
attr_reader :vector
|
106
|
-
|
108
|
+
attr_accessor :scope_description
|
107
109
|
attr_writer :parent
|
108
110
|
attr_reader :pre_sync_related_records
|
109
111
|
|
@@ -222,7 +224,6 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
222
224
|
end
|
223
225
|
|
224
226
|
def filter_records(related_records)
|
225
|
-
# possibly we should never get here???
|
226
227
|
scope_args = @vector.last.is_a?(Array) ? @vector.last[1..-1] : []
|
227
228
|
@scope_description.filter_records(related_records, scope_args)
|
228
229
|
end
|
@@ -231,16 +232,22 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
231
232
|
@live_scopes ||= Set.new
|
232
233
|
end
|
233
234
|
|
235
|
+
def in_this_collection(related_records)
|
236
|
+
return related_records unless @association
|
237
|
+
related_records.select do |r|
|
238
|
+
r.backing_record.attributes[@association.inverse_of] == @owner
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
234
242
|
def set_pre_sync_related_records(related_records, _record = nil)
|
235
|
-
|
236
|
-
@pre_sync_related_records = related_records #in_this_collection related_records <- not sure if this works
|
243
|
+
@pre_sync_related_records = in_this_collection(related_records)
|
237
244
|
live_scopes.each { |scope| scope.set_pre_sync_related_records(@pre_sync_related_records) }
|
238
245
|
end
|
239
246
|
|
240
247
|
# NOTE sync_scopes is overridden in scope_description.rb
|
241
248
|
def sync_scopes(related_records, record, filtering = true)
|
242
249
|
#related_records = related_records.intersection([*@collection])
|
243
|
-
|
250
|
+
related_records = in_this_collection(related_records) if filtering
|
244
251
|
live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) }
|
245
252
|
notify_of_change unless related_records.empty?
|
246
253
|
ensure
|
@@ -301,7 +308,7 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
301
308
|
@collection = []
|
302
309
|
elsif filter?
|
303
310
|
# puts "#{self}.sync_collection_with_parent (@parent = #{@parent}) calling filter records on (#{@parent.collection})"
|
304
|
-
@collection = filter_records(@parent.collection)
|
311
|
+
@collection = filter_records(@parent.collection).to_a
|
305
312
|
end
|
306
313
|
elsif !@linked && @parent._count_internal(false).zero?
|
307
314
|
# don't check _count_internal if already linked as this cause an unnecessary rendering cycle
|
@@ -391,7 +398,7 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
391
398
|
# child.test_model = 1
|
392
399
|
# so... we go back starting at this collection and look for the first
|
393
400
|
# collection with an owner... that is our guy
|
394
|
-
child = proxy_association.klass
|
401
|
+
child = ReactiveRecord::Base.find_by_id(proxy_association.klass, id)
|
395
402
|
push child
|
396
403
|
set_belongs_to child
|
397
404
|
end
|
@@ -463,13 +470,29 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
463
470
|
notify_of_change self
|
464
471
|
end
|
465
472
|
|
466
|
-
[:first, :last].each do |method|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
+
# [:first, :last].each do |method|
|
474
|
+
# define_method method do |*args|
|
475
|
+
# if args.count == 0
|
476
|
+
# all.send(method)
|
477
|
+
# else
|
478
|
+
# apply_scope(method, *args)
|
479
|
+
# end
|
480
|
+
# end
|
481
|
+
# end
|
482
|
+
|
483
|
+
def first(n = nil)
|
484
|
+
if n
|
485
|
+
apply_scope(:first, n)
|
486
|
+
else
|
487
|
+
self[0]
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
def last(n = nil)
|
492
|
+
if n
|
493
|
+
apply_scope(:__hyperstack_internal_scoped_last_n, n)
|
494
|
+
else
|
495
|
+
__hyperstack_internal_scoped_last
|
473
496
|
end
|
474
497
|
end
|
475
498
|
|
@@ -539,19 +562,47 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
539
562
|
@dummy_collection.loading?
|
540
563
|
end
|
541
564
|
|
565
|
+
def loaded?
|
566
|
+
false && @collection && (!@dummy_collection || !@dummy_collection.loading?) && (!@owner || @owner.id || @vector.length > 1)
|
567
|
+
end
|
568
|
+
|
542
569
|
def empty?
|
543
570
|
# should be handled by method missing below, but opal-rspec does not deal well
|
544
571
|
# with method missing, so to test...
|
545
572
|
all.empty?
|
546
573
|
end
|
547
574
|
|
575
|
+
def find_by(attrs)
|
576
|
+
attrs = @target_klass.__hyperstack_preprocess_attrs(attrs)
|
577
|
+
# r = @collection&.detect { |lr| lr.new_record? && !attrs.detect { |k, v| lr.attributes[k] != v } }
|
578
|
+
# return r if r
|
579
|
+
(r = __hyperstack_internal_scoped_find_by(attrs)) || return
|
580
|
+
r.backing_record.sync_attributes(attrs).set_ar_instance!
|
581
|
+
end
|
582
|
+
|
583
|
+
def find(id)
|
584
|
+
find_by @target_klass.primary_key => id
|
585
|
+
end
|
586
|
+
|
587
|
+
def _find_by_initializer(scope, attrs)
|
588
|
+
found =
|
589
|
+
if scope.is_a? Collection
|
590
|
+
scope.parent.collection&.detect { |lr| !attrs.detect { |k, v| lr.attributes[k] != v } }
|
591
|
+
else
|
592
|
+
ReactiveRecord::Base.find_locally(@target_klass, attrs)&.ar_instance
|
593
|
+
end
|
594
|
+
return first unless found
|
595
|
+
@collection = [found]
|
596
|
+
found
|
597
|
+
end
|
598
|
+
|
548
599
|
def method_missing(method, *args, &block)
|
549
|
-
if
|
600
|
+
if args.count == 1 && method.start_with?('find_by_')
|
601
|
+
find_by(method.sub(/^find_by_/, '') => args[0])
|
602
|
+
elsif [].respond_to? method
|
550
603
|
all.send(method, *args, &block)
|
551
604
|
elsif ScopeDescription.find(@target_klass, method)
|
552
605
|
apply_scope(method, *args)
|
553
|
-
elsif args.count == 1 && method.start_with?('find_by_')
|
554
|
-
apply_scope(:find_by, method.sub(/^find_by_/, '') => args.first)
|
555
606
|
elsif @target_klass.respond_to?(method) && ScopeDescription.find(@target_klass, "_#{method}")
|
556
607
|
apply_scope("_#{method}", *args).first
|
557
608
|
else
|
@@ -577,7 +628,6 @@ To determine this sync_scopes first asks if the record being changed is in the s
|
|
577
628
|
Hyperstack::Internal::State::Variable.set(self, "collection", collection) unless ReactiveRecord::Base.data_loading?
|
578
629
|
value
|
579
630
|
end
|
580
|
-
|
581
631
|
end
|
582
632
|
|
583
633
|
end
|