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

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,11 +4,11 @@ module ReactiveRecord
4
4
  # the appropriate string. The order of execution is important!
5
5
  module BackingRecordInspector
6
6
  def inspection_details
7
- return error_details unless errors.empty?
8
- return new_details if new?
7
+ return error_details unless errors.empty?
8
+ return new_details if new?
9
9
  return destroyed_details if destroyed
10
- return loading_details unless @attributes.key? primary_key
11
- return dirty_details unless changed_attributes.empty?
10
+ return loading_details unless @attributes.key? primary_key
11
+ return dirty_details unless changed_attributes.empty?
12
12
  "[loaded id: #{id}]"
13
13
  end
14
14
 
@@ -71,7 +71,7 @@ module ReactiveRecord
71
71
  if (id_to_find = attrs[model.primary_key])
72
72
  !new_only && lookup_by_id(model, id_to_find)
73
73
  else
74
- @records[model].detect do |r|
74
+ @records[model.base_class].detect do |r|
75
75
  (r.new? || !new_only) &&
76
76
  !attrs.detect { |attr, value| r.synced_attributes[attr] != value }
77
77
  end
@@ -118,6 +118,9 @@ module ReactiveRecord
118
118
  # this is the equivilent of find but for associations and aggregations
119
119
  # because we are not fetching a specific attribute yet, there is NO communication with the
120
120
  # server. That only happens during find.
121
+
122
+ return DummyPolymorph.new(vector) unless model
123
+
121
124
  model = model.base_class
122
125
 
123
126
  # do we already have a record with this vector? If so return it, otherwise make a new one.
@@ -147,7 +150,7 @@ module ReactiveRecord
147
150
  @attributes = {}
148
151
  @changed_attributes = []
149
152
  @virgin = true
150
- records[model] << self
153
+ records[model.base_class] << self
151
154
  Base.set_object_id_lookup(self)
152
155
  end
153
156
 
@@ -204,7 +207,7 @@ module ReactiveRecord
204
207
  end
205
208
 
206
209
  def errors
207
- @errors ||= ActiveModel::Errors.new(self)
210
+ @errors ||= ActiveModel::Errors.new(ar_instance)
208
211
  end
209
212
 
210
213
  # called when we have a newly created record, to initialize
@@ -250,7 +253,7 @@ module ReactiveRecord
250
253
  return if @create_sync
251
254
  @create_sync = true
252
255
  end
253
- model.unscoped << ar_instance
256
+ model.unscoped._internal_push ar_instance
254
257
  @synced_with_unscoped = !@synced_with_unscoped
255
258
  end
256
259
 
@@ -307,9 +310,11 @@ module ReactiveRecord
307
310
  @saving = true
308
311
  end
309
312
 
310
- def errors!(hash)
313
+ def errors!(hash, saving)
314
+ @errors_at_last_sync = hash if saving
311
315
  notify_waiting_for_save
312
316
  errors.clear && return unless hash
317
+ errors.non_reactive_clear
313
318
  hash.each do |attribute, messages|
314
319
  messages.each do |message|
315
320
  errors.add(attribute, message)
@@ -317,6 +322,11 @@ module ReactiveRecord
317
322
  end
318
323
  end
319
324
 
325
+ def revert_errors!
326
+ puts "#{inspect}.revert_errors! @errors_at_last_sync: #{@errors_at_last_sync}"
327
+ errors!(@errors_at_last_sync)
328
+ end
329
+
320
330
  def saved!(save_only = nil) # sets saving to false AND notifies
321
331
  notify_waiting_for_save
322
332
  return self if save_only
@@ -418,6 +428,7 @@ module ReactiveRecord
418
428
 
419
429
  def destroy_associations
420
430
  @destroyed = false
431
+ @being_destroyed = true
421
432
  model.reflect_on_all_associations.each do |association|
422
433
  if association.collection?
423
434
  @attributes[association.attribute].replace([]) if @attributes[association.attribute]
@@ -77,6 +77,8 @@ module ReactiveRecord
77
77
  (@collection.length..index).each do |i|
78
78
  new_dummy_record = ReactiveRecord::Base.new_from_vector(@target_klass, nil, *@vector, "*#{i}")
79
79
  new_dummy_record.attributes[@association.inverse_of] = @owner if @association && !@association.through_association?
80
+ # HMT-TODO: the above needs to be looked into... if we are a hmt then don't we need to create a dummy on the joins collection as well?
81
+ # or maybe this just does not work for HMT?
80
82
  @collection << new_dummy_record
