hyper-mesh 1.0.0.lap27 → 1.0.0.lap28

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +6 -2
  4. data/Gemfile +0 -1
  5. data/Rakefile +2 -2
  6. data/hyper-mesh.gemspec +1 -1
  7. data/lib/active_record_base.rb +39 -27
  8. data/lib/hyper-mesh.rb +6 -1
  9. data/lib/hypermesh/version.rb +1 -1
  10. data/lib/object/tap.rb +7 -0
  11. data/lib/reactive_record/active_record/associations.rb +14 -3
  12. data/lib/reactive_record/active_record/base.rb +1 -2
  13. data/lib/reactive_record/active_record/class_methods.rb +120 -67
  14. data/lib/reactive_record/active_record/error.rb +17 -12
  15. data/lib/reactive_record/active_record/errors.rb +374 -0
  16. data/lib/reactive_record/active_record/instance_methods.rb +58 -67
  17. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +1 -4
  18. data/lib/reactive_record/active_record/reactive_record/base.rb +129 -234
  19. data/lib/reactive_record/active_record/reactive_record/collection.rb +51 -18
  20. data/lib/reactive_record/active_record/reactive_record/column_types.rb +5 -3
  21. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +6 -4
  22. data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
  23. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +99 -87
  24. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
  25. data/lib/reactive_record/active_record/reactive_record/operations.rb +2 -1
  26. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +1 -1
  27. data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
  28. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +4 -5
  29. data/lib/reactive_record/active_record_error.rb +4 -0
  30. data/lib/reactive_record/broadcast.rb +55 -18
  31. data/lib/reactive_record/permissions.rb +5 -4
  32. data/lib/reactive_record/scope_description.rb +14 -6
  33. data/lib/reactive_record/server_data_cache.rb +119 -70
  34. metadata +16 -13
  35. data/lib/reactive_record/active_record/reactive_record/reactive_set_relationship_helpers.rb +0 -189
@@ -1,26 +1,31 @@
1
1
  module ActiveModel
2
-
3
2
  class Error
4
-
3
+
5
4
  attr_reader :messages
6
-
7
- def initialize(msgs = {})
8
- @messages = msgs || {}
9
- @messages.each { |attribute, messages| @messages[attribute] = messages.uniq }
5
+
6
+ def initialize(initial_msgs = {})
7
+ @messages = Hash.new { |h, k| h[k] = [] }
8
+ initial_msgs.each { |attr, msgs| @messages[attr] = msgs.uniq }
10
9
  end
11
-
10
+
12
11
  def [](attribute)
13
12
  messages[attribute]
14
13
  end
15
-
14
+
16
15
  def delete(attribute)
17
16
  messages.delete(attribute)
18
17
  end
19
-
18
+
20
19
  def empty?
21
20
  messages.empty?
22
21
  end
23
-
22
+
23
+ def clear
24
+ @messages.clear
25
+ end
26
+
27
+ def add(attribute, message:)
28
+ @messages[attribute] << message unless @messages[attribute].include? message
29
+ end
24
30
  end
