state_machines-activerecord 0.9.0 → 0.100.0
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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +16 -3
- data/lib/state_machines/integrations/active_record/locale.rb +12 -9
- data/lib/state_machines/integrations/active_record/version.rb +3 -1
- data/lib/state_machines/integrations/active_record.rb +72 -109
- data/lib/state_machines-activerecord.rb +2 -0
- metadata +36 -142
- data/test/files/en.yml +0 -5
- data/test/files/models/post.rb +0 -11
- data/test/integration_test.rb +0 -25
- data/test/machine_by_default_test.rb +0 -16
- data/test/machine_errors_test.rb +0 -19
- data/test/machine_multiple_test.rb +0 -17
- data/test/machine_nested_action_test.rb +0 -38
- data/test/machine_unmigrated_test.rb +0 -14
- data/test/machine_with_aliased_attribute_test.rb +0 -23
- data/test/machine_with_callbacks_test.rb +0 -172
- data/test/machine_with_column_state_attribute_test.rb +0 -44
- data/test/machine_with_complex_pluralization_scopes_test.rb +0 -16
- data/test/machine_with_conflicting_predicate_test.rb +0 -18
- data/test/machine_with_conflicting_state_name_test.rb +0 -29
- data/test/machine_with_custom_attribute_test.rb +0 -21
- data/test/machine_with_default_scope_test.rb +0 -18
- data/test/machine_with_different_column_default_test.rb +0 -27
- data/test/machine_with_different_integer_column_default_test.rb +0 -29
- data/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb +0 -24
- data/test/machine_with_dirty_attribute_and_state_events_test.rb +0 -20
- data/test/machine_with_dirty_attributes_and_custom_attribute_test.rb +0 -32
- data/test/machine_with_dirty_attributes_during_loopback_test.rb +0 -22
- data/test/machine_with_dirty_attributes_test.rb +0 -35
- data/test/machine_with_dynamic_initial_state_test.rb +0 -99
- data/test/machine_with_event_attributes_on_autosave_test.rb +0 -48
- data/test/machine_with_event_attributes_on_custom_action_test.rb +0 -41
- data/test/machine_with_event_attributes_on_save_bang_test.rb +0 -82
- data/test/machine_with_event_attributes_on_save_test.rb +0 -244
- data/test/machine_with_event_attributes_on_validation_test.rb +0 -143
- data/test/machine_with_events_test.rb +0 -13
- data/test/machine_with_failed_action_test.rb +0 -40
- data/test/machine_with_failed_after_callbacks_test.rb +0 -35
- data/test/machine_with_failed_before_callbacks_test.rb +0 -36
- data/test/machine_with_initialized_state_test.rb +0 -41
- data/test/machine_with_internationalization_test.rb +0 -180
- data/test/machine_with_loopback_test.rb +0 -22
- data/test/machine_with_non_column_state_attribute_defined_test.rb +0 -29
- data/test/machine_with_same_column_default_test.rb +0 -26
- data/test/machine_with_same_integer_column_default_test.rb +0 -30
- data/test/machine_with_scopes_and_joins_test.rb +0 -38
- data/test/machine_with_scopes_and_owner_subclass_test.rb +0 -27
- data/test/machine_with_scopes_test.rb +0 -70
- data/test/machine_with_state_driven_validations_test.rb +0 -30
- data/test/machine_with_states_test.rb +0 -13
- data/test/machine_with_static_initial_state_test.rb +0 -167
- data/test/machine_with_transactions_test.rb +0 -26
- data/test/machine_with_validations_and_custom_attribute_test.rb +0 -21
- data/test/machine_with_validations_test.rb +0 -47
- data/test/machine_without_database_test.rb +0 -20
- data/test/machine_without_transactions_test.rb +0 -26
- data/test/model_test.rb +0 -12
- data/test/test_helper.rb +0 -58
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8da92c0d0872bf79ae81017dd5d16459e5222d10ee304390ddff3af14f1e1e05
|
|
4
|
+
data.tar.gz: c7ebd9fbdff4ee5bd2b0cca81b5daad186bb92157eddcb912829685571aa84eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66d176f9efbc9d44ac2d5a5786f078e593d3590a99d855804ff4775649b18abae64264781f4ddc36a60ac253366aea0a9c02d3d1712d680d78fe443fffd437e7
|
|
7
|
+
data.tar.gz: f5e7a924679499d596439009b9bad2633af5389069959016595d31c44243f7551893cd2e53769e5e1b3a99b979f8d6ad1fa24d86caaa9134f0aa63528739b108
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
[](https://codeclimate.com/github/state-machines/state_machines-activerecord)
|
|
1
|
+
[](https://github.com/state-machines/state_machines-activerecord/actions/workflows/ruby.yml)
|
|
3
2
|
|
|
4
3
|
# StateMachines Active Record Integration
|
|
5
4
|
|
|
6
|
-
The Active Record
|
|
5
|
+
The Active Record 7.2+ integration adds support for database transactions, automatically
|
|
7
6
|
saving the record, named scopes, validation errors.
|
|
8
7
|
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- Ruby 3.2+
|
|
11
|
+
- Rails 7.2+
|
|
12
|
+
|
|
9
13
|
## Installation
|
|
10
14
|
|
|
11
15
|
Add this line to your application's Gemfile:
|
|
@@ -64,6 +68,15 @@ Vehicle.with_state(:parked) # also plural #with_states
|
|
|
64
68
|
Vehicle.without_states(:first_gear, :second_gear) # also singular #without_state
|
|
65
69
|
```
|
|
66
70
|
|
|
71
|
+
#### Transparent Scopes
|
|
72
|
+
State scopes will return all records when `nil` is passed, making them perfect for search filters:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
Vehicle.with_state(nil) # Returns all vehicles
|
|
76
|
+
Vehicle.with_state(params[:state]) # Returns all vehicles if params[:state] is nil
|
|
77
|
+
Vehicle.where(color: 'red').with_state(nil) # Returns all red vehicles (chainable)
|
|
78
|
+
```
|
|
79
|
+
|
|
67
80
|
### State driven validations
|
|
68
81
|
|
|
69
82
|
As mentioned in `StateMachines::Machine#state`, you can define behaviors,
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Use lazy evaluation to avoid circular dependencies with frozen default_messages
|
|
4
|
+
# This ensures messages can be updated after gem loading while maintaining thread safety
|
|
1
5
|
{ en: {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
6
|
+
activerecord: {
|
|
7
|
+
errors: {
|
|
8
|
+
messages: {
|
|
9
|
+
invalid: ->(*) { StateMachines::Machine.default_messages[:invalid] },
|
|
10
|
+
invalid_event: ->(*) { format(StateMachines::Machine.default_messages[:invalid_event], '%<state>s') },
|
|
11
|
+
invalid_transition: ->(*) { format(StateMachines::Machine.default_messages[:invalid_transition], '%<event>s') }
|
|
12
|
+
}
|
|
10
13
|
}
|
|
14
|
+
}
|
|
11
15
|
} }
|
|
12
|
-
|
|
@@ -3,7 +3,7 @@ require 'active_record'
|
|
|
3
3
|
require 'state_machines/integrations/active_record/version'
|
|
4
4
|
|
|
5
5
|
module StateMachines
|
|
6
|
-
module Integrations
|
|
6
|
+
module Integrations # :nodoc:
|
|
7
7
|
# Adds support for integrating state machines with ActiveRecord models.
|
|
8
8
|
#
|
|
9
9
|
# == Examples
|
|
@@ -78,16 +78,14 @@ module StateMachines
|
|
|
78
78
|
# === Security implications
|
|
79
79
|
#
|
|
80
80
|
# Beware that public event attributes mean that events can be fired
|
|
81
|
-
# whenever mass-assignment is being used.
|
|
82
|
-
# users from tampering with events through URLs / forms,
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# class
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
# state_machine do
|
|
90
|
-
# ...
|
|
81
|
+
# whenever mass-assignment is being used. If you want to prevent malicious
|
|
82
|
+
# users from tampering with events through URLs / forms, you should use
|
|
83
|
+
# Rails' strong parameters to control which attributes are permitted:
|
|
84
|
+
#
|
|
85
|
+
# class VehiclesController < ApplicationController
|
|
86
|
+
# def vehicle_params
|
|
87
|
+
# params.require(:vehicle).permit(:color, :make, :model)
|
|
88
|
+
# # Exclude state_event to prevent tampering
|
|
91
89
|
# end
|
|
92
90
|
# end
|
|
93
91
|
#
|
|
@@ -95,8 +93,7 @@ module StateMachines
|
|
|
95
93
|
# you can build two state machines (one public and one protected) like so:
|
|
96
94
|
#
|
|
97
95
|
# class Vehicle < ApplicationRecord
|
|
98
|
-
#
|
|
99
|
-
#
|
|
96
|
+
# # Define private machine
|
|
100
97
|
# state_machine do
|
|
101
98
|
# # Define private events here
|
|
102
99
|
# end
|
|
@@ -105,6 +102,8 @@ module StateMachines
|
|
|
105
102
|
# state_machine :public_state, :attribute => :state do
|
|
106
103
|
# # Define public events here
|
|
107
104
|
# end
|
|
105
|
+
#
|
|
106
|
+
# # Control access via strong parameters in your controller
|
|
108
107
|
# end
|
|
109
108
|
#
|
|
110
109
|
# == Transactions
|
|
@@ -199,33 +198,43 @@ module StateMachines
|
|
|
199
198
|
#
|
|
200
199
|
# == Scopes
|
|
201
200
|
#
|
|
202
|
-
# To assist in filtering models with specific states, a series of
|
|
203
|
-
#
|
|
201
|
+
# To assist in filtering models with specific states, a series of scopes
|
|
202
|
+
# are defined on the model for finding records with or without a
|
|
204
203
|
# particular set of states.
|
|
205
204
|
#
|
|
206
|
-
# These
|
|
205
|
+
# These scopes are essentially the functional equivalent of the
|
|
207
206
|
# following definitions:
|
|
208
207
|
#
|
|
209
208
|
# class Vehicle < ApplicationRecord
|
|
210
209
|
# # with_states also aliased to with_state
|
|
210
|
+
# scope :with_states, ->(states) { states.present? ? where(state: states) : all }
|
|
211
211
|
#
|
|
212
|
-
# named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
|
|
213
212
|
# # without_states also aliased to without_state
|
|
213
|
+
# scope :without_states, ->(states) { states.present? ? where.not(state: states) : all }
|
|
214
214
|
# end
|
|
215
215
|
#
|
|
216
216
|
# *Note*, however, that the states are converted to their stored values
|
|
217
217
|
# before being passed into the query.
|
|
218
218
|
#
|
|
219
|
-
# Because of the way
|
|
219
|
+
# Because of the way scopes work in ActiveRecord, they can be
|
|
220
220
|
# chained like so:
|
|
221
221
|
#
|
|
222
|
-
# Vehicle.with_state(:parked).
|
|
222
|
+
# Vehicle.with_state(:parked).order(id: :desc)
|
|
223
223
|
#
|
|
224
224
|
# Note that states can also be referenced by the string version of their
|
|
225
225
|
# name:
|
|
226
226
|
#
|
|
227
227
|
# Vehicle.with_state('parked')
|
|
228
228
|
#
|
|
229
|
+
# === Transparent Scopes
|
|
230
|
+
#
|
|
231
|
+
# When `nil` is passed to any of the state scopes, they return `all` records
|
|
232
|
+
# without applying any filters. This allows for more flexible scope chaining
|
|
233
|
+
# in search interfaces:
|
|
234
|
+
#
|
|
235
|
+
# Vehicle.with_state(params[:state]) # Returns all vehicles if params[:state] is nil
|
|
236
|
+
# Vehicle.where(color: 'red').with_state(nil) # Returns all red vehicles
|
|
237
|
+
#
|
|
229
238
|
# == Callbacks
|
|
230
239
|
#
|
|
231
240
|
# All before/after transition callbacks defined for ActiveRecord models
|
|
@@ -266,7 +275,7 @@ module StateMachines
|
|
|
266
275
|
# your callback to roll back. You can work around this issue like so:
|
|
267
276
|
#
|
|
268
277
|
# class TransitionLog < ApplicationRecord
|
|
269
|
-
#
|
|
278
|
+
# connects_to database: { writing: :primary, reading: :primary }
|
|
270
279
|
# end
|
|
271
280
|
#
|
|
272
281
|
# class Vehicle < ApplicationRecord
|
|
@@ -279,7 +288,7 @@ module StateMachines
|
|
|
279
288
|
# end
|
|
280
289
|
# end
|
|
281
290
|
#
|
|
282
|
-
# The +TransitionLog+ model establishes a
|
|
291
|
+
# The +TransitionLog+ model establishes a separate connection to the database
|
|
283
292
|
# that allows new records to be saved without being affected by rollbacks
|
|
284
293
|
# in the +Vehicle+ model's transaction.
|
|
285
294
|
#
|
|
@@ -304,65 +313,9 @@ module StateMachines
|
|
|
304
313
|
# * (-) end transaction (if enabled)
|
|
305
314
|
# * (9) after_commit
|
|
306
315
|
#
|
|
307
|
-
# == Observers
|
|
308
|
-
#
|
|
309
|
-
# In addition to support for ActiveRecord-like hooks, there is additional
|
|
310
|
-
# support for ActiveRecord observers. Because of the way ActiveRecord
|
|
311
|
-
# observers are designed, there is less flexibility around the specific
|
|
312
|
-
# transitions that can be hooked in. However, a large number of hooks
|
|
313
|
-
# *are* supported. For example, if a transition for a record's +state+
|
|
314
|
-
# attribute changes the state from +parked+ to +idling+ via the +ignite+
|
|
315
|
-
# event, the following observer methods are supported:
|
|
316
|
-
# * before/after/after_failure_to-_ignite_from_parked_to_idling
|
|
317
|
-
# * before/after/after_failure_to-_ignite_from_parked
|
|
318
|
-
# * before/after/after_failure_to-_ignite_to_idling
|
|
319
|
-
# * before/after/after_failure_to-_ignite
|
|
320
|
-
# * before/after/after_failure_to-_transition_state_from_parked_to_idling
|
|
321
|
-
# * before/after/after_failure_to-_transition_state_from_parked
|
|
322
|
-
# * before/after/after_failure_to-_transition_state_to_idling
|
|
323
|
-
# * before/after/after_failure_to-_transition_state
|
|
324
|
-
# * before/after/after_failure_to-_transition
|
|
325
|
-
#
|
|
326
|
-
# The following class shows an example of some of these hooks:
|
|
327
|
-
#
|
|
328
|
-
# class VehicleObserver < ActiveRecord::Observer
|
|
329
|
-
# def before_save(vehicle)
|
|
330
|
-
# # log message
|
|
331
|
-
# end
|
|
332
|
-
#
|
|
333
|
-
# # Callback for :ignite event *before* the transition is performed
|
|
334
|
-
# def before_ignite(vehicle, transition)
|
|
335
|
-
# # log message
|
|
336
|
-
# end
|
|
337
|
-
#
|
|
338
|
-
# # Callback for :ignite event *after* the transition has been performed
|
|
339
|
-
# def after_ignite(vehicle, transition)
|
|
340
|
-
# # put on seatbelt
|
|
341
|
-
# end
|
|
342
|
-
#
|
|
343
|
-
# # Generic transition callback *before* the transition is performed
|
|
344
|
-
# def after_transition(vehicle, transition)
|
|
345
|
-
# Audit.log(vehicle, transition)
|
|
346
|
-
# end
|
|
347
|
-
# end
|
|
348
|
-
#
|
|
349
|
-
# More flexible transition callbacks can be defined directly within the
|
|
350
|
-
# model as described in StateMachines::Machine#before_transition
|
|
351
|
-
# and StateMachines::Machine#after_transition.
|
|
352
|
-
#
|
|
353
|
-
# To define a single observer for multiple state machines:
|
|
354
|
-
#
|
|
355
|
-
# class StateMachineObserver < ActiveRecord::Observer
|
|
356
|
-
# observe Vehicle, Switch, Project
|
|
357
|
-
#
|
|
358
|
-
# def after_transition(record, transition)
|
|
359
|
-
# Audit.log(record, transition)
|
|
360
|
-
# end
|
|
361
|
-
# end
|
|
362
|
-
#
|
|
363
316
|
# == Internationalization
|
|
364
317
|
#
|
|
365
|
-
#
|
|
318
|
+
# Any error message that is generated from performing invalid
|
|
366
319
|
# transitions can be localized. The following default translations are used:
|
|
367
320
|
#
|
|
368
321
|
# en:
|
|
@@ -375,9 +328,6 @@ module StateMachines
|
|
|
375
328
|
# # %{value} = attribute value, %{event} = Human event name, %{state} = Human current state name
|
|
376
329
|
# invalid_transition: "cannot transition via %{event}"
|
|
377
330
|
#
|
|
378
|
-
# Notice that the interpolation syntax is %{key} in Rails 3+. In Rails 2.x,
|
|
379
|
-
# the appropriate syntax is {{key}}.
|
|
380
|
-
#
|
|
381
331
|
# You can override these for a specific model like so:
|
|
382
332
|
#
|
|
383
333
|
# en:
|
|
@@ -417,7 +367,7 @@ module StateMachines
|
|
|
417
367
|
include ActiveModel
|
|
418
368
|
|
|
419
369
|
# The default options to use for state machines using this integration
|
|
420
|
-
@defaults = {:
|
|
370
|
+
@defaults = { action: :save, use_transactions: true }
|
|
421
371
|
class << self
|
|
422
372
|
# Classes that inherit from ActiveRecord::Base will automatically use
|
|
423
373
|
# the ActiveRecord integration.
|
|
@@ -435,28 +385,29 @@ module StateMachines
|
|
|
435
385
|
|
|
436
386
|
# Gets the db default for the machine's attribute
|
|
437
387
|
def owner_class_attribute_default
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
388
|
+
return unless owner_class.connected? && owner_class.table_exists?
|
|
389
|
+
|
|
390
|
+
owner_class.column_defaults[attribute.to_s]
|
|
441
391
|
end
|
|
442
392
|
|
|
443
393
|
def define_state_initializer
|
|
444
|
-
define_helper :instance, <<-
|
|
394
|
+
define_helper :instance, <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
445
395
|
def initialize(attributes = nil, *)
|
|
446
396
|
super(attributes) do |*args|
|
|
447
|
-
|
|
397
|
+
attributes = (attributes || {}).transform_keys { |key| self.class.attribute_aliases[key.to_s] || key }
|
|
398
|
+
scoped_attributes = attributes.merge(self.class.scope_attributes)
|
|
448
399
|
|
|
449
400
|
self.class.state_machines.initialize_states(self, {}, scoped_attributes)
|
|
450
401
|
yield(*args) if block_given?
|
|
451
402
|
end
|
|
452
403
|
end
|
|
453
|
-
|
|
404
|
+
END_EVAL
|
|
454
405
|
end
|
|
455
406
|
|
|
456
407
|
# Uses around callbacks to run state events if using the :save hook
|
|
457
408
|
def define_action_hook
|
|
458
409
|
if action_hook == :save
|
|
459
|
-
define_helper :instance, <<-
|
|
410
|
+
define_helper :instance, <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
460
411
|
def save(*, **)
|
|
461
412
|
self.class.state_machine(#{name.inspect}).send(:around_save, self) { super }
|
|
462
413
|
end
|
|
@@ -469,34 +420,44 @@ module StateMachines
|
|
|
469
420
|
def changed_for_autosave?
|
|
470
421
|
super || self.class.state_machines.any? {|name, machine| machine.action == :save && machine.read(self, :event)}
|
|
471
422
|
end
|
|
472
|
-
|
|
423
|
+
END_EVAL
|
|
473
424
|
else
|
|
474
425
|
super
|
|
475
426
|
end
|
|
476
427
|
end
|
|
477
428
|
|
|
478
429
|
# Runs state events around the machine's :save action
|
|
479
|
-
def around_save(object)
|
|
480
|
-
|
|
430
|
+
def around_save(object, &)
|
|
431
|
+
# Pass fiber: false to avoid deadlocks with ActiveRecord's LoadInterlockAwareMonitor
|
|
432
|
+
object.class.state_machines.transitions(object, action, fiber: false).perform(&)
|
|
481
433
|
end
|
|
482
434
|
|
|
483
435
|
# Creates a scope for finding records *with* a particular state or
|
|
484
436
|
# states for the attribute
|
|
485
437
|
def create_with_scope(name)
|
|
486
|
-
|
|
438
|
+
attr_name = attribute
|
|
439
|
+
lambda do |klass, values|
|
|
440
|
+
if values.present?
|
|
441
|
+
klass.where(attr_name => values)
|
|
442
|
+
else
|
|
443
|
+
klass.all
|
|
444
|
+
end
|
|
445
|
+
end
|
|
487
446
|
end
|
|
488
447
|
|
|
489
448
|
# Creates a scope for finding records *without* a particular state or
|
|
490
449
|
# states for the attribute
|
|
491
450
|
def create_without_scope(name)
|
|
492
|
-
|
|
451
|
+
attr_name = attribute
|
|
452
|
+
lambda do |klass, values|
|
|
453
|
+
if values.present?
|
|
454
|
+
klass.where.not(attr_name => values)
|
|
455
|
+
else
|
|
456
|
+
klass.all
|
|
457
|
+
end
|
|
458
|
+
end
|
|
493
459
|
end
|
|
494
460
|
|
|
495
|
-
# Generates the fully-qualifed column name for this machine's attribute
|
|
496
|
-
def attribute_column
|
|
497
|
-
connection = owner_class.connection
|
|
498
|
-
"#{connection.quote_table_name(owner_class.table_name)}.#{connection.quote_column_name(attribute)}"
|
|
499
|
-
end
|
|
500
461
|
|
|
501
462
|
# Runs a new database transaction, rolling back any changes by raising
|
|
502
463
|
# an ActiveRecord::Rollback exception if the yielded block fails
|
|
@@ -515,17 +476,19 @@ module StateMachines
|
|
|
515
476
|
|
|
516
477
|
private
|
|
517
478
|
|
|
518
|
-
# Defines a new named scope with the given name
|
|
519
|
-
def create_scope(name, scope)
|
|
520
|
-
lambda { |model, values| model.where(scope.call(values)) }
|
|
521
|
-
end
|
|
522
479
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
480
|
+
# Generates the results for the given scope based on one or more states to filter by
|
|
481
|
+
def run_scope(scope, machine, klass, states)
|
|
482
|
+
values = states.flatten.compact.map { |state| machine.states.fetch(state).value }
|
|
483
|
+
scope.call(klass, values)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# ActiveModel's use of method_missing / respond_to for attribute methods
|
|
487
|
+
# breaks both ancestor lookups and defined?(super). Need to special-case
|
|
488
|
+
# the existence of query attribute methods.
|
|
489
|
+
def owner_class_ancestor_has_method?(scope, method)
|
|
490
|
+
scope == :instance && method == "#{attribute}?" ? owner_class : super
|
|
491
|
+
end
|
|
529
492
|
end
|
|
530
493
|
register(ActiveRecord)
|
|
531
494
|
end
|