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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2458d824d9c7121f8e628dca72a9df1f6a87ecfc947361046887ea1eca56657
4
- data.tar.gz: 4e6d8aa0a03d07c0080d73aa8bddbc876c1fa56eaebca3f7615425914f8fee07
3
+ metadata.gz: b2f63052beefadb742e008e12976afe889105a0cdbd864ea49902654eb82be51
4
+ data.tar.gz: 2bdf35fb8b4ccd7fd00144be9ba52d8a133797e47825b91cf6acae8dc0673043
5
5
  SHA512:
6
- metadata.gz: 2e0e89354d96b059f62b51023a568e2b334ac2b47bc5940bc8ac6621940259e0b03ec90a61e5e9e135a07954d0cd75a93c6f765486412f80470aedb5a5dc96d0
7
- data.tar.gz: 6b1642506f04f6fc00fd6e6be388632e1157e6405c629928a61d5a8d525fac8666b0f6611d4e261060573b2dc3b184ea3fe0c5640f1a955d2193899b6223202e
6
+ metadata.gz: 2e7481f8e04d3d464507d7c25c20b3dc55167dd6b8cb368f28113e9f52b4ba95815611b8f4fe740d207860f1bc60cdb4ddcdafb996d83ddf2d51afb8cc397fb5
7
+ data.tar.gz: e0e5fb04e304496c3d0ef8353a9ed0bd36a303257e3f062d9ed61e75b4af8d1ded8ed18defd37e7e44dd79b618b221d129287a39d9e331bb20b9cc648c668124
data/.rspec CHANGED
@@ -1,2 +1 @@
1
- --format documentation
2
1
  --color
data/Gemfile CHANGED
@@ -1,10 +1,11 @@
1
1
  source 'https://rubygems.org'
2
- #gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
3
- # hyper-model is still using an ancient inlined version of hyper-spec
4
- #gem 'hyper-spec', path: '../hyper-spec'
5
- gem 'hyperstack-config', path: '../hyperstack-config'
6
2
  gem 'hyper-state', path: '../hyper-state'
7
3
  gem 'hyper-component', path: '../hyper-component'
8
4
  gem 'hyper-operation', path: '../hyper-operation'
9
-
5
+ gem 'hyper-spec', path: '../hyper-spec'
6
+ gem 'hyper-trace', path: '../hyper-trace'
7
+ gem 'hyperstack-config', path: '../hyperstack-config'
8
+ unless ENV['OPAL_VERSION']&.match("0.11")
9
+ gem 'opal-browser', git: 'https://github.com/opal/opal-browser'
10
+ end
10
11
  gemspec
data/Rakefile CHANGED
@@ -32,7 +32,7 @@ end
32
32
 
33
33
  namespace :spec do
34
34
  task :prepare do
35
- sh %(cd spec/test_app; bundle exec rails db:setup)
35
+ sh %(cd spec/test_app; rm db/schema.rb; RAILS_ENV=test bundle exec rails db:setup; RAILS_ENV=test bundle exec rails db:migrate)
36
36
  end
37
37
  (1..7).each do |batch|
38
38
  RSpec::Core::RakeTask.new(:"batch#{batch}") do |t|
data/hyper-model.gemspec CHANGED
@@ -27,31 +27,24 @@ Gem::Specification.new do |spec|
27
27
 
28
28
  spec.add_dependency 'activemodel'
29
29
  spec.add_dependency 'activerecord', '>= 4.0.0'
30
- spec.add_dependency 'hyper-component', HyperModel::VERSION
31
30
  spec.add_dependency 'hyper-operation', HyperModel::VERSION