25
-
26
- end
31
+ end
@@ -0,0 +1,374 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module ActiveModel
4
+ class Errors
5
+ include Enumerable
6
+
7
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
8
+ MESSAGE_OPTIONS = [:message]
9
+
10
+ attr_reader :messages, :details
11
+
12
+ # Pass in the instance of the object that is using the errors object.
13
+ #
14
+ # class Person
15
+ # def initialize
16
+ # @errors = ActiveModel::Errors.new(self)
17
+ # end
18
+ # end
19
+ def initialize(base = {})
20
+ @base = base
21
+ @messages = apply_default_array({})
22
+ @details = apply_default_array({})
23
+ reactive_empty! true
24
+ end
25
+
26
+ # When passed a symbol or a name of a method, returns an array of errors
27
+ # for the method.
28
+ #
29
+ # person.errors[:name] # => ["cannot be nil"]
30
+ # person.errors['name'] # => ["cannot be nil"]
31
+ def [](attribute)
32
+ messages[attribute]
33
+ end
34
+
35
+ # Iterates through each error key, value pair in the error messages hash.
36
+ # Yields the attribute and the error for that attribute. If the attribute
37
+ # has more than one error message, yields once for each error message.
38
+ #
39
+ # person.errors.add(:name, :blank, message: "can't be blank")
40
+ # person.errors.each do |attribute, error|
41
+ # # Will yield :name and "can't be blank"
42
+ # end
43
+ #
44
+ # person.errors.add(:name, :not_specified, message: "must be specified")
45
+ # person.errors.each do |attribute, error|
46
+ # # Will yield :name and "can't be blank"
47
+ # # then yield :name and "must be specified"
48
+ # end
49
+ def each
50
+ messages.each_key do |attribute|
51
+ messages[attribute].each { |error| yield attribute, error }
52
+ end
53
+ end
54
+
55
+ # Delete messages for +key+. Returns the deleted messages.
56
+ #
57
+ # person.errors[:name] # => ["cannot be nil"]
58
+ # person.errors.delete(:name) # => ["cannot be nil"]
59
+ # person.errors[:name] # => []
60
+ def delete(attribute)
61
+ details.delete(attribute)
62
+ messages.delete(attribute)
63
+ end
64
+
65
+ # Returns the number of error messages.
66
+ #
67
+ # person.errors.add(:name, :blank, message: "can't be blank")
68
+ # person.errors.size # => 1
69
+ # person.errors.add(:name, :not_specified, message: "must be specified")
70
+ # person.errors.size # => 2
71
+ def size
72
+ values.flatten.size
73
+ end
74
+ alias :count :size
75
+
76
+ # Returns all message keys.
77
+ #
78
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
79
+ # person.errors.keys # => [:name]
80
+ def keys
81
+ messages.select do |key, value|
82
+ !value.empty?
83
+ end.keys
84
+ end
85
+
86
+ # Returns all message values.
87
+ #
88
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
89
+ # person.errors.values # => [["cannot be nil", "must be specified"]]
90
+ def values
91
+ messages.select do |key, value|
92
+ !value.empty?
93
+ end.values
94
+ end
95
+
96
+ # NOTE: Doesn't use i18n
97
+ #
98
+ # Returns a full message for a given attribute.
99
+ #
100
+ # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
101
+ def full_message(attribute, message)
102
+ return message if attribute == :base
103
+ # TODO: When opal_activesupport 0.3.2 is released, use `humanize`
104
+ # attr_name = attribute.to_s.tr('.', '_').humanize
105
+ attr_name =
106
+ attribute.to_s.tr('.', '_').tr('_', ' ').gsub(/_id$/, '').capitalize
107
+ # attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
108
+ # if @base.class.respond_to?(:human_attribute_name)
109
+ attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
110
+ # end
111
+ # I18n.t(:"errors.format",
112
+ # default: "%{attribute} %{message}",
113
+ # attribute: attr_name,
114
+ # message: message)
115
+ "#{attr_name} #{message}"
116
+ end
117
+
118
+ # Returns all the full error messages in an array.
119
+ #
120
+ # class Person
121
+ # validates_presence_of :name, :address, :email
122
+ # validates_length_of :name, in: 5..30
123
+ # end
124
+ #
125
+ # person = Person.create(address: '123 First St.')
126
+ # person.errors.full_messages
127
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
128
+ def full_messages
129
+ map { |attribute, message| full_message(attribute, message) }
130
+ end
131
+ alias :to_a :full_messages
132
+
133
+ # Returns all the full error messages for a given attribute in an array.
134
+ #
135
+ # class Person
136
+ # validates_presence_of :name, :email
137
+ # validates_length_of :name, in: 5..30
138
+ # end
139
+ #
140
+ # person = Person.create()
141
+ # person.errors.full_messages_for(:name)
142
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
143
+ def full_messages_for(attribute)
144
+ messages[attribute].map { |message| full_message(attribute, message) }
145
+ end
146
+
147
+ # Returns a Hash of attributes with their error messages. If +full_messages+
148
+ # is +true+, it will contain full messages (see +full_message+).
149
+ #
150
+ # person.errors.to_hash # => {:name=>["cannot be nil"]}
151
+ # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
152
+ def to_hash(full_messages = false)
153
+ if full_messages
154
+ messages.each_with_object({}) do |(attribute, array), messages|
155
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
156
+ end
157
+ else
158
+ without_default_proc(messages)
159
+ end
160
+ end
161
+
162
+ # Returns a Hash that can be used as the JSON representation for this
163
+ # object. You can pass the <tt>:full_messages</tt> option. This determines
164
+ # if the json object should contain full messages or not (false by default).
165
+ #
166
+ # person.errors.as_json # => {:name=>["cannot be nil"]}
167
+ # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
168
+ def as_json(options = nil)
169
+ to_hash(options && options[:full_messages])
170
+ end
171
+
172
+ # NOTE: Doesn't actually do any of the below i18n lookups
173
+ #
174
+ # Translates an error message in its default scope
175
+ # (<tt>activemodel.errors.messages</tt>).
176
+ #
177
+ # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
178
+ # if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
179
+ # that is not there also, it returns the translation of the default message
180
+ # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
181
+ # name, translated attribute name and the value are available for
182
+ # interpolation.
183
+ #
184
+ # When using inheritance in your models, it will check all the inherited
185
+ # models too, but only if the model itself hasn't been found. Say you have
186
+ # <tt>class Admin < User; end</tt> and you wanted the translation for
187
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
188
+ # it looks for these translations:
189
+ #
190
+ # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
191
+ # * <tt>activemodel.errors.models.admin.blank</tt>
192
+ # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
193
+ # * <tt>activemodel.errors.models.user.blank</tt>
194
+ # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
195
+ # * <tt>activemodel.errors.messages.blank</tt>
196
+ # * <tt>errors.attributes.title.blank</tt>
197
+ # * <tt>errors.messages.blank</tt>
198
+ def generate_message(attribute, type = :invalid, options = {})
199
+ options.delete(:message) || type
200
+ end
201
+
202
+ # Returns +true+ if no errors are found, +false+ otherwise.
203
+ # If the error message is a string it can be empty.
204
+ #
205
+ # person.errors.full_messages # => ["name cannot be nil"]
206
+ # person.errors.empty? # => false
207
+ def empty?
208
+ size.zero?
209
+ end
210
+ alias :blank? :empty?
211
+
212
+ def reactive_empty?
213
+ React::State.get_state(self, 'ERRORS?')
214
+ end
215
+
216
+ # Clear the error messages.
217
+ #
218
+ # person.errors.full_messages # => ["name cannot be nil"]
219
+ # person.errors.clear
220
+ # person.errors.full_messages # => []
221
+ def clear
222
+ messages.clear
223
+ details.clear.tap { reactive_empty! true }
224
+ end
225
+
226
+ # Merges the errors from <tt>other</tt>.
227
+ #
228
+ # other - The ActiveModel::Errors instance.
229
+ #
230
+ # Examples
231
+ #
232
+ # person.errors.merge!(other)
233
+ def merge!(other)
234
+ @messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 }
235
+ @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }.tap { reactive_empty! }
236
+ end
237
+
238
+ # Returns +true+ if the error messages include an error for the given key
239
+ # +attribute+, +false+ otherwise.
240
+ #
241
+ # person.errors.messages # => {:name=>["cannot be nil"]}
242
+ # person.errors.include?(:name) # => true
243
+ # person.errors.include?(:age) # => false
244
+ def include?(attribute)
245
+ attribute = attribute.to_sym
246
+ messages.key?(attribute) && messages[attribute].present?
247
+ end
248
+ alias :has_key? :include?
249
+ alias :key? :include?
250
+
251
+ # NOTE: strict option isn't ported yet
252
+ #
253
+ # Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
254
+ # More than one error can be added to the same +attribute+.
255
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
256
+ #
257
+ # person.errors.add(:name)
258
+ # # => ["is invalid"]
259
+ # person.errors.add(:name, :not_implemented, message: "must be implemented")
260
+ # # => ["is invalid", "must be implemented"]
261
+ #
262
+ # person.errors.messages
263
+ # # => {:name=>["is invalid", "must be implemented"]}
264
+ #
265
+ # person.errors.details
266
+ # # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
267
+ #
268
+ # If +message+ is a symbol, it will be translated using the appropriate
269
+ # scope (see +generate_message+).
270
+ #
271
+ # If +message+ is a proc, it will be called, allowing for things like
272
+ # <tt>Time.now</tt> to be used within an error.
273
+ #
274
+ # If the <tt>:strict</tt> option is set to +true+, it will raise
275
+ # ActiveModel::StrictValidationFailed instead of adding the error.
276
+ # <tt>:strict</tt> option can also be set to any other exception.
277
+ #
278
+ # person.errors.add(:name, :invalid, strict: true)
279
+ # # => ActiveModel::StrictValidationFailed: Name is invalid
280
+ # person.errors.add(:name, :invalid, strict: NameIsInvalid)
281
+ # # => NameIsInvalid: Name is invalid
282
+ #
283
+ # person.errors.messages # => {}
284
+ #
285
+ # +attribute+ should be set to <tt>:base</tt> if the error is not
286
+ # directly associated with a single attribute.
287
+ #
288
+ # person.errors.add(:base, :name_or_email_blank,
289
+ # message: "either name or email must be present")
290
+ # person.errors.messages
291
+ # # => {:base=>["either name or email must be present"]}
292
+ # person.errors.details
293
+ # # => {:base=>[{error: :name_or_email_blank}]}
294
+ def add(attribute, message = :invalid, options = {})
295
+ message = message.call if message.respond_to?(:call)
296
+ detail = normalize_detail(message, options)
297
+ message = normalize_message(attribute, message, options)
298
+ # if exception = options[:strict]
299
+ # exception = ActiveModel::StrictValidationFailed if exception == true
300
+ # raise exception, full_message(attribute, message)
301
+ # end
302
+ details[attribute.to_sym] << detail
303
+ (messages[attribute.to_sym] << message).tap { reactive_empty! false }
304
+ end
305
+
306
+ # NOTE: Due to Opal not supporting Symbol this isn't identical,
307
+ # but probably still works fine in most cases.
308
+ #
309
+ # Returns +true+ if an error on the attribute with the given message is
310
+ # present, or +false+ otherwise. +message+ is treated the same as for +add+.
311
+ #
312
+ # person.errors.add :name, :blank
313
+ # person.errors.added? :name, :blank # => true
314
+ # person.errors.added? :name, "can't be blank" # => true
315
+ #
316
+ # If the error message requires an option, then it returns +true+ with
317
+ # the correct option, or +false+ with an incorrect or missing option.
318
+ #
319
+ # person.errors.add :name, :too_long, { count: 25 }
320
+ # person.errors.added? :name, :too_long, count: 25 # => true
321
+ # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
322
+ # person.errors.added? :name, :too_long, count: 24 # => false
323
+ # person.errors.added? :name, :too_long # => false
324
+ # person.errors.added? :name, "is too long" # => false
325
+ def added?(attribute, message = :invalid, options = {})
326
+ # if message.is_a? Symbol
327
+ # self.details[attribute].map { |e| e[:error] }.include? message
328
+ # else
329
+ # message = message.call if message.respond_to?(:call)
330
+ # message = normalize_message(attribute, message, options)
331
+ # self[attribute].include? message
332
+ # end
333
+ return true if details[attribute].map { |e| e[:error] }.include? message
334
+ message = message.call if message.respond_to?(:call)
335
+ message = normalize_message(attribute, message, options)
336
+ self[attribute].include? message
337
+ end
338
+
339
+ private
340
+
341
+ def apply_default_array(hash)
342
+ hash.default_proc = proc { |h, key| h[key] = [] }
343
+ hash
344
+ end
345
+
346
+ def normalize_message(attribute, message, options)
347
+ # case message
348
+ # when Symbol
349
+ # generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
350
+ # else
351
+ # message
352
+ # end
353
+ generate_message(
354
+ attribute, message, options.reject { |k, _| CALLBACKS_OPTIONS.include?(k) }
355
+ )
356
+ end
357
+
358
+ def normalize_detail(message, options)
359
+ # { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
360
+ ignore = (CALLBACKS_OPTIONS + MESSAGE_OPTIONS)
361
+ { error: message }.merge(options.reject { |k, _| ignore.include?(k) })
362
+ end
363
+
364
+ def without_default_proc(hash)
365
+ hash.dup.tap do |new_h|
366
+ new_h.default_proc = nil
367
+ end
368
+ end
369
+
370
+ def reactive_empty!(state = empty?)
371
+ React::State.set_state(self, 'ERRORS?', state) unless ReactiveRecord::Base.data_loading?
372
+ end
373
+ end
374
+ end
@@ -1,7 +1,5 @@
1
1
  module ActiveRecord
