hyper-model 1.0.alpha1.2 → 1.0.alpha1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rspec +0 -1
  4. data/Gemfile +6 -5
  5. data/Rakefile +27 -3
  6. data/hyper-model.gemspec +11 -19
  7. data/lib/active_record_base.rb +105 -33
  8. data/lib/enumerable/pluck.rb +3 -2
  9. data/lib/hyper-model.rb +4 -1
  10. data/lib/hyper_model/version.rb +1 -1
  11. data/lib/hyper_react/input_tags.rb +2 -1
  12. data/lib/reactive_record/active_record/associations.rb +130 -34
  13. data/lib/reactive_record/active_record/base.rb +32 -0
  14. data/lib/reactive_record/active_record/class_methods.rb +124 -52
  15. data/lib/reactive_record/active_record/error.rb +2 -0
  16. data/lib/reactive_record/active_record/errors.rb +8 -4
  17. data/lib/reactive_record/active_record/instance_methods.rb +73 -5
  18. data/lib/reactive_record/active_record/public_columns_hash.rb +25 -26
  19. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
  20. data/lib/reactive_record/active_record/reactive_record/base.rb +50 -24
  21. data/lib/reactive_record/active_record/reactive_record/collection.rb +226 -68
  22. data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
  23. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +27 -15
  24. data/lib/reactive_record/active_record/reactive_record/getters.rb +33 -10
  25. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +81 -51
  26. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
  27. data/lib/reactive_record/active_record/reactive_record/operations.rb +10 -3
  28. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -0
  29. data/lib/reactive_record/active_record/reactive_record/setters.rb +105 -68
  30. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +249 -32
  31. data/lib/reactive_record/broadcast.rb +62 -25
  32. data/lib/reactive_record/interval.rb +3 -3
  33. data/lib/reactive_record/permissions.rb +14 -2
  34. data/lib/reactive_record/scope_description.rb +3 -2
  35. data/lib/reactive_record/server_data_cache.rb +99 -49
  36. data/polymorph-notes.md +143 -0
  37. data/spec_fails.txt +3 -0
  38. metadata +54 -153
  39. data/Gemfile.lock +0 -421
@@ -6,5 +6,37 @@ 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
+ def self.where(*args)
14
+ if args[0].is_a? Hash
15
+ # we can compute membership in the scope when the arg is a hash
16
+ __hyperstack_internal_where_hash_scope(args[0])
17
+ else
18
+ # otherwise the scope has to always be computed on the server
19
+ __hyperstack_internal_where_sql_scope(*args)
20
+ end
21
+ end
22
+
23
+ scope :__hyperstack_internal_where_hash_scope,
24
+ client: ->(attrs) { !attrs.detect { |k, v| self[k] != v } }
25
+
26
+ scope :__hyperstack_internal_where_sql_scope
27
+
28
+ ReactiveRecord::ScopeDescription.new(
29
+ self, :___hyperstack_internal_scoped_find_by,
30
+ client: ->(attrs) { !attrs.detect { |attr, value| attributes[attr] != value } }
31
+ )
32
+
33
+ def self.__hyperstack_internal_scoped_find_by(attrs)
34
+ collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs).observed
35
+ if !collection.collection
36
+ collection._find_by_initializer(self, attrs)
37
+ else
38
+ collection.first
39
+ end
40
+ end
9
41
  end
10
42
  end
@@ -1,11 +1,20 @@
1
1
  module ActiveRecord
2
-
3
2
  module ClassMethods
4
-
5
- alias _new_without_sti_type_cast new
6
-
7
- def new(*args, &block)
8
- _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type
3
+ begin
4
+ # Opal 0.11 super did not work with new, but new was defined
5
+ alias _new_without_sti_type_cast new
6
+ def new(*args, &block)
7
+ _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type
8
+ end
9
+ rescue NameError
10
+ def self.extended(base)
11
+ base.singleton_class.class_eval do
12
+ alias_method :_new_without_sti_type_cast, :new
13
+ define_method :new do |*args, &block|
14
+ _new_without_sti_type_cast(*args, &block).cast_to_current_sti_type
15
+ end
16
+ end
17
+ end
9
18
  end