32
- spec.add_development_dependency 'bundler', ['>= 1.17.3', '< 2.1']
33
- spec.add_development_dependency 'capybara'
34
- spec.add_development_dependency 'chromedriver-helper', '1.2.0'
35
- spec.add_development_dependency 'libv8'
36
- spec.add_development_dependency 'mini_racer', '~> 0.2.4'
37
- spec.add_development_dependency 'selenium-webdriver'
31
+
32
+ spec.add_development_dependency 'bundler'
38
33
  spec.add_development_dependency 'database_cleaner'
39
34
  spec.add_development_dependency 'factory_bot_rails'
40
- #spec.add_development_dependency 'hyper-spec', HyperModel::VERSION
41
- spec.add_development_dependency 'mysql2'
42
- spec.add_development_dependency 'opal-activesupport', '~> 0.3.1'
43
- spec.add_development_dependency 'opal-browser', '~> 0.2.0'
44
- spec.add_development_dependency 'opal-rails', '~> 0.9.4'
45
- spec.add_development_dependency 'parser'
46
- spec.add_development_dependency 'pry'
35
+ spec.add_development_dependency 'hyper-spec', HyperModel::VERSION
36
+ spec.add_development_dependency 'hyper-trace', HyperModel::VERSION
37
+ spec.add_development_dependency 'mini_racer'
38
+ spec.add_development_dependency 'pg'
39
+ spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0'
47
40
  spec.add_development_dependency 'pry-rescue'
41
+ spec.add_development_dependency 'pry-stack_explorer'
48
42
  spec.add_development_dependency 'puma'
49
43
  spec.add_development_dependency 'pusher'
50
44
  spec.add_development_dependency 'pusher-fake'
51
- spec.add_development_dependency 'rails', '>= 4.0.0'
45
+ spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0'
52
46
  spec.add_development_dependency 'rake'
53
47
  spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
54
- spec.add_development_dependency 'reactrb-rails-generator'
55
48
  spec.add_development_dependency 'rspec-collection_matchers'
56
49
  spec.add_development_dependency 'rspec-expectations'
57
50
  spec.add_development_dependency 'rspec-its'
@@ -62,8 +55,7 @@ Gem::Specification.new do |spec|
62
55
  spec.add_development_dependency 'rubocop', '~> 0.51.0'
63
56
  spec.add_development_dependency 'shoulda'
64
57
  spec.add_development_dependency 'shoulda-matchers'
65
- spec.add_development_dependency 'spring-commands-rspec'
66
- spec.add_development_dependency 'sqlite3', '~> 1.3.6' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6'
58
+ spec.add_development_dependency 'spring-commands-rspec', '~> 1.0.4'
59
+ spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6'
67
60
  spec.add_development_dependency 'timecop', '~> 0.8.1'
68
- spec.add_development_dependency 'unparser'
69
61
  end
@@ -5,17 +5,46 @@ module ActiveRecord
5
5
  # processes these arguments, and the will always leave the true server side scoping
6
6
  # proc in the `:server` opts. This method is common to client and server.
7
7
  class Base
