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

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.
@@ -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)