10
19
 
11
20
  def base_class
@@ -51,14 +60,27 @@ module ActiveRecord
51
60
  @model_name ||= ActiveModel::Name.new(self)
52
61
  end
53
62
 
54
- def find(id)
55
- ReactiveRecord::Base.find(self, primary_key => id)
63
+ def __hyperstack_preprocess_attrs(attrs)
64
+ if inheritance_column && self < base_class && !attrs.key?(inheritance_column)
65
+ attrs = attrs.merge(inheritance_column => model_name.to_s)
66
+ end
67
+ dealiased_attrs = {}
68
+ attrs.each { |attr, value| dealiased_attrs[_dealias_attribute(attr)] = value }
69
+ dealiased_attrs
70
+ end
71
+
72
+ def find(*args)
73
+ args = args[0] if args[0].is_a? Array
74
+ return args.collect { |id| find(id) } if args.count > 1
75
+ find_by(primary_key => args[0])
56
76
  end
57
77
 
58
- def find_by(opts = {})
59
- dealiased_opts = {}
60
- opts.each { |attr, value| dealiased_opts[_dealias_attribute(attr)] = value }
61
- ReactiveRecord::Base.find(self, dealiased_opts)
78
+ def find_by(attrs = {})
79
+ attrs = __hyperstack_preprocess_attrs(attrs)
80
+ # r = ReactiveRecord::Base.find_locally(self, attrs, new_only: true)
81
+ # return r.ar_instance if r
82
+ (r = __hyperstack_internal_scoped_find_by(attrs)) || return
83
+ r.backing_record.sync_attributes(attrs).set_ar_instance!
62
84
  end
63
85
 
64
86
  def enum(*args)
@@ -162,7 +184,7 @@ module ActiveRecord
162
184
  :full_table_name_prefix, :full_table_name_suffix, :reset_sequence_name, :sequence_name=, :next_sequence_value, :column_defaults, :content_columns,
163
185
  :readonly_attributes, :attr_readonly, :create, :create!, :instantiate, :find, :type_caster, :arel_table, :find_by, :find_by!, :initialize_find_by_cache,
164
186
  :generated_association_methods, :arel_engine, :arel_attribute, :predicate_builder, :collection_cache_key, :relation_delegate_class,
165
- :initialize_relation_delegate_cache, :enum, :collecting_queries_for_explain, :exec_explain, :i18n_scope, :lookup_ancestors, :human_attribute_name,
187
+ :initialize_relation_delegate_cache, :enum, :collecting_queries_for_explain, :exec_explain, :i18n_scope, :lookup_ancestors,
166
188
  :references, :uniq, :maximum, :none, :exists?, :second, :limit, :order, :eager_load, :update, :delete_all, :destroy, :ids, :many?, :pluck, :third,
167
189
  :delete, :fourth, :fifth, :forty_two, :second_to_last, :third_to_last, :preload, :sum, :take!, :first!, :last!, :second!, :offset, :select, :fourth!,
168
190
  :third!, :third_to_last!, :fifth!, :where, :first_or_create, :second_to_last!, :forty_two!, :first, :having, :any?, :one?, :none?, :find_or_create_by,
@@ -175,8 +197,11 @@ module ActiveRecord
175
197
  ]
176
198
 
177
199
  def method_missing(name, *args, &block)
178
- if args.count == 1 && name.start_with?("find_by_") && !block
179
- find_by(_dealias_attribute(name.sub(/^find_by_/, "")) => args[0])
200
+ if name == 'human_attribute_name'
201
+ opts = args[1] || {}
202
+ opts[:default] || args[0]
203
+ elsif args.count == 1 && name.start_with?("find_by_") && !block
204
+ find_by(name.sub(/^find_by_/, '') => args[0])
180
205
  elsif [].respond_to?(name)