8
- def self._synchromesh_scope_args_check(args)
9
- opts = if args.count == 2 && args[1].is_a?(Hash)
10
- args[1].merge(server: args[0])
11
- elsif args[0].is_a? Hash
12
- args[0]
13
- else
14
- { server: args[0] }
15
- end
16
- return opts if opts && opts[:server].respond_to?(:call)
17
- raise 'must provide either a proc as the first arg or by the '\
18
- '`:server` option to scope and default_scope methods'
8
+ class << self
9
+ def _synchromesh_scope_args_check(args)
10
+ opts = if args.count == 2 && args[1].is_a?(Hash)
11
+ args[1].merge(server: args[0])
12
+ elsif args[0].is_a? Hash
13
+ args[0]
14
+ else
15
+ { server: args[0] }
16
+ end
17
+ return opts if opts && opts[:server].respond_to?(:call)
18
+ raise 'must provide either a proc as the first arg or by the '\
19
+ '`:server` option to scope and default_scope methods'
20
+ end
21
+
22
+ alias pre_hyperstack_has_and_belongs_to_many has_and_belongs_to_many unless RUBY_ENGINE == 'opal'
23
+
24
+ def has_and_belongs_to_many(other, opts = {}, &block)
25
+ join_table_name = [other.to_s, table_name].sort.join('_')
26
+ join_model_name = "HyperstackInternalHabtm#{join_table_name.singularize.camelize}"
27
+ join_model =
28
+ if Object.const_defined? join_model_name
29
+ Object.const_get(join_model_name)
30
+ else
31
+ Object.const_set(join_model_name, Class.new(ActiveRecord::Base))
32
+ end
33
+
34
+ join_model.class_eval { belongs_to other.to_s.singularize.to_sym }
35
+
36
+ has_many join_model_name.underscore.pluralize.to_sym
37
+
38
+ if RUBY_ENGINE == 'opal'
39
+ Object.const_set("HABTM_#{other.to_s.camelize}", join_model)
40
+ join_model.inheritance_column = nil
41
+ has_many other, through: join_model_name.underscore.pluralize.to_sym
42
+ else
43
+ join_model.table_name = join_table_name
44
+ join_model.belongs_to other
45
+ pre_hyperstack_has_and_belongs_to_many(other, opts, &block)
46
+ end
47
+ end
19
48
  end
20
49
  end
21
50
  if RUBY_ENGINE != 'opal'
@@ -270,6 +299,12 @@ module ActiveRecord
270
299
  Hyperstack::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access. Called from #{caller_locations(1)}")
271
300
  end
272
301
 
302
+ unless method_defined? :saved_changes # for backwards compatibility to Rails < 5.1.7
303
+ def saved_changes
304
+ previous_changes
305
+ end
306
+ end
307
+
273
308
  # call do_not_synchronize to block synchronization of a model
274
309
 
275
310
  def self.do_not_synchronize
@@ -286,17 +321,28 @@ module ActiveRecord
286
321
  self.class.do_not_synchronize?
287
322
  end
288
323
 
324
+ before_create :synchromesh_mark_update_time
325
+ before_update :synchromesh_mark_update_time
326
+ before_destroy :synchromesh_mark_update_time
327
+
328
+ attr_reader :__synchromesh_update_time
329
+
330
+ def synchromesh_mark_update_time
331
+ @__synchromesh_update_time = Time.now.to_f
332
+ end
333
+
289
334
  after_commit :synchromesh_after_create, on: [:create]
290
335
  after_commit :synchromesh_after_change, on: [:update]
291
336
  after_commit :synchromesh_after_destroy, on: [:destroy]
292
337
 
293
338
  def synchromesh_after_create
339
+ puts "#{self}.synchromesh_after_create: #{do_not_synchronize?} channels: #{Hyperstack::Connection.active}" if Hyperstack::Connection.show_diagnostics
294
340
  return if do_not_synchronize?
295
341
  ReactiveRecord::Broadcast.after_commit :create, self
296
342
  end
297
343
 
298
344
  def synchromesh_after_change
299
- return if do_not_synchronize? || previous_changes.empty?
345
+ return if do_not_synchronize? || saved_changes.empty?
300
346
  ReactiveRecord::Broadcast.after_commit :change, self
301
347
  end
302
348
 
data/lib/hyper-model.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'set'
2
2
  require 'hyperstack-config'
3
- require 'hyper-component'
3
+
4
+ Hyperstack.import 'hyper-model'
5
+
4
6
  if RUBY_ENGINE == 'opal'
5
7
  require 'hyper-operation'
6
8
  require 'active_support'
@@ -1,3 +1,3 @@
1
1
  module HyperModel
2
- VERSION = '1.0.alpha1.5'
2
+ VERSION = '1.0.alpha1.6'
3
3
  end
@@ -3,7 +3,7 @@ module ActiveRecord
3
3
  class Base
4
4
 
5
5
  def self.reflect_on_all_associations
