state_machine 0.4.3 → 0.5.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.
Files changed (48) hide show
  1. data/CHANGELOG.rdoc +17 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +54 -84
  4. data/Rakefile +1 -1
  5. data/examples/Car_state.png +0 -0
  6. data/examples/Vehicle_state.png +0 -0
  7. data/examples/auto_shop.rb +11 -0
  8. data/examples/car.rb +19 -0
  9. data/examples/traffic_light.rb +9 -0
  10. data/examples/vehicle.rb +35 -0
  11. data/lib/state_machine.rb +65 -52
  12. data/lib/state_machine/assertions.rb +1 -1
  13. data/lib/state_machine/callback.rb +13 -9
  14. data/lib/state_machine/eval_helpers.rb +4 -3
  15. data/lib/state_machine/event.rb +51 -33
  16. data/lib/state_machine/extensions.rb +2 -2
  17. data/lib/state_machine/guard.rb +47 -41
  18. data/lib/state_machine/integrations.rb +67 -0
  19. data/lib/state_machine/integrations/active_record.rb +62 -36
  20. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  21. data/lib/state_machine/integrations/data_mapper.rb +23 -37
  22. data/lib/state_machine/integrations/data_mapper/observer.rb +23 -9
  23. data/lib/state_machine/integrations/sequel.rb +23 -24
  24. data/lib/state_machine/machine.rb +380 -277
  25. data/lib/state_machine/node_collection.rb +142 -0
  26. data/lib/state_machine/state.rb +114 -69
  27. data/lib/state_machine/state_collection.rb +38 -0
  28. data/lib/state_machine/transition.rb +36 -17
  29. data/test/active_record.log +2940 -85664
  30. data/test/functional/state_machine_test.rb +49 -53
  31. data/test/sequel.log +747 -11990
  32. data/test/unit/assertions_test.rb +2 -1
  33. data/test/unit/callback_test.rb +14 -12
  34. data/test/unit/eval_helpers_test.rb +25 -6
  35. data/test/unit/event_test.rb +144 -124
  36. data/test/unit/guard_test.rb +118 -140
  37. data/test/unit/integrations/active_record_test.rb +102 -68
  38. data/test/unit/integrations/data_mapper_test.rb +48 -37
  39. data/test/unit/integrations/sequel_test.rb +34 -25
  40. data/test/unit/integrations_test.rb +42 -0
  41. data/test/unit/machine_test.rb +460 -531
  42. data/test/unit/node_collection_test.rb +208 -0
  43. data/test/unit/state_collection_test.rb +167 -0
  44. data/test/unit/state_machine_test.rb +1 -1
  45. data/test/unit/state_test.rb +223 -200
  46. data/test/unit/transition_test.rb +81 -46
  47. metadata +17 -3
  48. data/test/data_mapper.log +0 -30860
@@ -8,9 +8,9 @@ module StateMachine
8
8
  # ActiveRecord model:
9
9
  #
10
10
  # class Vehicle < ActiveRecord::Base
11
- # state_machine :initial => 'parked' do
11
+ # state_machine :initial => :parked do
12
12
  # event :ignite do
13
- # transition :to => 'idling', :from => 'parked'
13
+ # transition :to => :idling, :from => :parked
14
14
  # end
15
15
  # end
16
16
  # end
@@ -28,7 +28,7 @@ module StateMachine
28
28
  #
29
29
  # For example,
30
30
  #
31
- # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: nil>
31
+ # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
32
32
  # vehicle.name = 'Ford Explorer'
33
33
  # vehicle.ignite # => true
34
34
  # vehicle.reload # => #<Vehicle id: 1, name: "Ford Explorer", state: "idling">
@@ -51,7 +51,7 @@ module StateMachine
51
51
  # end
52
52
  # end
53
53
  #
54
- # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: nil>
54
+ # vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
55
55
  # vehicle.ignite # => false
56
56
  # Message.count # => 0
57
57
  #
@@ -66,21 +66,24 @@ module StateMachine
66
66
  # scopes are defined on the model for finding records with or without a
67
67
  # particular set of states.
68
68
  #
69
- # These named scopes are the functional equivalent of the following
70
- # definitions:
69
+ # These named scopes are essentially the functional equivalent of the
70
+ # following definitions:
71
71
  #
