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

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