6
- base_class.instance_eval { @associations ||= superclass.instance_eval { (@associations && @associations.dup) || [] } }
6
+ @associations ||= superclass.instance_eval { @associations&.dup || [] }
7
7
  end
8
8
 
9
9
  def self.reflect_on_association(attr)
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  end
12
12
 
13
13
  def self.reflect_on_association_by_foreign_key(key)
14
- reflection_finder { |assoc| assoc.association_foreign_key == key }
14
+ reflection_finder { |assoc| assoc.association_foreign_key == key && assoc.macro != :has_many }
15
15
  end
16
16
 
17
17
  def self.reflection_finder(&block)
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  module Associations
33
33
 
34
34
  class AssociationReflection
35
-
35
+ attr_reader :klass_name
36
36
  attr_reader :association_foreign_key
37
37
  attr_reader :attribute
38
38
  attr_reader :macro
@@ -79,6 +79,10 @@ module ActiveRecord
79
79
  @macro != :has_many
80
80
  end
81
81
 
82
+ def habtm?
83
+ through_association&.klass_name =~ /^HyperstackInternalHabtm/
84
+ end
85
+
82
86
  def through_association
83
87
  return unless @options[:through]
84
88
  @through_association ||= @owner_class.reflect_on_all_associations.detect do |association|
@@ -161,6 +165,7 @@ module ActiveRecord
161
165
  def find_inverse(model) # private
162
166
  the_klass = klass(model)
163
167
  the_klass.reflect_on_all_associations.each do |association|
168
+ next if association == self
164
169
  next if association.association_foreign_key != @association_foreign_key
165
170
  next if association.attribute == attribute
166
171
  return association if association.polymorphic? || association.klass == owner_class
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  )
17
17
 
18
18
  def self.__hyperstack_internal_scoped_find_by(attrs)
19
- collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs)
19
+ collection = all.apply_scope(:___hyperstack_internal_scoped_find_by, attrs).observed
20
20
  if !collection.collection
21
21
  collection._find_by_initializer(self, attrs)
22
22
  else
@@ -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
@@ -57,10 +66,13 @@ module ActiveRecord
57
66
  end
58
67
  dealiased_attrs = {}
59
68
  attrs.each { |attr, value| dealiased_attrs[_dealias_attribute(attr)] = value }
69
+ dealiased_attrs
60
70
  end
61
71
 
62
- def find(id)
63
- find_by(primary_key => id)
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])
64
76
  end
65
77
 
66
78
  def find_by(attrs = {})
@@ -295,24 +307,34 @@ module ActiveRecord
295
307
  assoc = Associations::AssociationReflection.new(self, macro, name, opts)
296
308
  if macro == :has_many
297
309
  define_method(name) { @backing_record.get_has_many(assoc, nil) }
298
- 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) }
299
311
  else
300
312
  define_method(name) { @backing_record.get_belongs_to(assoc, nil) }
301
- 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) }
302
314
  end
315
+ alias_method "#{name}=", "_hyperstack_internal_setter_#{name}"
303
316
  assoc
304
317
  end
305
318
  end
306
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
+
307
328
  def composed_of(name, opts = {})
308
329
  reflection = Aggregations::AggregationReflection.new(base_class, :composed_of, name, opts)
309
330
  if reflection.klass < ActiveRecord::Base
310
331
  define_method(name) { @backing_record.get_ar_aggregate(reflection, nil) }
311
- 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) }
312
333
  else
313
334
  define_method(name) { @backing_record.get_non_ar_aggregate(name, nil) }
314
- 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) }
315
336
  end
337
+ alias_method "#{name}=", "_hyperstack_internal_setter_#{name}"
316
338
  end
317
339
 
318
340
  def column_names
@@ -337,34 +359,29 @@ module ActiveRecord
337
359
  vector = args.count.zero? ? name : [[name] + args]