81
83
  end
82
84
  end
@@ -212,7 +214,7 @@ To determine this sync_scopes first asks if the record being changed is in the s
212
214
  return [] unless attrs[@association.inverse_of] == @owner
213
215
  if !@association.through_association
214
216
  [record]
215
- elsif (source = attrs[@association.source])
217
+ elsif (source = attrs[@association.source]) && source.is_a?(@target_klass)
216
218
  [source]
217
219
  else
218
220
  []
@@ -233,7 +235,8 @@ To determine this sync_scopes first asks if the record being changed is in the s
233
235
  end
234
236
 
235
237
  def in_this_collection(related_records)
236
- return related_records unless @association
238
+ # HMT-TODO: I don't think we can get a set of related records here with a through association unless they are part of the collection
239
+ return related_records if !@association || @association.through_association?
237
240
  related_records.select do |r|
238
241
  r.backing_record.attributes[@association.inverse_of] == @owner
239
242
  end
@@ -406,7 +409,11 @@ To determine this sync_scopes first asks if the record being changed is in the s
406
409
  def set_belongs_to(child)
407
410
  if @owner
408
411
  # TODO this is major broken...current
409
- child.send("#{@association.inverse_of}=", @owner) if @association && !@association.through_association
412
+ if (through_association = @association.through_association)
413
+ # HMT-TODO: create a new record with owner and child
414
+ else
415
+ child.send("#{@association.inverse_of}=", @owner) if @association && !@association.through_association
416
+ end
410
417
  elsif @parent
411
418
  @parent.set_belongs_to(child)
412
419
  end
@@ -421,19 +428,40 @@ To determine this sync_scopes first asks if the record being changed is in the s
421
428
 
422
429
  def update_child(item)
423
430
  backing_record = item.backing_record
424
- if backing_record && @owner && @association && !@association.through_association? && item.attributes[@association.inverse_of] != @owner
431
+ # HMT TODO: The following && !association.through_association was commented out, causing wrong class items to be added to
432
+ # associations
433
+ # Why was it commented out.
434
+ if backing_record && @owner && @association && item.attributes[@association.inverse_of] != @owner && !@association.through_association?
425
435
  inverse_of = @association.inverse_of
426
- current_association = item.attributes[inverse_of]
436
+ current_association_value = item.attributes[inverse_of]
427
437
  backing_record.virgin = false unless backing_record.data_loading?
438
+ # next line was commented out and following line was active.
428
439
  backing_record.update_belongs_to(inverse_of, @owner)
429
- if current_association && current_association.attributes[@association.attribute]
430
- current_association.attributes[@association.attribute].delete(item)
431
- end
440
+ #backing_record.set_belongs_to_via_has_many(@association, @owner)
441
+ # following is handled by update_belongs_to and is redundant
442
+ # unless current_association_value.nil? # might be a dummy value which responds to nil
443
+ # current_association = @association.inverse.inverse(current_association_value)
444
+ # current_association_attribute = current_association.attribute
445
+ # if current_association.collection? && current_association_value.attributes[current_association_attribute]
446
+ # current_association.attributes[current_association_attribute].delete(item)
447
+ # end
448
+ # end
432
449
  @owner.backing_record.sync_has_many(@association.attribute)
433
450
  end
434
451
  end
435
452
 
436
453
  def push(item)
454
+ if (through_association = @association&.through_association)
455
+ through_association.klass.create(@association.inverse_of => @owner, @association.source => item)
456
+ self
457
+ else
458
+ _internal_push(item)
459
+ end
460
+ end
461
+
462
+ alias << push
463
+
464
+ def _internal_push(item)
437
465
  item.itself # force get of at least the id
438
466
  if collection
439
467
  self.force_push item
@@ -449,8 +477,6 @@ To determine this sync_scopes first asks if the record being changed is in the s
449
477
  self
450
478
  end
451
479
 
452
- alias << push
453
-
454
480
  def sort!(*args, &block)
455
481
  replace(sort(*args, &block))
456
482
  end
@@ -514,19 +540,19 @@ To determine this sync_scopes first asks if the record being changed is in the s
514
540
  @dummy_collection.notify
515
541
  array = new_array.is_a?(Collection) ? new_array.collection : new_array
516
542
  @collection.each_with_index do |r, i|
