state_machine 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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