338
360
  @backing_record.get_server_method(vector, true)
339
361
  end
362
+ define_method("_hyperstack_internal_setter_#{name}") do |val|
363
+ backing_record.set_attr_value(name, val)
364
+ end
340
365
  end
341
366
 
342
- # def define_attribute_methods
343
- # columns_hash.each do |name, column_hash|
344
- # next if name == primary_key
345
- # column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]
346
- #
347
- # define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
348
- # define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
349
- # define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } unless method_defined?("#{name}=")
350
- # define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
351
- # define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
352
- # end
353
- # self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column)
354
- # end
355
-
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.
356
372
 
357
373
  def define_attribute_methods
358
374
  columns_hash.each do |name, column_hash|
359
- next if name == primary_key
375
+ next if name == :id
360
376
  # only add serialized key if its serialized. This just makes testing a bit
361
377
  # easier by keeping the columns_hash the same if there are no seralized strings
362
378
  # see rspec ./spec/batch1/column_types/column_type_spec.rb:100
363
379
  column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]
364
-
380
+
365
381
  define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
366
382
  define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
367
- define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } 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}=")
368
385
  define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
369
386
  define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
370
387
  end
@@ -397,18 +414,23 @@ module ActiveRecord
397
414
  associations = reflect_on_all_associations
398
415
 
399
416
  already_processed_keys = Set.new
400
- old_param = param.dup
401
417
 
402
418
  param = param.collect do |key, value|
403
419
  next if already_processed_keys.include? key
404
420
 
405
421
  model_name = model_id = nil
406
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
+
407
429
  assoc = associations.detect do |poly_assoc|
408
430
  if key == poly_assoc.polymorphic_type_attribute
409
431
  model_name = value
410
432
  already_processed_keys << poly_assoc.association_foreign_key
411
- elsif key == poly_assoc.association_foreign_key
433
+ elsif key == poly_assoc.association_foreign_key && (poly_assoc.polymorphic_type_attribute || poly_assoc.macro == :belongs_to)
412
434
  model_id = value
413
435
  already_processed_keys << poly_assoc.polymorphic_type_attribute
414
436
  end
@@ -440,7 +462,7 @@ module ActiveRecord
440
462
  [assoc.attribute, { id: [value]}]
441
463
  end
442
464
  else
443
- [key, [value]]
465
+ [*key, [value]]
444
466
  end
445
467
  end.compact
446
468
  ReactiveRecord::Base.load_data do
@@ -1,7 +1,34 @@
1
1
  module ActiveRecord
2
2
  module InstanceMethods
3
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
+
4
30
  def method_missing(missing, *args, &block)
31
+ missing ||= `Opal.__name_of_super`
5
32
  column = self.class.columns_hash.detect { |name, *| missing =~ /^#{name}/ }
6
33
  if column
7
34
  name = column[0]
@@ -17,6 +44,16 @@ module ActiveRecord
17
44
  end
18
45
  end
19
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
+
20
57
  def inspect
21
58
  "<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\
22
59
  "(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\
@@ -55,7 +92,7 @@ module ActiveRecord
55
92
  end
56
93
  self.class.load_data do
57
94
  h.each do |attribute, value|
58
- next if attribute == primary_key
95
+ next if attribute == :id
59
96
  @ar_instance[attribute] = value
60
97
  changed_attributes << attribute
61
98
  end
@@ -89,8 +126,8 @@ module ActiveRecord
89
126
  @backing_record.revert
90
127
  end
91
128
 
92
- def changed?
93
- @backing_record.changed?
129
+ def changed?(attr = nil)
130
+ @backing_record.changed?(*attr)
94
131
  end
95
132
 
96
133
  def dup
@@ -125,6 +162,14 @@ module ActiveRecord
125
162
  # end
126
163
  end
127
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
+
128
173
  def load(*attributes, &block)
129
174
  first_time = true
130
175
  ReactiveRecord.load do