181
206
  all.send(name, *args, &block)
182
207
  elsif name.end_with?('!')
@@ -193,16 +218,6 @@ module ActiveRecord
193
218
  # Any method ending with ! just means apply the method after forcing a reload
194
219
  # from the DB.
195
220
 
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
221
  def create(*args, &block)
207
222
  new(*args).save(&block)
208
223
  end
@@ -213,9 +228,6 @@ module ActiveRecord
213
228
  singleton_class.send(:define_method, name) do |*vargs|
214
229
  all.build_child_scope(scope_description, *name, *vargs)
215
230
  end
216
- # singleton_class.send(:define_method, "#{name}=") do |_collection|
217
- # raise 'NO LONGER IMPLEMENTED - DOESNT PLAY WELL WITH SYNCHROMESH'
218
- # end
219
231
  end
220
232
 
221
233
  def default_scope(*args, &block)
@@ -249,10 +261,6 @@ module ActiveRecord
249
261
  end
250
262
  end
251
263
 
252
- # def all=(_collection)
253
- # raise "NO LONGER IMPLEMENTED DOESNT PLAY WELL WITH SYNCHROMESH"
254
- # end
255
-
256
264
  def unscoped
257
265
  ReactiveRecord::Base.unscoped[self] ||=
258
266
  ReactiveRecord::Collection
@@ -264,7 +272,8 @@ module ActiveRecord
264
272
  ReactiveRecord::ScopeDescription.new(self, "_#{name}", {})
265
273
  [name, "#{name}!"].each do |method|
266
274
  singleton_class.send(:define_method, method) do |*vargs|
267
- all.apply_scope("_#{method}", *vargs).first
275
+ collection = all.apply_scope("_#{method}", *vargs)
276
+ collection.first
268
277
  end
269
278
  end
270
279
  end
@@ -298,24 +307,34 @@ module ActiveRecord
298
307
  assoc = Associations::AssociationReflection.new(self, macro, name, opts)
299
308
  if macro == :has_many
300
309
  define_method(name) { @backing_record.get_has_many(assoc, nil) }
301
- define_method("#{name}=") { |val| @backing_record.set_has_many(assoc, val) }
310
+ define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_has_many(assoc, val) }
302
311
  else
303
312
  define_method(name) { @backing_record.get_belongs_to(assoc, nil) }
304
- define_method("#{name}=") { |val| @backing_record.set_belongs_to(assoc, val) }
313
+ define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_belongs_to(assoc, val) }
305
314
  end
315
+ alias_method "#{name}=", "_hyperstack_internal_setter_#{name}"
306
316
  assoc
307
317
  end
308
318
  end
309
319
 
320
+ def table_name
321
+ @table_name || name.downcase.pluralize
322
+ end
323
+
324
+ def table_name=(name)
325
+ @table_name = name
326
+ end
327
+
310
328
  def composed_of(name, opts = {})
311
329
  reflection = Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts)
312
330
  if reflection.klass < ActiveRecord::Base
313
331
  define_method(name) { @backing_record.get_ar_aggregate(reflection, nil) }
314
- define_method("#{name}=") { |val| @backing_record.set_ar_aggregate(reflection, val) }
332
+ define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_ar_aggregate(reflection, val) }
315
333
  else
316
334
  define_method(name) { @backing_record.get_non_ar_aggregate(name, nil) }
317
- define_method("#{name}=") { |val| @backing_record.set_non_ar_aggregate(reflection, val) }
335
+ define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_non_ar_aggregate(reflection, val) }
318
336
  end
337
+ alias_method "#{name}=", "_hyperstack_internal_setter_#{name}"
319
338
  end
320
339
 
321
340
  def column_names
