hyper-model 1.0.alpha1.3 → 1.0.alpha1.4
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/.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
|