2
-
3
2
  module InstanceMethods
4
-
5
3
  def inspect
6
4
  "<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\
7
5
  "(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\
@@ -14,25 +12,36 @@ module ActiveRecord
14
12
  @backing_record.attributes
15
13
  end
16
14
 
17
- def initialize(hash = {})
15
+ def changed_attributes
16
+ backing_record.changed_attributes_and_values
17
+ end
18
+
19
+ def changes
20
+ backing_record.changes
21
+ end
18
22
 
23
+ def initialize(hash = {})
19
24
  if hash.is_a? ReactiveRecord::Base
20
25
  @backing_record = hash
21
26
  else
22
27
  # standard active_record new -> creates a new instance, primary key is ignored if present
23
28
  # we have to build the backing record first then initialize it so associations work correctly
24
29
  @backing_record = ReactiveRecord::Base.new(self.class, {}, self)
30
+ if self.class.inheritance_column && !hash.key?(self.class.inheritance_column)
31
+ hash[self.class.inheritance_column] = self.class.name
32
+ end
25
33
  @backing_record.instance_eval do
26
- h = Hash.new
27
- hash.each { |a, v| h[a] = convert(a, v).itself }
34
+ h = {}
35
+ hash.each do |a, v|
36
+ a = model._dealias_attribute(a)
37
+ h[a] = convert(a, v).itself
38
+ end
28
39
  self.class.load_data do