72
72
  # class Vehicle < ActiveRecord::Base
73
- # named_scope :with_states, lambda {|*values| {:conditions => {:state => values.flatten}}}
73
+ # named_scope :with_states, lambda {|*states| {:conditions => {:state => states}}}
74
74
  # # with_states also aliased to with_state
75
75
  #
76
- # named_scope :without_states, lambda {|*values| {:conditions => ['state NOT IN (?)', values.flatten]}}
76
+ # named_scope :without_states, lambda {|*states| {:conditions => ['state NOT IN (?)', states]}}
77
77
  # # without_states also aliased to without_state
78
78
  # end
79
79
  #
80
+ # *Note*, however, that the states are converted to their stored values
81
+ # before being passed into the query.
82
+ #
80
83
  # Because of the way named scopes work in ActiveRecord, they can be
81
84
  # chained like so:
82
85
  #
83
- # Vehicle.with_state('parked').all(:order => 'id DESC')
86
+ # Vehicle.with_state(:parked).all(:order => 'id DESC')
84
87
  #
85
88
  # == Callbacks
86
89
  #
@@ -91,8 +94,8 @@ module StateMachine
91
94
  # For example,
92
95
  #
93
96
  # class Vehicle < ActiveRecord::Base
94
- # state_machine :initial => 'parked' do
95
- # before_transition :to => 'idling' do |vehicle|
97
+ # state_machine :initial => :parked do
98
+ # before_transition :to => :idling do |vehicle|
96
99
  # vehicle.put_on_seatbelt
97
100
  # end
98
101
  #
@@ -101,7 +104,7 @@ module StateMachine
101
104
  # end
102
105
  #
103
106
  # event :ignite do
104
- # transition :to => 'idling', :from => 'parked'
107
+ # transition :to => :idling, :from => :parked
105
108
  # end
106
109
  # end
107
110
  #
@@ -164,6 +167,11 @@ module StateMachine
164
167
  defined?(::ActiveRecord::Base) && klass <= ::ActiveRecord::Base
165
168
  end
166
169
 
170
+ # Loads additional files specific to ActiveRecord
171
+ def self.extended(base) #:nodoc:
172
+ require 'state_machine/integrations/active_record/observer'
173
+ end
174
+
167
175
  # Runs a new database transaction, rolling back any changes by raising
168
176
  # an ActiveRecord::Rollback exception if the yielded block fails
169
177
  # (i.e. returns false).
@@ -188,24 +196,23 @@ module StateMachine
188
196
  # Forces all attribute methods to be generated for the model so that
189
197
  # the reader/writer methods for the attribute are available
190
198
  def define_attribute_accessor
191
- if owner_class.table_exists?
199
+ # If an exception is raised while trying to access the connection, then
200
+ # the assumption is that there's an issue with the database (most likely
201
+ # doesn't exist yet), so we won't be able to check the table properties
202
+ connection_exists = begin; owner_class.connection; true; rescue Exception; false; end
203
+
204
+ if connection_exists && owner_class.table_exists?
192
205
  owner_class.define_attribute_methods
193
206
 
194
207
  # Support attribute predicate for ActiveRecord columns
195
- if owner_class.column_names.include?(attribute)
208
+ if owner_class.column_names.include?(attribute.to_s)
196
209
  attribute = self.attribute
197
210
 
198
211
  owner_class.class_eval do
212
+ # Checks whether the current state is a given value. If there
213
+ # are no arguments, then this checks for the presence of the attribute.
199
214
  define_method("#{attribute}?") do |*args|
200
- if args.empty?
201
- # No arguments: querying for presence of the attribute
202
- super
203
- else
204
- # Arguments: querying for the attribute's current value
205
- state = args.first
206
- raise ArgumentError, "#{state.inspect} is not a known #{attribute} value" unless self.class.state_machines[attribute].states.include?(state)
207
- send(attribute) == state
208
- end
215
+ args.empty? ? super(*args) : self.class.state_machines[attribute].state?(self, *args)
209
216
  end
210
217
  end
211
218
  end
@@ -214,18 +221,18 @@ module StateMachine
214
221
  super