@@ -340,16 +359,31 @@ module ActiveRecord
340
359
  vector = args.count.zero? ? name : [[name] + args]
341
360
  @backing_record.get_server_method(vector, true)
342
361
  end
362
+ define_method("_hyperstack_internal_setter_#{name}") do |val|
363
+ backing_record.set_attr_value(name, val)
364
+ end
343
365
  end
344
366
 
367
+ # define all the methods for each column. To allow overriding the methods they will NOT
368
+ # be defined if already defined (i.e. by the model) See the instance_methods module for how
369
+ # super calls are handled in this case. The _hyperstack_internal_setter_... methods
370
+ # are used by the load_from_json method when bringing in data from the server, and so therefore
371
+ # does not want to be overriden.
372
+
345
373
  def define_attribute_methods
346
374
  columns_hash.each do |name, column_hash|
347
- next if name == primary_key
348
- define_method(name) { @backing_record.get_attr_value(name, nil) }
349
- define_method("#{name}!") { @backing_record.get_attr_value(name, true) }
350
- define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) }
351
- define_method("#{name}_changed?") { @backing_record.changed?(name) }
352
- define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? }
375
+ next if name == :id
376
+ # only add serialized key if its serialized. This just makes testing a bit
377
+ # easier by keeping the columns_hash the same if there are no seralized strings
378
+ # see rspec ./spec/batch1/column_types/column_type_spec.rb:100
379
+ column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]
380
+
381
+ define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
382
+ define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
383
+ define_method("_hyperstack_internal_setter_#{name}") { |val| @backing_record.set_attr_value(name, val) }
384
+ alias_method "#{name}=", "_hyperstack_internal_setter_#{name}" unless method_defined?("#{name}=")
385
+ define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
386
+ define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
353
387
  end
354
388
  self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column)
355
389
  end
@@ -370,29 +404,67 @@ module ActiveRecord
370
404
  # TODO: changed values as changes while just updating the synced values.
371
405
  target =
372
406
  if param[primary_key]
373
- find(param[primary_key])
407
+ ReactiveRecord::Base.find(self, primary_key => param[primary_key]).tap do |r|
408
+ r.backing_record.loaded_id = param[primary_key]
409
+ end
374
410
  else
375
411
  new
376
412
  end
377
413
 
378
414
  associations = reflect_on_all_associations
379
415
 
416
+ already_processed_keys = Set.new
417
+
380
418
  param = param.collect do |key, value|
381
- assoc = associations.detect do |association|
382
- association.association_foreign_key == key
419
+ next if already_processed_keys.include? key
420
+
421
+ model_name = model_id = nil
422
+
423
+ # polymorphic association is where the belongs_to side holds the
424
+ # id, and the type of the model the id points to
425
+
426
+ # belongs_to :duplicate_of, class_name: 'Report', required: false
427
+ # has_many :duplicates, class_name: 'Report', foreign_key: 'duplicate_of_id'
428
+
429
+ assoc = associations.detect do |poly_assoc|
430
+ if key == poly_assoc.polymorphic_type_attribute
431
+ model_name = value
432
+ already_processed_keys << poly_assoc.association_foreign_key
433
+ elsif key == poly_assoc.association_foreign_key && (poly_assoc.polymorphic_type_attribute || poly_assoc.macro == :belongs_to)
434
+ model_id = value
435
+ already_processed_keys << poly_assoc.polymorphic_type_attribute
436
+ end
383
437
  end
384
438
 
385
439
  if assoc
386
- if value
387
- [assoc.attribute, { id: [value] }]
388
- else
440
+ if !value
389
441
  [assoc.attribute, [nil]]