29
40
  h.each do |attribute, value|
30
- unless attribute == primary_key
31
- reactive_set!(attribute, value)
32
- changed_attributes << attribute
33
- end
41
+ next if attribute == primary_key
42
+ @ar_instance[attribute] = value
43
+ changed_attributes << attribute
34
44
  end
35
- #changed_attributes << primary_key # insures that changed attributes has at least one element
36
45
  end
37
46
  end
38
47
  end
@@ -42,22 +51,18 @@ module ActiveRecord
42
51
  self.class.primary_key
43
52
  end
44
53
 
45
- def type
46
- @backing_record.reactive_get!(:type, nil)
47
- end
48
-
49
- def type=(val)
50
- @backing_record.reactive_set!(:type, backing_record.convert(:type, val))
51
- end
52
-
53
54
  def id
54
- @backing_record.reactive_get!(primary_key)
55
+ @backing_record.get_primary_key_value
55
56
  end
56
57
 
57
58
  def id=(value)
58
59
  @backing_record.id = value
59
60
  end
60
61
 
62
+ def id?
63
+ id.present?
64
+ end
65
+
61
66
  def model_name
62
67
  # in reality should return ActiveModel::Name object, blah blah
63
68
  self.class.model_name
@@ -87,61 +92,22 @@ module ActiveRecord
87
92
  send("#{attr}=", val)