517
- r.id = new_array[i].id if array[i] and array[i].id and !r.new? and r.backing_record.vector.last =~ /^\*[0-9]+$/
543
+ r.id = new_array[i].id if array[i] and array[i].id and !r.new_record? and r.backing_record.vector.last =~ /^\*[0-9]+$/
518
544
  end
519
545
  end
520
-
521
- @collection.dup.each { |item| delete(item) } if @collection # this line is a big nop I think
546
+ # the following makes sure that the existing elements are properly removed from the collection
547
+ @collection.dup.each { |item| delete(item) } if @collection
522
548
  @collection = []
523
549
  if new_array.is_a? Collection
524
550
  @dummy_collection = new_array.dummy_collection
525
551
  @dummy_record = new_array.dummy_record
526
- new_array.collection.each { |item| self << item } if new_array.collection
552
+ new_array.collection.each { |item| _internal_push item } if new_array.collection
527
553
  else
528
554
  @dummy_collection = @dummy_record = nil
529
- new_array.each { |item| self << item }
555
+ new_array.each { |item| _internal_push item }
530
556
  end
531
557
  notify_of_change new_array
532
558
  end
@@ -534,9 +560,9 @@ To determine this sync_scopes first asks if the record being changed is in the s
534
560
  def delete(item)
535
561
  unsaved_children.delete(item)