215
222
  end
216
223
 
217
- # Defines a scope for finding records *with* a particular value or
218
- # values for the attribute
219
- def define_with_scope(name)
224
+ # Creates a scope for finding records *with* a particular state or
225
+ # states for the attribute
226
+ def create_with_scope(name)
220
227
  attribute = self.attribute
221
- owner_class.named_scope name.to_sym, lambda {|*values| {:conditions => {attribute => values.flatten}}}
228
+ define_scope(name, lambda {|values| {:conditions => {attribute => values}}})
222
229
  end
223
230
 
224
- # Defines a scope for finding records *without* a particular value or
225
- # values for the attribute
226
- def define_without_scope(name)
231
+ # Creates a scope for finding records *without* a particular state or
232
+ # states for the attribute
233
+ def create_without_scope(name)
227
234
  attribute = self.attribute
228
- owner_class.named_scope name.to_sym, lambda {|*values| {:conditions => ["#{attribute} NOT IN (?)", values.flatten]}}
235
+ define_scope(name, lambda {|values| {:conditions => ["#{attribute} NOT IN (?)", values]}})
229
236
  end
230
237
 
231
238
  # Creates a new callback in the callback chain, always inserting it
@@ -240,6 +247,28 @@ module StateMachine
240
247
  end
241
248
 
242
249
  private
250
+ # Defines a new named scope with the given name. Since ActiveRecord
251
+ # does not allow direct access to the model being used within the
252
+ # evaluation of a dynamic named scope, the scope must be generated
253
+ # manually. It's necessary to have access to the model so that the
254
+ # state names can be translated to their associated values and so that
255
+ # inheritance is respected properly.
256
+ def define_scope(name, scope)
257
+ name = name.to_sym
258
+ attribute = self.attribute
259
+
260
+ # Created the scope and then override it with state translation
261
+ owner_class.named_scope(name)
262
+ owner_class.scopes[name] = lambda do |klass, *states|
263
+ machine_states = klass.state_machines[attribute].states
264
+ values = states.flatten.map {|state| machine_states.fetch(state).value}
265
+
266
+ ::ActiveRecord::NamedScope::Scope.new(klass, scope.call(values))
267
+ end
268
+
269
+ false
270
+ end
271
+
243
272
  # Notifies observers on the given object that a callback occurred
244
273
  # involving the given transition. This will attempt to call the
245
274
  # following methods on observers:
@@ -251,11 +280,8 @@ module StateMachine
251
280
  def notify(type, object, transition)
252
281
  qualified_event = namespace ? "#{transition.event}_#{namespace}" : transition.event
253
282
  ["#{type}_#{qualified_event}", "#{type}_transition"].each do |method|
254
- object.class.class_eval do
255
- @observer_peers.dup.each do |observer|
256
- observer.send(method, object, transition) if observer.respond_to?(method)
257
- end if defined?(@observer_peers)
258
- end
283
+ object.class.changed
284
+ object.class.notify_observers(method, object, transition)
259
285
  end
260
286
 
261
287
  true
@@ -0,0 +1,41 @@
1
+ module StateMachine
2
+ module Integrations #:nodoc:
3
+ module ActiveRecord
4
+ # Adds support for invoking callbacks on ActiveRecord observers with more
5
+ # than one argument (e.g. the record *and* the state transition). By
6
+ # default, ActiveRecord only supports passing the record into the
7
+ # callbacks.
8
+ #
9
+ # For example:
10
+ #
11
+ # class VehicleObserver < ActiveRecord::Observer
12
+ # # The default behavior: only pass in the record
13
+ # def after_save(vehicle)
14
+ # end
15
+ #
16
+ # # Custom behavior: allow the transition to be passed in as well
17
+ # def after_transition(vehicle, transition)
18
+ # Audit.log(vehicle, transition)
19
+ # end
20
+ # end
21
+ module Observer
22
+ def self.included(base) #:nodoc:
23
+ base.class_eval do
24
+ alias_method :update_without_multiple_args, :update
25
+ alias_method :update, :update_with_multiple_args
26
+ end
27
+ end
28
+
29
+ # Allows additional arguments other than the object to be passed to the
30
+ # observed methods
31
+ def update_with_multiple_args(observed_method, object, *args) #:nodoc:
32
+ send(observed_method, object, *args) if respond_to?(observed_method)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::Observer.class_eval do
40
+ include StateMachine::Integrations::ActiveRecord::Observer
41
+ end
@@ -2,19 +2,6 @@ module StateMachine
2
2
  module Integrations #:nodoc:
3
3
  # Adds support for integrating state machines with DataMapper resources.
4
4
  #
5
- # == Requirements
6
- #
7
- # To use this feature of the DataMapper integration, the dm-observer library
8
- # must be available. This can be installed either directly or indirectly
9
- # through dm-more. When loading DataMapper, be sure to load the dm-observer
10
- # library as well like so:
11
- #
12
- # require 'rubygems'
13
- # require 'dm-core'
14
- # require 'dm-observer'
15
- #
16
- # If dm-observer is not available, then this feature will be skipped.
17
- #
18
5
  # == Examples
19
6
  #
20
7
  # Below is an example of a simple state machine defined within a
@@ -27,9 +14,9 @@ module StateMachine
27
14
  # property :name, String
28
15
  # property :state, String
29
16
  #
30
- # state_machine :initial => 'parked' do
17
+ # state_machine :initial => :parked do
31
18
  # event :ignite do
32
- # transition :to => 'idling', :from => 'parked'
19
+ # transition :to => :idling, :from => :parked
33
20
  # end
34
21
  # end
35
22
  # end
@@ -47,7 +34,7 @@ module StateMachine
47
34
  #
48
35
  # For example,
49
36
  #
50
- # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=nil>
37
+ # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
51
38
  # vehicle.name = 'Ford Explorer'
52
39
  # vehicle.ignite # => true
53
40
  # vehicle.reload # => #<Vehicle id=1 name="Ford Explorer" state="idling">
@@ -74,7 +61,7 @@ module StateMachine
74
61
  # end
75
62
  # end
76
63
  #
77
- # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=nil>
64
+ # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
78
65
  # vehicle.ignite # => false
79
66
  # Message.all.count # => 0
80
67
  #
@@ -99,22 +86,25 @@ module StateMachine
99
86
  # property :state, String
100
87
  #
101
88
  # class << self
102
- # def with_states(*values)
103
- # all(:state => values.flatten)
89
+ # def with_states(*states)
90
+ # all(:state => states.flatten)
104
91
  # end
105
92
  # alias_method :with_state, :with_states
106
93
  #
107
- # def without_states(*values)
108
- # all(:state.not => values.flatten)
94
+ # def without_states(*states)
95
+ # all(:state.not => states.flatten)
109
96
  # end
110
97
  # alias_method :without_state, :without_states
111
98
  # end
112
99
  # end
113
100
  #
101
+ # *Note*, however, that the states are converted to their stored values
102
+ # before being passed into the query.
103
+ #
114
104
  # Because of the way scopes work in DataMapper, they can be chained like
115
105
  # so:
116
106
  #
117
- # Vehicle.with_state('parked').all(:order => [:id.desc])
107
+ # Vehicle.with_state(:parked).all(:order => [:id.desc])
118
108
  #
119
109
  # == Callbacks / Observers
120
110
  #
@@ -131,8 +121,8 @@ module StateMachine
131
121
  # property :id, Serial
132
122
  # property :state, String
133
123
  #
134
- # state_machine :initial => 'parked' do
135
- # before_transition :to => 'idling' do
124
+ # state_machine :initial => :parked do
125
+ # before_transition :to => :idling do
136
126
  # put_on_seatbelt
137
127
  # end
138
128
  #
@@ -141,7 +131,7 @@ module StateMachine
141
131
  # end
142
132
  #
143
133
  # event :ignite do
144
- # transition :to => 'idling', :from => 'parked'
134
+ # transition :to => :idling, :from => :parked
145
135
  # end
146
136
  # end
147
137
  #
@@ -181,22 +171,18 @@ module StateMachine
181
171
  :save
182
172
  end
183
173
 
184
- # Defines a scope for finding records *with* a particular value or
185
- # values for the attribute
186
- def define_with_scope(name)
174
+ # Creates a scope for finding records *with* a particular state or
175
+ # states for the attribute
176
+ def create_with_scope(name)
187
177
  attribute = self.attribute