88
93
  end
89
94
 
90
- def method_missing_warning(name)
91
- @backing_record.deprecation_warning("Server side method #{name} must be defined using the 'server_method' macro.")
92
- end
93
-
94
- def method_missing(name, *args, &block)
95
- original_name = name
96
-
97
- if name.end_with?('!')
98
- name = name.chop # remove '!'
99
- force_update = true
100
- end
101
-
102
- chopped_name = name.end_with?('=') ? name.chop : name
103
- is_server_method = self.class.server_methods.has_key?(chopped_name)
104
- chopped_name = chopped_name.end_with?('?') ? chopped_name.chop : name
105
- is_attribute = attributes.has_key?(chopped_name)
106
-
107
- unless is_server_method || is_attribute
108
- if ReactiveRecord::Base.public_columns_hash.has_key?(self.class.name) && ReactiveRecord::Base.public_columns_hash[self.class.name].has_key?(chopped_name)
109
- is_attribute = true
110
- end
111
- method_missing_warning("#{original_name}(#{args})") unless is_attribute
112
- end
113
-
114
- if name.end_with?('_changed?')
115
- @backing_record.changed?(name[0...-9]) # remove '_changed?'
116
- elsif args.count == 1 && name.end_with?('=') && !block
117
- attribute_name = name.chop # remove '='
118
- # for rails auto generated methods for booleans, remove '?' to get the attribute
119
- attribute_name = attribute_name.chop if !is_server_method && is_attribute && attribute_name.end_with?('?')
120
- @backing_record.reactive_set!(attribute_name, backing_record.convert(attribute_name, args[0]))
121
- elsif args.count.zero? && !block
122
- # for rails auto generated methods for booleans, remove '?' to get the attribute
123
- name = name.chop if !is_server_method && is_attribute && name.end_with?('?')
124
- @backing_record.reactive_get!(name, force_update)
125
- elsif !block
126
- # for rails auto generated methods for booleans, remove '?' to get the attribute
127
- name = name.chop if !is_server_method && is_attribute && name.end_with?('?')
128
- @backing_record.reactive_get!([[name]+args], force_update)
129
- else
130
- super
131
- end
132
- end
133
-
134
95
  def itself