536
562
  notify_of_change(
537
- if @owner && @association && !@association.through_association?
563
+ if @owner && @association
538
564
  inverse_of = @association.inverse_of
539
- if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner
565
+ if (backing_record = item.backing_record) && item.attributes[inverse_of] == @owner && !@association.through_association?
540
566
  # the if prevents double update if delete is being called from << (see << above)
541
567
  backing_record.update_belongs_to(inverse_of, nil)
542
568
  end
@@ -562,20 +588,8 @@ To determine this sync_scopes first asks if the record being changed is in the s
562
588
  @dummy_collection.loading?
563
589
  end
564
590
 
565
- def loaded?
566
- false && @collection && (!@dummy_collection || !@dummy_collection.loading?) && (!@owner || @owner.id || @vector.length > 1)
567
- end
568
-
569
- def empty?
570
- # should be handled by method missing below, but opal-rspec does not deal well
571
- # with method missing, so to test...
572
- all.empty?
573
- end
574
-
575
591
  def find_by(attrs)
576
592
  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
593
  (r = __hyperstack_internal_scoped_find_by(attrs)) || return
580
594
  r.backing_record.sync_attributes(attrs).set_ar_instance!
581
595
  end
@@ -596,6 +610,16 @@ To determine this sync_scopes first asks if the record being changed is in the s
596
610
  found
597
611
  end
598
612
 
613
+ # to avoid fetching the entire collection array we check empty and any against the count
614
+
615
+ def empty?
616
+ count.zero?
617
+ end
618
+
619
+ def any?
620
+ !count.zero?
621
+ end
622
+
599
623
  def method_missing(method, *args, &block)
600
624
  if args.count == 1 && method.start_with?('find_by_')
601
625
  find_by(method.sub(/^find_by_/, '') => args[0])
@@ -0,0 +1,22 @@
1
+ module ReactiveRecord
2
+ class DummyPolymorph
3
+ def initialize(vector)
4
+ @vector = vector
5
+ puts "VECTOR: #{@vector.inspect}"
6
+ Base.load_from_db(nil, *vector, 'id')
7
+ Base.load_from_db(nil, *vector, 'model_name')
8
+ end
9
+
10
+ def nil?
11
+ true
12
+ end
13
+
14
+ def method_missing(*)
15
+ self
16
+ end
17
+
18
+ def self.reflect_on_all_associations(*)
19
+ []
20
+ end
21
+ end
22
+ end
@@ -72,6 +72,7 @@ module ReactiveRecord
72
72
  alias build_default_value_for_bigint build_default_value_for_integer
73
73
 
74
74
  def build_default_value_for_string
75
+ return @column_hash[:default] if @column_hash[:serialized?]
75
76
  @column_hash[:default] || ''
76
77
  end
77
78
 
@@ -4,9 +4,13 @@ module ReactiveRecord
4
4
  module Getters
5
5
  def get_belongs_to(assoc, reload = nil)
6
6
  getter_common(assoc.attribute, reload) do |has_key, attr|
7
- return if new?
8
- value = fetch_by_id(attr, @model.primary_key) if id.present?
9
- value = find_association(assoc, value)
7
+ next if new?
8
+ if id.present?
9
+ value = fetch_by_id(attr, @model.primary_key)
10
+ klass = fetch_by_id(attr, 'model_name')
11
+ klass &&= Object.const_get(klass)
12
+ end
13
+ value = find_association(assoc, value, klass)
10
14
  sync_ignore_dummy attr, value, has_key
11
15
  end&.cast_to_current_sti_type
12
16
  end
@@ -104,18 +108,26 @@ module ReactiveRecord
104
108
  else
105
109
  virtual_fetch_on_server_warning(attribute) if on_opal_server? && changed?
106
110
  yield false, attribute
107
- end.tap { |value| Hyperstack::Internal::State::Variable.get(self, attribute) unless data_loading? }
111
+ end.tap { Hyperstack::Internal::State::Variable.get(self, attribute) unless data_loading? }
108
112
  end
109
113
 
110
- def find_association(association, id)
111
- inverse_of = association.inverse_of
114
+ def find_association(association, id, klass)
112
115
  instance = if id
113
- find(association.klass, association.klass.primary_key => id)
116
+ find(klass, klass.primary_key => id)
117
+ elsif association.polymorphic?
118
+ new_from_vector(nil, nil, *vector, association.attribute)
114
119
  else
115
120
  new_from_vector(association.klass, nil, *vector, association.attribute)
116
121
  end
122
+ return instance if instance.is_a? DummyPolymorph
123
+ inverse_of = association.inverse_of(instance)
117
124
  instance_backing_record_attributes = instance.attributes
118
125
  inverse_association = association.klass.reflect_on_association(inverse_of)
126
+ # HMT-TODO: don't we need to do something with the through association case.
127
+ # perhaps we never hit this point...
128
+ if association.through_association?
129
+ IsomorphicHelpers.log "*********** called #{ar_instance}.find_association(#{association.attribute}) which is has many through!!!!!!!", :error
130
+ end
119
131
  if inverse_association.collection?
120
132
  instance_backing_record_attributes[inverse_of] = if id and id != ""
121
133
  Collection.new(@model, instance, inverse_association, association.klass, ["find", id], inverse_of)
@@ -135,7 +135,6 @@ module ReactiveRecord
135
135
  model.columns_hash[method_name] || model.server_methods[method_name]
136
136
  end
137
137
 
138
-
139
138
  class << self
140
139
 
141
140
  attr_reader :pending_fetches
@@ -234,7 +233,7 @@ module ReactiveRecord
234
233
  if association.collection?
235
234
  # following line changed from .all to .collection on 10/28
236
235
  [*value.collection, *value.unsaved_children].each do |assoc|
237
- add_new_association.call(record, attribute, assoc.backing_record) if assoc.changed?(association.inverse_of) or assoc.new?
236
+ add_new_association.call(record, attribute, assoc.backing_record) if assoc.changed?(association.inverse_of(assoc)) or assoc.new_record?
238
237
  end
239
238
  elsif record.new? || record.changed?(attribute) || (record == record_being_saved && force)
240
239
  if value.nil?
@@ -243,7 +242,7 @@ module ReactiveRecord
243
242
  add_new_association.call record, attribute, value.backing_record
244
243
  end
245
244
  end
246
- elsif aggregation = record.model.reflect_on_aggregation(attribute) and (aggregation.klass < ActiveRecord::Base)
245
+ elsif (aggregation = record.model.reflect_on_aggregation(attribute)) && (aggregation.klass < ActiveRecord::Base)
247
246
  add_new_association.call record, attribute, value.backing_record unless value.nil?
248
247
  elsif aggregation
249
248
  new_value = aggregation.serialize(value)
@@ -264,8 +263,17 @@ module ReactiveRecord
264
263
  HyperMesh.load do
265
264
  ReactiveRecord.loads_pending! unless self.class.pending_fetches.empty?
266
265
  end.then { send_save_to_server(save, validate, force, &block) }
267
- #save_to_server(validate, force, &block)
268
266
  else
267
+ if validate
268
+ # Handles the case where a model is valid, then some attribute is
269
+ # updated, and model.validate is called updating the error data.
270
+ # Now lets say the attribute changes back to the last synced value. In
271
+ # this case we need to revert the error records.
272
+ models, _, backing_records = self.class.gather_records([self], true, self)
273
+ models.each do |item|
274
+ backing_records[item[:id]].revert_errors!
275
+ end
276
+ end
269
277
  promise = Promise.new
270
278
  yield true, nil, [] if block
271
279
  promise.resolve({success: true})
@@ -302,14 +310,13 @@ module ReactiveRecord
302
310
 
303
311
  response[:saved_models].each do | item |
304
312
  backing_records[item[0]].sync_unscoped_collection! if save
305
- backing_records[item[0]].errors! item[3]
313
+ backing_records[item[0]].errors! item[3], save
306
314
  end
307
315
 
308
316
  yield response[:success], response[:message], response[:models] if block
309
317
  promise.resolve response # TODO this could be problematic... there was no .json here, so .... what's to do?
310
318
 
311
319
  rescue Exception => e
312
- # debugger
313
320
  log("Exception raised while saving - #{e}", :error)
314
321
  ensure
315
322
  backing_records.each { |_id, record| record.saved! rescue nil } if save
@@ -454,6 +461,11 @@ module ReactiveRecord
454
461
  elsif parent.class.reflect_on_association(association[:attribute].to_sym).collection?
455
462
  #puts ">>>>>>>>>> #{parent.class.name}.send('#{association[:attribute]}') << #{reactive_records[association[:child_id]]})"
456
463
  dont_save_list.delete(parent)
464
+
465
+
466
+ # if reactive_records[association[:child_id]]&.new_record?
467
+ # dont_save_list << reactive_records[association[:child_id]]
468
+ # end
457
469
  #if false and parent.new?
458
470
  #parent.send("#{association[:attribute]}") << reactive_records[association[:child_id]]
459
471
  # puts "updated"
@@ -464,10 +476,13 @@ module ReactiveRecord
464
476
  #puts ">>>>ASSOCIATION>>>> #{parent.class.name}.send('#{association[:attribute]}=', #{reactive_records[association[:child_id]]})"
465
477
  parent.send("#{association[:attribute]}=", reactive_records[association[:child_id]])
466
478
  dont_save_list.delete(parent)
467
- #puts "updated"
479
+
480
+ # if parent.class.reflect_on_association(association[:attribute].to_sym).macro == :has_one &&
481
+ # reactive_records[association[:child_id]]&.new_record?
482
+ # dont_save_list << reactive_records[association[:child_id]]
483
+ # end
468
484
  end
469
485
  end if associations
470
-
471
486
  # get rid of any records that don't require further processing, as a side effect
472
487
  # we also save any records that need to be saved (these may be rolled back later.)
473
488
 
@@ -476,7 +491,9 @@ module ReactiveRecord
476
491
  next true if record.frozen? # skip (but process later) frozen records
477
492
  next true if dont_save_list.include?(record) # skip if the record is on the don't save list
478
493
  next true if record.changed.include?(record.class.primary_key) # happens on an aggregate
479
- next false if record.id && !record.changed? # throw out any existing records with no changes
494
+ #next true if record.persisted? # record may be have been saved as result of has_one assignment
495
+ # next false if record.id && !record.changed? # throw out any existing records with no changes
496
+ next record.persisted? if record.id && !record.changed?
480
497
  # if we get to here save the record and return true to keep it
481
498
  op = new_models.include?(record) ? :create_permitted? : :update_permitted?
482
499
  record.check_permission_with_acting_user(acting_user, op).save(validate: false) || true
@@ -492,14 +509,15 @@ module ReactiveRecord
492
509
  # the all the error messages during a save so we can dump them to the server log.
493
510
 
494
511
  all_messages = []
512
+ attributes = nil
495
513
 
496
514
  saved_models = reactive_records.collect do |reactive_record_id, model|
497
515
  messages = model.errors.messages if validate && !model.valid?
498
516
  all_messages << [model, messages] if save && messages
499
517
  attributes = model.__hyperstack_secure_attributes(acting_user)
518
+ attributes[model.class.primary_key] = model[model.class.primary_key]
500
519
  [reactive_record_id, model.class.name, attributes, messages]
501
520
  end
502
-
503
521
  # if we are not saving (i.e. just validating) then we rollback the transaction
504
522
 
505
523
  raise ActiveRecord::Rollback, 'This Rollback is intentional!' unless save
@@ -514,7 +532,6 @@ module ReactiveRecord
514
532
  end
515
533
  raise 'HyperModel saving records failed!'
516
534
  end
517
-
518
535
  end
519
536
 
520
537
  { success: true, saved_models: saved_models }