442
+ elsif assoc.polymorphic?
443
+ model_id ||= param.detect { |k, *| k == assoc.association_foreign_key }&.last
444
+ model_id ||= target.send(assoc.attribute)&.id
445
+ if model_id.nil?
446
+ raise "Error in #{self.name}._react_param_conversion. \n"\
447
+ "Could not determine the id of #{assoc.attribute} of #{target.inspect}.\n"\
448
+ "It was not provided in the conversion data, "\
449
+ "and it is unknown on the client"
450
+ end
451
+ model_name ||= param.detect { |k, *| k == assoc.polymorphic_type_attribute }&.last
452
+ model_name ||= target.send(assoc.polymorphic_type_attribute)
453
+ unless Object.const_defined?(model_name)
454
+ raise "Error in #{self.name}._react_param_conversion. \n"\
455
+ "Could not determine the type of #{assoc.attribute} of #{target.inspect}.\n"\
456
+ "It was not provided in the conversion data, "\
457
+ "and it is unknown on the client"
458
+ end
459
+
460
+ [assoc.attribute, { id: [model_id], model_name: [model_name] }]
461
+ else
462
+ [assoc.attribute, { id: [value]}]
390
463
  end
391
464
  else
392
- [key, [value]]
465
+ [*key, [value]]
393
466
  end
394
- end
395
- # TODO: verify wrapping with load_data was added so broadcasting works in 1.0.0.lap28
467
+ end.compact
396
468
  ReactiveRecord::Base.load_data do
397
469
  ReactiveRecord::ServerDataCache.load_from_json(Hash[param], target)
398
470
  end
@@ -24,6 +24,8 @@ module ActiveModel
24
24
  @messages.clear
25
25
  end
26
26
 
27
+ alias non_reactive_clear clear
28
+
27
29
  def add(attribute, message = :invalid, _options = {})
28
30
  @messages[attribute] << message unless @messages[attribute].include? message
29
31
  end
@@ -89,7 +89,7 @@ module ActiveModel
89
89
  # person.errors.values # => [["cannot be nil", "must be specified"]]
90
90
  def values
91
91
  messages.select do |key, value|
92
- !value.empty?
92
+ !value&.empty?
93
93
  end.values
94
94
  end
95
95
 
@@ -105,9 +105,9 @@ module ActiveModel
105
105
  attr_name =
106
106
  attribute.to_s.tr('.', '_').tr('_', ' ').gsub(/_id$/, '').capitalize
107
107
  # attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
108
- # if @base.class.respond_to?(:human_attribute_name)
108
+ if @base.class.respond_to?(:human_attribute_name)
109
109
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
110
- # end
110
+ end
111
111
  # I18n.t(:"errors.format",
112
112
  # default: "%{attribute} %{message}",
113
113
  # attribute: attr_name,
@@ -219,8 +219,12 @@ module ActiveModel
219
219
  # person.errors.clear
220
220
  # person.errors.full_messages # => []
221
221
  def clear
222
+ non_reactive_clear.tap { reactive_empty! true }
223
+ end
224
+
225
+ def non_reactive_clear
222
226
  messages.clear
223
- details.clear.tap { reactive_empty! true }
227
+ details.clear
224
228
  end
225
229
 
226
230
  # Merges the errors from <tt>other</tt>.
@@ -1,5 +1,59 @@
1
1
  module ActiveRecord
2
2
  module InstanceMethods