135
96
  # this is useful when you just want to get a handle on record instance
136
97
  # in the ReactiveRecord.load method
137
98
  id # force load of id...
138
- self
99
+ # if self.class.columns_hash.keys.include?(self.class.inheritance_column) &&
100
+ # (klass = self[self.class.inheritance_column]).loaded?
101
+ # Object.const_get(klass).new(attributes)
102
+ # else
103
+ self
104
+ # end
139
105
  end
140
106
 
141
107
  def load(*attributes, &block)
142
108
  first_time = true
143
109
  ReactiveRecord.load do
144
- results = attributes.collect { |attr| @backing_record.reactive_get!(attr, first_time) }
110
+ results = attributes.collect { |attr| send("#{attr}#{'!' if first_time}") }
145
111
  results = yield(*results) if block
146
112
  first_time = false
147
113
  block.nil? && results.count == 1 ? results.first : results
@@ -149,7 +115,21 @@ module ActiveRecord
149
115
  end
150
116
 
151
117
  def save(opts = {}, &block)
152
- @backing_record.save(opts.has_key?(:validate) ? opts[:validate] : true, opts[:force], &block)
118
+ @backing_record.save_or_validate(true, opts.has_key?(:validate) ? opts[:validate] : true, opts[:force], &block)
119
+ end
120
+
121
+ def validate(opts = {}, &block)
122
+ @backing_record.save_or_validate(false, true, opts[:force]).then do
123
+ if block
124
+ yield @backing_record.ar_instance
125
+ else
126
+ @backing_record.ar_instance
127
+ end
128
+ end
129
+ end
130
+
131
+ def valid?
132
+ errors.reactive_empty?
153
133
  end
154
134
 
155
135
  def saving?
@@ -191,6 +171,17 @@ module ActiveRecord
191
171
  id.to_i <=> other.id.to_i
192
172
  end
193
173
 
194
- end
174
+ def becomes(klass)
175
+ klass._new_without_sti_type_cast(backing_record)
176
+ end
177
+
178
+ def becomes!(klass)
179
+ self[self.class.inheritance_column] = klass.name
180
+ becomes(klass)
181
+ end
195
182
 
183
+ def cast_to_current_sti_type
184
+ @backing_record.set_ar_instance!
185
+ end
186
+ end
196
187
  end