hyper-model 1.0.alpha1.5 → 1.0.alpha1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -38,6 +38,7 @@ module ReactiveRecord
38
38
  attr_accessor :aggregate_owner
39
39
  attr_accessor :aggregate_attribute
40
40
  attr_accessor :destroyed
41
+ attr_accessor :being_destroyed
41
42
  attr_accessor :updated_during
42
43
  attr_accessor :synced_attributes
43
44
  attr_accessor :virgin
@@ -310,7 +311,7 @@ module ReactiveRecord
310
311
  @saving = true
311
312
  end
312
313
 
313
- def errors!(hash, saving)
314
+ def errors!(hash, saving = false)
314
315
  @errors_at_last_sync = hash if saving
315
316
  notify_waiting_for_save
316
317
  errors.clear && return unless hash
@@ -323,7 +324,6 @@ module ReactiveRecord
323
324
  end
324
325
 
325
326
  def revert_errors!
326
- puts "#{inspect}.revert_errors! @errors_at_last_sync: #{@errors_at_last_sync}"
327
327
  errors!(@errors_at_last_sync)
328
328
  end
329
329
 
@@ -141,7 +141,10 @@ To determine this sync_scopes first asks if the record being changed is in the s
141
141
 
142
142
 
143
143
  =end
144
+ attr_accessor :broadcast_updated_at
145
+
144
146
  def sync_scopes(broadcast)
147
+ self.broadcast_updated_at = broadcast.updated_at
145
148
  # record_with_current_values will return nil if data between
146
149
  # the broadcast record and the value on the client is out of sync
147
150
  # not running set_pre_sync_related_records will cause sync scopes
@@ -159,6 +162,8 @@ To determine this sync_scopes first asks if the record being changed is in the s
159
162
  )
160
163
  record.backing_record.sync_unscoped_collection! if record.destroyed? || broadcast.new?
161
164
  end
165
+ ensure
166
+ self.broadcast_updated_at = nil
162
167
  end
163
168
 
164
169
  def apply_to_all_collections(method, record, dont_gather)
@@ -336,26 +341,26 @@ To determine this sync_scopes first asks if the record being changed is in the s
336
341
  end
337
342
 
338
343
  def observed
339
- return if @observing || ReactiveRecord::Base.data_loading?
344
+ return self if @observing || ReactiveRecord::Base.data_loading?
340
345
  begin
341
346
  @observing = true
342
347
  link_to_parent
343
348
  reload_from_db(true) if @out_of_date
344
349
  Hyperstack::Internal::State::Variable.get(self, :collection)
350
+ self
345
351
  ensure
346
352
  @observing = false
347
353
  end
348
354
  end
349
355
 
350
- def set_count_state(val)
356
+ def count_state=(val)
351
357
  unless ReactiveRecord::WhileLoading.observed?
352
358
  Hyperstack::Internal::State::Variable.set(self, :collection, collection, true)
353
359
  end
360
+ @count_updated_at = ReactiveRecord::Operations::Base.last_response_sent_at
354
361
  @count = val
355
362
  end
356
363
 
357
-
358
-
359
364
  def _count_internal(load_from_client)
360
365
  # when count is called on a leaf, count_internal is called for each
361
366
  # ancestor. Only the outermost count has load_from_client == true
@@ -462,16 +467,18 @@ To determine this sync_scopes first asks if the record being changed is in the s
462
467
  alias << push
463
468
 
464
469
  def _internal_push(item)
465
- item.itself # force get of at least the id
466
- if collection
467
- self.force_push item
468
- else
469
- unsaved_children << item
470
- update_child(item)
471
- @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association
472
- if !@count.nil?
473
- @count += item.destroyed? ? -1 : 1
474
- notify_of_change self
470
+ insure_sync do
471
+ item.itself # force get of at least the id
472
+ if collection
473
+ self.force_push item
474
+ else
475
+ unsaved_children << item
476
+ update_child(item)
477
+ @owner.backing_record.sync_has_many(@association.attribute) if @owner && @association
478
+ if !@count.nil?
479
+ @count += (item.destroyed? ? -1 : 1)
480
+ notify_of_change self
481
+ end
475
482
  end