3
+
4
+ # if methods are missing, then they must be a column, which we look up
5
+ # in the columns_hash.
6
+
7
+ # For effeciency all attributes will by default have all the methods defined,
8
+ # when the class is loaded. See define_attribute_methods class method.
9
+ # However a model may override the attribute methods definition, but then call
10
+ # super. Which will result in the method missing call.
11
+
12
+ # When loading data from the server we do NOT want to call overridden methods
13
+ # so we also define a _hyperstack_internal_setter_... method for each attribute
14
+ # as well as for belongs_to relationships, server_methods, and the special
15
+ # type and model_name methods. See the ClassMethods module for details.
16
+
17
+ # meanwhile in Opal 1.0 there is currently an issue where the name of the method
18
+ # does not get passed to method_missing from super.
19
+ # https://github.com/opal/opal/issues/2165
20
+ # So the following hack works around that issue until its fixed.
21
+
22
+ %x{
23
+ Opal.orig_find_super_dispatcher = Opal.find_super_dispatcher
24
+ Opal.find_super_dispatcher = function(obj, mid, current_func, defcheck, allow_stubs) {
25
+ Opal.__name_of_super = mid;
26
+ return Opal.orig_find_super_dispatcher(obj, mid, current_func, defcheck, allow_stubs)
27
+ }
28
+ }
29
+
30
+ def method_missing(missing, *args, &block)
31
+ missing ||= `Opal.__name_of_super`
32
+ column = self.class.columns_hash.detect { |name, *| missing =~ /^#{name}/ }
33
+ if column
34
+ name = column[0]
35
+ case missing
36
+ when /\!\z/ then @backing_record.get_attr_value(name, true)
37
+ when /\=\z/ then @backing_record.set_attr_value(name, *args)
38
+ when /\_changed\?\z/ then @backing_record.changed?(name)
39
+ when /\?/ then @backing_record.get_attr_value(name, nil).present?
40
+ else @backing_record.get_attr_value(name, nil)
41
+ end
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ # the system assumes that there is "virtual" model_name and type attribute so
48
+ # we define the internal setter here. If the user defines some other attributes
49
+ # or uses these names no harm is done since the exact same method would have been
50
+ # defined by the define_attribute_methods class method anyway.
51
+ %i[model_name type].each do |attr|
52
+ define_method("_hyperstack_internal_setter_#{attr}") do |val|
53
+ @backing_record.set_attr_value(:model_name, val)
54
+ end
55
+ end
56
+
3
57
  def inspect
4
58
  "<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\
5
59
  "(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\
@@ -38,7 +92,7 @@ module ActiveRecord
38
92
  end
39
93
  self.class.load_data do
40
94
  h.each do |attribute, value|
41
- next if attribute == primary_key
95
+ next if attribute == :id
42
96
  @ar_instance[attribute] = value
43
97
  changed_attributes << attribute
44
98
  end
@@ -72,8 +126,8 @@ module ActiveRecord
72
126
  @backing_record.revert
73
127
  end
74
128
 
75
- def changed?
76
- @backing_record.changed?
129
+ def changed?(attr = nil)
130
+ @backing_record.changed?(*attr)
77
131
  end
78
132
 
79
133
  def dup
@@ -81,7 +135,11 @@ module ActiveRecord
81
135
  end
82
136
 
83
137
  def ==(ar_instance)
84
- @backing_record == ar_instance.instance_eval { @backing_record }
138
+ return true if @backing_record == ar_instance.instance_eval { @backing_record }
139
+ return false unless ar_instance.is_a?(ActiveRecord::Base)
140
+ return false if ar_instance.new_record?
141
+ return false unless self.class.base_class == ar_instance.class.base_class
142
+ id == ar_instance.id
85
143
  end
86
144
 
87
145
  def [](attr)
@@ -104,6 +162,14 @@ module ActiveRecord
104
162
  # end
105
163
  end
106
164
 
165
+ def increment!(attr)
166
+ load(attr).then { |current_value| update(attr => current_value + 1) }
167
+ end
168
+
169
+ def decrement!(attr)
170
+ load(attr).then { |current_value| update(attr => current_value - 1) }
171
+ end
172
+
107
173
  def load(*attributes, &block)
108
174
  first_time = true
109
175
  ReactiveRecord.load do
@@ -144,10 +210,12 @@ module ActiveRecord
144
210
  @backing_record.destroyed
145
211
  end
146
212
 
147
- def new?
213
+ def new_record?
148
214
  @backing_record.new?
149
215
  end
150
216
 
217
+ alias new? new_record?
218
+
151
219
  def errors
152
220
  Hyperstack::Internal::State::Variable.get(@backing_record, @backing_record)
153
221
  @backing_record.errors