188
- (class << owner_class; self; end).class_eval do
189
- define_method(name) {|*values| all(attribute => values.flatten)}
190
- end
178
+ lambda {|resource, values| resource.all(attribute => values)}
191
179
  end
192
180
 
193
- # Defines a scope for finding records *without* a particular value or
194
- # values for the attribute
195
- def define_without_scope(name)
181
+ # Creates a scope for finding records *without* a particular state or
182
+ # states for the attribute
183
+ def create_without_scope(name)
196
184
  attribute = self.attribute
197
- (class << owner_class; self; end).class_eval do
198
- define_method(name) {|*values| all(attribute.to_sym.not => values.flatten)}
199
- end
185
+ lambda {|resource, values| resource.all(attribute.to_sym.not => values)}
200
186
  end
201
187
 
202
188
  # Creates a new callback in the callback chain, always ensuring that
@@ -22,6 +22,20 @@ module StateMachine
22
22
  # Audit.log(self, transition) if saved
23
23
  # end
24
24
  # end
25
+ #
26
+ # == Requirements
27
+ #
28
+ # To use this feature of the DataMapper integration, the dm-observer library
29
+ # must be available. This can be installed either directly or indirectly
30
+ # through dm-more. When loading DataMapper, be sure to load the dm-observer
31
+ # library as well like so:
32
+ #
33
+ # require 'rubygems'
34
+ # require 'dm-core'
35
+ # require 'dm-observer'
36
+ #
37
+ # If dm-observer is not available, then this feature will be skipped.
38
+ #
25
39
  module Observer
26
40
  # Creates a callback that will be invoked *before* a transition is
27
41
  # performed, so long as the given configuration options match the
@@ -39,9 +53,9 @@ module StateMachine
39
53
  # property :id, Serial
40
54
  # property :state, :String
41
55
  #
42
- # state_machine :initial => 'parked' do
56
+ # state_machine :initial => :parked do
43
57
  # event :ignite do
44
- # transition :to => 'idling', :from => 'parked'
58
+ # transition :to => :idling, :from => :parked
45
59
  # end
46
60
  # end
47
61
  # end
@@ -56,12 +70,12 @@ module StateMachine
56
70
  # end
57
71
  #
58
72
  # # Target all state machines
59
- # before_transition :to => 'idling', :from => 'parked', :on => 'ignite' do
73
+ # before_transition :to => :idling, :from => :parked, :on => :ignite do
60
74
  # # put on seatbelt
61
75
  # end
62
76
  #
63
77
  # # Target a specific state machine
64
- # before_transition :state, :to => 'idling' do
78
+ # before_transition :state, :to => :idling do
65
79
  # # put on seatbelt
66
80
  # end
67
81
  #
@@ -95,9 +109,9 @@ module StateMachine
95
109
  # property :id, Serial
96
110
  # property :state, :String
97
111
  #
98
- # state_machine :initial => 'parked' do
112
+ # state_machine :initial => :parked do
99
113
  # event :ignite do
100
- # transition :to => 'idling', :from => 'parked'
114
+ # transition :to => :idling, :from => :parked
101
115
  # end
102
116
  # end
103
117
  # end
@@ -112,12 +126,12 @@ module StateMachine
112
126
  # end
113
127
  #
114
128
  # # Target all state machines
115
- # after_transition :to => 'idling', :from => 'parked', :on => 'ignite' do
129
+ # after_transition :to => :idling, :from => :parked, :on => :ignite do
116
130
  # # put on seatbelt
117
131
  # end
118
132
  #
119
133
  # # Target a specific state machine
120
- # after_transition :state, :to => 'idling' do
134
+ # after_transition :state, :to => :idling do
121
135
  # # put on seatbelt
122
136
  # end
123
137
  #
@@ -143,7 +157,7 @@ module StateMachine
143
157
  def add_transition_callback(type, *args, &block)
144
158
  if args.first && !args.first.is_a?(Hash)
145
159
  # Specific attribute is being targeted
146
- attribute = args.first.to_s
160
+ attribute = args.first
147
161
  transition_args = args[1..-1]
148
162
  else