476
483
  end
477
484
  self
@@ -557,9 +564,9 @@ To determine this sync_scopes first asks if the record being changed is in the s
557
564
  notify_of_change new_array
558
565
  end
559
566
 
560
- def delete(item)
561
- unsaved_children.delete(item)
562
- notify_of_change(
567
+ def destroy_non_habtm(item)
568
+ Hyperstack::Internal::State::Mapper.bulk_update do
569
+ unsaved_children.delete(item)
563
570
  if @owner && @association
564
571
  inverse_of = @association.inverse_of
565
572
  if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner && !@association.through_association?
@@ -569,17 +576,44 @@ To determine this sync_scopes first asks if the record being changed is in the s
569
576
  delete_internal(item) { @owner.backing_record.sync_has_many(@association.attribute) }
570
577
  else
571
578
  delete_internal(item)
572
- end
579
+ end.tap { Hyperstack::Internal::State::Variable.set(self, :collection, collection) }
580
+ end
581
+ end
582
+
583
+ def destroy(item)
584
+ return destroy_non_habtm(item) unless @association&.habtm?
585
+
586
+ ta = @association.through_association
587
+ item_foreign_key = @association.source_belongs_to_association.association_foreign_key
588
+ join_record = ta.klass.find_by(
589
+ ta.association_foreign_key => @owner.id,
590
+ item_foreign_key => item.id
573
591
  )
592
+ return destroy_non_habtm(item) if join_record.nil? ||
593
+ join_record.backing_record.being_destroyed
594
+
595
+ join_record&.destroy
596
+ end
597
+
598
+ def insure_sync
599
+ if Collection.broadcast_updated_at && @count_updated_at && Collection.broadcast_updated_at < @count_updated_at
600
+ reload_from_db
601
+ else
602
+ yield
603
+ end
574
604
  end
575
605
 
606
+ alias delete destroy
607
+
576
608
  def delete_internal(item)
577
- if collection
578
- all.delete(item)
579
- elsif !@count.nil?
580
- @count -= 1
609
+ insure_sync do
610
+ if collection
611
+ all.delete(item)
612
+ elsif !@count.nil?
613
+ @count -= 1
614
+ end
615
+ yield if block_given? # was yield item, but item is not used
581
616
  end
582
- yield if block_given? # was yield item, but item is not used
583
617
  item
584
618
  end
585
619
 
@@ -594,8 +628,10 @@ To determine this sync_scopes first asks if the record being changed is in the s
594
628
  r.backing_record.sync_attributes(attrs).set_ar_instance!
595
629
  end
596
630
 
597
- def find(id)
598
- find_by @target_klass.primary_key => id
631
+ def find(*args)
632
+ args = args[0] if args[0].is_a? Array
633
+ return args.collect { |id| find(id) } if args.count > 1
634
+ find_by(@target_klass.primary_key => args[0])
599
635
  end
600
636
 
601
637
  def _find_by_initializer(scope, attrs)
@@ -616,8 +652,22 @@ To determine this sync_scopes first asks if the record being changed is in the s
616
652
  count.zero?
617
653
  end
618
654
 
619
- def any?
620
- !count.zero?
655
+ def any?(*args, &block)
656
+ # If there are any args passed in, then the collection is being used in the condition
657
+ # and we must load it all into memory.
658
+ return all.any?(*args, &block) if args&.length&.positive? || block.present?
659
+
660
+ # Otherwise we can just check the count for efficiency
661
+ !empty?
662
+ end
663
+
664
+ def none?(*args, &block)
665
+ # If there are any args passed in, then the collection is being used in the condition
666
+ # and we must load it all into memory.
667
+ return all.none?(*args, &block) if args&.length&.positive? || block.present?
668
+
669
+ # Otherwise we can just check the count for efficiency
670
+ empty?
621
671
  end
622
672
 
623
673
  def method_missing(method, *args, &block)
@@ -1,4 +1,4 @@
1
- # add mehods to Object to determine if this is a dummy object or not
1
+ # add methods to Object to determine if this is a dummy object or not
2
2
  class Object
3
3
  def loaded?
4
4
  !loading?
@@ -7,10 +7,6 @@ class Object
7
7
  def loading?
8
8
  false
9
9
  end
10
-
11
- def present?
12
- !!self
13
- end
14
10
  end
15
11
 
16
12
  module ReactiveRecord
@@ -36,6 +32,12 @@ module ReactiveRecord
36
32
  @column_hash[:default] || nil
37
33
  end
38
34
 
35
+ def build_default_value_for_json
36
+ ::JSON.parse(@column_hash[:default]) if @column_hash[:default]
37
+ end
38
+
39
+ alias build_default_value_for_jsonb build_default_value_for_json
40
+
39
41
  def build_default_value_for_datetime
40
42
  if @column_hash[:default]
41
43
  ::Time.parse(@column_hash[:default].gsub(' ','T')+'+00:00')
@@ -55,18 +57,20 @@ module ReactiveRecord
55
57
  end
56
58
  end
57
59
 
60
+ FALSY_VALUES = [false, nil, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"]
61
+
58
62
  def build_default_value_for_boolean
59
- @column_hash[:default] || false
63
+ !FALSY_VALUES.include?(@column_hash[:default])
60
64
  end
61
65
 
62
66
  def build_default_value_for_float
63
- @column_hash[:default] || Float(0.0)
67
+ @column_hash[:default]&.to_f || Float(0.0)
64
68
  end
65
69
 
66
70
  alias build_default_value_for_decimal build_default_value_for_float
67
71
 
68
72
  def build_default_value_for_integer
69
- @column_hash[:default] || Integer(0)
73
+ @column_hash[:default]&.to_i || Integer(0)
70
74
  end
71
75
 
72
76
  alias build_default_value_for_bigint build_default_value_for_integer
@@ -92,10 +96,6 @@ module ReactiveRecord
92
96
  false
93
97
  end
94
98
 
95
- def present?
96
- false
97
- end
98
-
99
99
  def nil?
100
100
  true
101
101
  end
@@ -104,6 +104,11 @@ module ReactiveRecord
104
104
  true
105
105
  end
106
106
 
107
+ def class
108
+ notify
109
+ @object.class
110
+ end
111
+
107
112
  def method_missing(method, *args, &block)
108
113
  if method.start_with?("build_default_value_for_")
109
114
  nil
@@ -158,7 +163,13 @@ module ReactiveRecord
158
163
 
159
164
  alias inspect to_s
160
165
 
161
- `#{self}.$$proto.toString = Opal.Object.$$proto.toString`
166
+ %x{
167
+ if (Opal.Object.$$proto) {
168
+ #{self}.$$proto.toString = Opal.Object.$$proto.toString
169
+ } else {
170
+ #{self}.$$prototype.toString = Opal.Object.$$prototype.toString
171
+ }
172
+ }
162
173
 
163
174
  def to_f
164
175
  notify
@@ -216,10 +227,10 @@ module ReactiveRecord
216
227
  # to convert it to a string, for rendering
217
228
  # advantage over a try(:method) is, that it doesnt raise und thus is faster
218
229
  # which is important during render
219
- def respond_to?(method)
230
+ def respond_to?(method, all = false)
220
231
  return true if method == :acts_as_string?
221
232
  return true if %i[inspect to_date to_f to_i to_numeric to_number to_s to_time].include? method
222
- return @object.respond_to? if @object
233
+ return @object.respond_to?(method, all) if @object
223
234
  false
224
235
  end
225
236
 
@@ -37,7 +37,8 @@ module ReactiveRecord
37
37
 
38
38
  def get_server_method(attr, reload = nil)
39
39
  non_relationship_getter_common(attr, reload) do |has_key|
40
- sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key
40
+ # SPLAT BUG: was sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key
41
+ sync_ignore_dummy attr, Base.load_from_db(self, *(vector || [nil]), *attr), has_key
41
42
  end
42
43
  end
43
44
 
@@ -79,7 +80,8 @@ module ReactiveRecord
79
80
  if new?
80
81
  yield has_key if block
81
82
  elsif on_opal_client?
82
- sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key
83
+ # SPLAT BUG: was sync_ignore_dummy attr, Base.load_from_db(self, *(vector ? vector : [nil]), attr), has_key
84
+ sync_ignore_dummy attr, Base.load_from_db(self, *(vector || [nil]), *attr), has_key
83
85
  elsif id.present?
84
86
  sync_attribute attr, fetch_by_id(attr)
85
87
  else
@@ -457,21 +457,18 @@ module ReactiveRecord
457
457
  parent.send("#{association[:attribute]}=", aggregate)
458
458
  #puts "updated is frozen? #{aggregate.frozen?}, parent attributes = #{parent.send(association[:attribute]).attributes}"
459
459
  elsif parent.class.reflect_on_association(association[:attribute].to_sym).nil?
460
- raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?"
460
+ raise "Missing association :#{association[:attribute]} for #{parent.class.name}. Was association defined on opal side only?"
461
461
  elsif parent.class.reflect_on_association(association[:attribute].to_sym).collection?
462
462
  #puts ">>>>>>>>>> #{parent.class.name}.send('#{association[:attribute]}') << #{reactive_records[association[:child_id]]})"
463
463
  dont_save_list.delete(parent)
464
464
 
465
-
466
465
  # if reactive_records[association[:child_id]]&.new_record?
467
466
  # dont_save_list << reactive_records[association[:child_id]]
468
467
  # end
469
- #if false and parent.new?
470
- #parent.send("#{association[:attribute]}") << reactive_records[association[:child_id]]
471
- # puts "updated"
472
- #else
473
- #puts "skipped"
474
- #end
468
+
469
+ if parent.new_record?
470
+ parent.send("#{association[:attribute]}") << reactive_records[association[:child_id]]
471
+ end
475
472
  else
476
473
  #puts ">>>>ASSOCIATION>>>> #{parent.class.name}.send('#{association[:attribute]}=', #{reactive_records[association[:child_id]]})"
477
474
  parent.send("#{association[:attribute]}=", reactive_records[association[:child_id]])
@@ -496,7 +493,8 @@ module ReactiveRecord
496
493
  next record.persisted? if record.id && !record.changed?
497
494
  # if we get to here save the record and return true to keep it
498
495
  op = new_models.include?(record) ? :create_permitted? : :update_permitted?
499
- record.check_permission_with_acting_user(acting_user, op).save(validate: false) || true
496
+
497
+ record.check_permission_with_acting_user(acting_user, op).save(validate: validate) || true
500
498
  end
501
499
 
502
500
  # if called from ServerDataCache then save and validate are both false, and we just return the
@@ -551,13 +549,11 @@ module ReactiveRecord
551
549
  if RUBY_ENGINE == 'opal'
552
550
 
553
551
  def destroy(&block)
552
+ return if @destroyed || @being_destroyed
554
553
 
555
- return if @destroyed
556
-
557
- #destroy_associations
554
+ # destroy_associations
558
555
 
559
556
  promise = Promise.new
560
-
561
557
  if !data_loading? && (id || vector)
562
558
  Operations::Destroy.run(model: ar_instance.model_name.to_s, id: id, vector: vector)
563
559
  .then do |response|
@@ -569,7 +565,7 @@ module ReactiveRecord
569
565
  destroy_associations
570
566
  # sync_unscoped_collection! # ? should we do this here was NOT being done before hypermesh integration
571
567
  yield true, nil if block
572
- promise.resolve({success: true})
568
+ promise.resolve(success: true)
573
569
  end
574
570
 
575
571
  # DO NOT CLEAR ATTRIBUTES. Records that are not found, are destroyed, and if they are searched for again, we want to make
@@ -589,18 +585,16 @@ module ReactiveRecord
589
585
  def self.destroy_record(model, id, vector, acting_user)
590
586
  model = Object.const_get(model)
591
587
  record = if id
592
- model.find(id)
593
- else
594
- ServerDataCache.new(acting_user, {})[*vector]
595
- end
596
-
588
+ model.find(id)
589
+ else
590
+ ServerDataCache.new(acting_user, {})[*vector].value
591
+ end
597
592
 
598
593
  record.check_permission_with_acting_user(acting_user, :destroy_permitted?).destroy
599
- {success: true, attributes: {}}
600
-
594
+ { success: true, attributes: {} }
601
595
  rescue Exception => e
602
- #ReactiveRecord::Pry.rescued(e)
603
- {success: false, record: record, message: e}
596
+ # ReactiveRecord::Pry.rescued(e)
597
+ { success: false, record: record, message: e }
604
598
  end
605
599
  end
606
600
  end
@@ -12,6 +12,10 @@ module ReactiveRecord
12
12
 
13
13
  FORMAT = '0x%x'
14
14
 
15
+ class << self
16
+ attr_accessor :last_response_sent_at
17
+ end
18
+
15
19
  def self.serialize_params(hash)
16
20
  hash['associations'].each do |assoc|
17
21
  assoc['parent_id'] = FORMAT % assoc['parent_id']
@@ -38,6 +42,7 @@ module ReactiveRecord
38
42
  response[:saved_models].each do |saved_model|
39
43
  saved_model[0] = FORMAT % saved_model[0]
40
44
  end if response.is_a?(Hash) && response[:saved_models]
45
+ response[:sent_at] = Time.now.to_f
41
46
  response
42
47
  end
43
48
 
@@ -45,6 +50,7 @@ module ReactiveRecord
45
50
  response[:saved_models].each do |saved_model|
46
51
  saved_model[0] = saved_model[0].to_i(16)
47
52
  end if response.is_a?(Hash) && response[:saved_models]
53
+ Base.last_response_sent_at = response.delete(:sent_at)
48
54
  response
49
55
  end
50
56
  end
@@ -93,7 +99,7 @@ module ReactiveRecord
93
99
  class Destroy < Base
94
100
  param :acting_user, nils: true
95
101
  param :model
96
- param :id
102
+ param :id, nils: true
97
103
  param :vector
98
104
  step do
99
105
  ReactiveRecord::Base.destroy_record(
@@ -94,7 +94,7 @@ module ReactiveRecord
94
94
  end
95
95
 
96
96
  def set_attribute_change_status_and_notify(attr, changed, new_value)
97
- if @virgin
97
+ if @virgin || @being_destroyed
98
98
  @attributes[attr] = new_value
99
99
  else
100
100
  change_status_and_notify_helper(attr, changed) do |had_key, current_value|
@@ -108,14 +108,13 @@ module ReactiveRecord
108
108
  end
109
109
 
110
110
  def set_change_status_and_notify_only(attr, changed)
111
- return if @virgin
111
+ return if @virgin || @being_destroyed
112
112
  change_status_and_notify_helper(attr, changed) do
113
113
  Hyperstack::Internal::State::Variable.set(self, attr, nil) unless data_loading?
114
114
  end
115
115
  end
116
116
 
117
117
  def change_status_and_notify_helper(attr, changed)
118
- return if @being_destroyed
119
118
  empty_before = changed_attributes.empty?
120
119
  if !changed || data_loading?
121
120
  changed_attributes.delete(attr)