149
163
  # Target all state machines
@@ -8,9 +8,9 @@ module StateMachine
8
8
  # Sequel model:
9
9
  #
10
10
  # class Vehicle < Sequel::Model
11
- # state_machine :initial => 'parked' do
11
+ # state_machine :initial => :parked do
12
12
  # event :ignite do
13
- # transition :to => 'idling', :from => 'parked'
13
+ # transition :to => :idling, :from => :parked
14
14
  # end
15
15
  # end
16
16
  # end
@@ -28,7 +28,7 @@ module StateMachine
28
28
  #
29
29
  # For example,
30
30
  #
31
- # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=nil>
31
+ # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
32
32
  # vehicle.name = 'Ford Explorer'
33
33
  # vehicle.ignite # => true
34
34
  # vehicle.refresh # => #<Vehicle id=1 name="Ford Explorer" state="idling">
@@ -51,7 +51,7 @@ module StateMachine
51
51
  # end
52
52
  # end
53
53
  #
54
- # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state=nil>
54
+ # vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
55
55
  # vehicle.ignite # => false
56
56
  # Message.count # => 0
57
57
  #
@@ -71,21 +71,24 @@ module StateMachine
71
71
  #
72
72
  # class Vehicle < Sequel::Model
73
73
  # class << self
74
- # def with_states(*values)
75
- # filter(:state => values)
74
+ # def with_states(*states)
75
+ # filter(:state => states)
76
76
  # end
77
77
  # alias_method :with_state, :with_states
78
78
  #
79
- # def without_states(*values)
80
- # filter(~{:state => values})
79
+ # def without_states(*states)
80
+ # filter(~{:state => states})
81
81
  # end
82
82
  # alias_method :without_state, :without_states
83
83
  # end
84
84
  # end
85
85
  #
86
+ # *Note*, however, that the states are converted to their stored values
87
+ # before being passed into the query.
88
+ #
86
89
  # Because of the way scopes work in Sequel, they can be chained like so:
87
90
  #
88
- # Vehicle.with_state('parked').with_state('idling').order(:id.desc)
91
+ # Vehicle.with_state(:parked).order(:id.desc)
89
92
  #
90
93
  # == Callbacks
91
94
  #
@@ -97,8 +100,8 @@ module StateMachine
97
100
  # For example,
98
101
  #
99
102
  # class Vehicle < Sequel::Model
100
- # state_machine :initial => 'parked' do
101
- # before_transition :to => 'idling' do
103
+ # state_machine :initial => :parked do
104
+ # before_transition :to => :idling do
102
105
  # put_on_seatbelt
103
106
  # end
104
107
  #
@@ -107,7 +110,7 @@ module StateMachine
107
110
  # end
108
111
  #
109
112
  # event :ignite do
110
- # transition :to => 'idling', :from => 'parked'
113
+ # transition :to => :idling, :from => :parked
111
114
  # end
112
115
  # end
113
116
  #
@@ -138,22 +141,18 @@ module StateMachine
138
141
  :save
139
142
  end
140
143
 
141
- # Defines a scope for finding records *with* a particular value or
142
- # values for the attribute
143
- def define_with_scope(name)
144
+ # Creates a scope for finding records *with* a particular state or
145
+ # states for the attribute
146
+ def create_with_scope(name)
144
147
  attribute = self.attribute
145
- (class << owner_class; self; end).class_eval do
146
- define_method(name) {|*values| filter(attribute.to_sym => values.flatten)}
147
- end
148
+ lambda {|model, values| model.filter(attribute.to_sym => values)}
148
149
  end
149
150
 
150
- # Defines a scope for finding records *without* a particular value or
151
- # values for the attribute
152
- def define_without_scope(name)
151
+ # Creates a scope for finding records *without* a particular state or
152
+ # states for the attribute
153
+ def create_without_scope(name)
153
154
  attribute = self.attribute
154
- (class << owner_class; self; end).class_eval do
155
- define_method(name) {|*values| filter(~{attribute.to_sym => values.flatten})}
156
- end
155
+ lambda {|model, values| model.filter(~{attribute.to_sym => values})}
157
156
  end
158
157
 
159
158
  # Creates a new callback in the callback chain, always ensuring that