power_enum 0.8.6 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,395 +0,0 @@
1
- # Copyright (c) 2005 Trevor Squires
2
- # Copyright (c) 2012 Arthur Shagall
3
- # Released under the MIT License. See the LICENSE file for more details.
4
-
5
- module ActiveRecord
6
- module Acts
7
- module Enumerated
8
- extend ActiveSupport::Concern
9
-
10
- module ClassMethods
11
-
12
- # Returns false for ActiveRecord models that do not act as enumerated.
13
- def acts_as_enumerated?
14
- false
15
- end
16
-
17
- # Declares the model as enumerated. See the README for detailed usage instructions.
18
- #
19
- # === Supported options
20
- # [:conditions]
21
- # SQL search conditions
22
- # [:order]
23
- # SQL load order clause
24
- # [:on_lookup_failure]
25
- # Specifies the name of a class method to invoke when the +[]+ method is unable to locate a BookingStatus
26
- # record for arg. The default is the built-in :enforce_none which returns nil. There are also built-ins for
27
- # :enforce_strict (raise and exception regardless of the type for arg), :enforce_strict_literals (raises an
28
- # exception if the arg is a Fixnum or Symbol), :enforce_strict_ids (raises and exception if the arg is a
29
- # Fixnum) and :enforce_strict_symbols (raises an exception if the arg is a Symbol). The purpose of the
30
- # :on_lookup_failure option is that a) under some circumstances a lookup failure is a Bad Thing and action
31
- # should be taken, therefore b) a fallback action should be easily configurable. You can also give it a
32
- # lambda that takes in a single argument (The arg that was passed to +[]+).
33
- # [:name_column]
34
- # Override for the 'name' column. By default, assumed to be 'name'.
35
- #
36
- # === Examples
37
- #
38
- # ====Example 1
39
- # class BookingStatus < ActiveRecord::Base
40
- # acts_as_enumerated
41
- # end
42
- #
43
- # ====Example 2
44
- # class BookingStatus < ActiveRecord::Base
45
- # acts_as_enumerated :on_lookup_failure => :enforce_strict
46
- # end
47
- #
48
- # ====Example 3
49
- # class BookingStatus < ActiveRecord::Base
50
- # acts_as_enumerated :conditions => [:exclude => false],
51
- # :order => 'created_at DESC',
52
- # :on_lookup_failure => :lookup_failed,
53
- # :name_column => :status_code
54
- #
55
- # def self.lookup_failed(arg)
56
- # logger.error("Invalid status code lookup #{arg.inspect}")
57
- # nil
58
- # end
59
- # end
60
- #
61
- # ====Example 4
62
- # class BookingStatus < ActiveRecord::Base
63
- # acts_as_enumerated :conditions => [:exclude => false],
64
- # :order => 'created_at DESC',
65
- # :on_lookup_failure => lambda { |arg| raise CustomError, "BookingStatus lookup failed; #{arg}" },
66
- # :name_column => :status_code
67
- # end
68
- def acts_as_enumerated(options = {})
69
- valid_keys = [:conditions, :order, :on_lookup_failure, :name_column]
70
- options.assert_valid_keys(*valid_keys)
71
-
72
- valid_keys.each do |key|
73
- class_attribute "acts_enumerated_#{key.to_s}"
74
- if options.has_key?( key )
75
- self.send "acts_enumerated_#{key.to_s}=", options[key]
76
- end
77
- end
78
-
79
- name_column = if options.has_key?(:name_column) then
80
- options[:name_column].to_s.to_sym
81
- else
82
- :name
83
- end
84
-
85
- class_attribute :acts_enumerated_name_column
86
- self.acts_enumerated_name_column = name_column
87
-
88
- unless self.is_a? ActiveRecord::Acts::Enumerated::EnumClassMethods
89
- extend ActiveRecord::Acts::Enumerated::EnumClassMethods
90
-
91
- class_eval do
92
- include ActiveRecord::Acts::Enumerated::EnumInstanceMethods
93
-
94
- before_save :enumeration_model_update
95
- before_destroy :enumeration_model_update
96
- validates name_column, :presence => true, :uniqueness => true
97
-
98
- define_method :name do
99
- read_attribute( name_column )
100
- end
101
- end
102
- end
103
- end
104
- end
105
-
106
- module EnumClassMethods
107
- attr_accessor :enumeration_model_updates_permitted
108
-
109
- # Returns true for ActiveRecord models that act as enumerated.
110
- def acts_as_enumerated?
111
- true
112
- end
113
-
114
- # Returns all the enum values. Caches results after the first time this method is run.
115
- def all
116
- return @all if @all
117
- conditions = self.acts_enumerated_conditions
118
- order = self.acts_enumerated_order
119
- @all = where(conditions).order(order).collect{|val| val.freeze}.freeze
120
- end
121
-
122
- # Returns all the active enum values. See the 'active?' instance method.
123
- def active
124
- return @all_active if @all_active
125
- @all_active = all.select{ |enum| enum.active? }.freeze
126
- end
127
-
128
- # Returns all the inactive enum values. See the 'inactive?' instance method.
129
- def inactive
130
- return @all_inactive if @all_inactive
131
- @all_inactive = all.select{ |enum| !enum.active? }.freeze
132
- end
133
-
134
- # Returns the names of all the enum values as an array of symbols.
135
- def names
136
- all.map { |item| item.name_sym }
137
- end
138
-
139
- # Enum lookup by Symbol, String, or id. Returns <tt>arg<tt> if arg is
140
- # an enum instance. Passing in a list of arguments returns a list of enums.
141
- def [](*args)
142
- case args.size
143
- when 0
144
- nil
145
- when 1
146
- arg = args.first
147
- case arg
148
- when Symbol
149
- return_val = lookup_name(arg.id2name) and return return_val
150
- when String
151
- return_val = lookup_name(arg) and return return_val
152
- when Fixnum
153
- return_val = lookup_id(arg) and return return_val
154
- when self
155
- return arg
156
- when nil
157
- nil
158
- else
159
- raise TypeError, "#{self.name}[]: argument should be a String, Symbol or Fixnum but got a: #{arg.class.name}"
160
- end
161
-
162
- handle_lookup_failure(arg)
163
- else
164
- args.map{ |item| self[item] }.uniq
165
- end
166
- end
167
-
168
- # Deals with a lookup failure for the given argument.
169
- def handle_lookup_failure(arg)
170
- if (lookup_failure_handler = self.acts_enumerated_on_lookup_failure)
171
- case lookup_failure_handler
172
- when Proc
173
- lookup_failure_handler.call(arg)
174
- else
175
- self.send(lookup_failure_handler, arg)
176
- end
177
- else
178
- self.send(:enforce_none, arg)
179
- end
180
- end
181
- private :handle_lookup_failure
182
-
183
- # Enum lookup by id
184
- def lookup_id(arg)
185
- all_by_id[arg]
186
- end
187
-
188
- # Enum lookup by String
189
- def lookup_name(arg)
190
- all_by_name[arg]
191
- end
192
-
193
- # Returns true if the enum lookup by the given Symbol, String or id would have returned a value, false otherwise.
194
- def include?(arg)
195
- case arg
196
- when Symbol
197
- !lookup_name(arg.id2name).nil?
198
- when String
199
- !lookup_name(arg).nil?
200
- when Fixnum
201
- !lookup_id(arg).nil?
202
- when self
203
- possible_match = lookup_id(arg.id)
204
- !possible_match.nil? && possible_match == arg
205
- else
206
- false
207
- end
208
- end
209
-
210
- # NOTE: purging the cache is sort of pointless because
211
- # of the per-process rails model.
212
- # By default this blows up noisily just in case you try to be more
213
- # clever than rails allows.
214
- # For those times (like in Migrations) when you really do want to
215
- # alter the records you can silence the carping by setting
216
- # enumeration_model_updates_permitted to true.
217
- def purge_enumerations_cache
218
- unless self.enumeration_model_updates_permitted
219
- raise "#{self.name}: cache purging disabled for your protection"
220
- end
221
- @all = @all_by_name = @all_by_id = @all_active = nil
222
- end
223
-
224
- # The preferred method to update an enumerations model. The same
225
- # warnings as 'purge_enumerations_cache' and
226
- # 'enumerations_model_update_permitted' apply. Pass a block to this
227
- # method (no args) where you perform your updates. Cache will be
228
- # flushed automatically.
229
- def update_enumerations_model
230
- if block_given?
231
- begin
232
- self.enumeration_model_updates_permitted = true
233
- yield
234
- ensure
235
- purge_enumerations_cache
236
- self.enumeration_model_updates_permitted = false
237
- end
238
- end
239
- end
240
-
241
- # Returns the name of the column this enum uses as the basic underlying value.
242
- def name_column
243
- @name_column ||= self.acts_enumerated_name_column
244
- end
245
-
246
- # ---Private methods---
247
-
248
- # Returns a hash of all enumeration members keyed by their ids.
249
- def all_by_id
250
- @all_by_id ||= all_by_attribute( :id )
251
- end
252
- private :all_by_id
253
-
254
- # Returns a hash of all the enumeration members keyed by their names.
255
- def all_by_name
256
- begin
257
- @all_by_name ||= all_by_attribute( :name )
258
- rescue NoMethodError => err
259
- if err.name == name_column
260
- raise TypeError, "#{self.name}: you need to define a '#{name_column}' column in the table '#{table_name}'"
261
- end
262
- raise
263
- end
264
- end
265
- private :all_by_name
266
-
267
- def all_by_attribute(attr)
268
- all.inject({}) { |memo, item|
269
- memo[item.send(attr)] = item
270
- memo
271
- }.freeze
272
- end
273
- private :all_by_attribute
274
-
275
- def enforce_none(arg)
276
- nil
277
- end
278
- private :enforce_none
279
-
280
- def enforce_strict(arg)
281
- raise_record_not_found(arg)
282
- end
283
- private :enforce_strict
284
-
285
- def enforce_strict_literals(arg)
286
- raise_record_not_found(arg) if (Fixnum === arg) || (Symbol === arg)
287
- nil
288
- end
289
- private :enforce_strict_literals
290
-
291
- def enforce_strict_ids(arg)
292
- raise_record_not_found(arg) if Fixnum === arg
293
- nil
294
- end
295
- private :enforce_strict_ids
296
-
297
- def enforce_strict_symbols(arg)
298
- raise_record_not_found(arg) if Symbol === arg
299
- nil
300
- end
301
- private :enforce_strict_symbols
302
-
303
- def raise_record_not_found(arg)
304
- raise ActiveRecord::RecordNotFound, "Couldn't find a #{self.name} identified by (#{arg.inspect})"
305
- end
306
- private :raise_record_not_found
307
-
308
- end
309
-
310
- module EnumInstanceMethods
311
- # Behavior depends on the type of +arg+.
312
- #
313
- # * If +arg+ is +nil+, returns +false+.
314
- # * If +arg+ is an instance of +Symbol+, +Fixnum+ or +String+, returns the result of +BookingStatus[:foo] == BookingStatus[arg]+.
315
- # * If +arg+ is an +Array+, returns +true+ if any member of the array returns +true+ for +===(arg)+, +false+ otherwise.
316
- # * In all other cases, delegates to +===(arg)+ of the superclass.
317
- #
318
- # Examples:
319
- #
320
- # BookingStatus[:foo] === :foo #Returns true
321
- # BookingStatus[:foo] === 'foo' #Returns true
322
- # BookingStatus[:foo] === :bar #Returns false
323
- # BookingStatus[:foo] === [:foo, :bar, :baz] #Returns true
324
- # BookingStatus[:foo] === nil #Returns false
325
- #
326
- # You should note that defining an +:on_lookup_failure+ method that raises an exception will cause +===+ to
327
- # also raise an exception for any lookup failure of +BookingStatus[arg]+.
328
- def ===(arg)
329
- case arg
330
- when nil
331
- false
332
- when Symbol, String, Fixnum
333
- return self == self.class[arg]
334
- when Array
335
- return self.in?(*arg)
336
- else
337
- super
338
- end
339
- end
340
-
341
- alias_method :like?, :===
342
-
343
- # Returns true if any element in the list returns true for ===(arg), false otherwise.
344
- def in?(*list)
345
- for item in list
346
- self === item and return true
347
- end
348
- false
349
- end
350
-
351
- # Returns the symbol representation of the name of the enum. BookingStatus[:foo].name_sym returns :foo.
352
- def name_sym
353
- self.name.to_sym
354
- end
355
-
356
- # By default enumeration #to_s should return stringified name of the enum. BookingStatus[:foo].to_s returns "foo"
357
- def to_s
358
- self.name.to_s
359
- end
360
-
361
- # Returns true if the instance is active, false otherwise. If it has an attribute 'active',
362
- # returns the attribute cast to a boolean, otherwise returns true. This method is used by the 'active'
363
- # class method to select active enums.
364
- def active?
365
- @_active_status ||= ( attributes.include?('active') ? !!self.active : true )
366
- end
367
-
368
- # Returns true if the instance is inactive, false otherwise. Default implementations returns !active?
369
- # This method is used by the 'inactive' class method to select inactive enums.
370
- def inactive?
371
- !active?
372
- end
373
-
374
- # NOTE: updating the models that back an acts_as_enumerated is
375
- # rather dangerous because of rails' per-process model.
376
- # The cached values could get out of synch between processes
377
- # and rather than completely disallow changes I make you jump
378
- # through an extra hoop just in case you're defining your enumeration
379
- # values in Migrations. I.e. set enumeration_model_updates_permitted = true
380
- def enumeration_model_update
381
- if self.class.enumeration_model_updates_permitted
382
- self.class.purge_enumerations_cache
383
- true
384
- else
385
- # Ugh. This just seems hack-ish. I wonder if there's a better way.
386
- self.errors.add(self.class.name_column, "changes to acts_as_enumeration model instances are not permitted")
387
- false
388
- end
389
- end
390
- private :enumeration_model_update
391
- end
392
- end
393
- end
394
- end
395
-
@@ -1,238 +0,0 @@
1
- # Copyright (c) 2005 Trevor Squires
2
- # Copyright (c) 2012 Arthur Shagall
3
- # Released under the MIT License. See the LICENSE file for more details.
4
-
5
- module ActiveRecord
6
- module Aggregations # :nodoc:
7
- module HasEnumerated # :nodoc:
8
-
9
- extend ActiveSupport::Concern
10
-
11
- module ClassMethods
12
-
13
- # Returns a list of all the attributes on the ActiveRecord model which are enumerated.
14
- def enumerated_attributes
15
- @enumerated_attributes ||= []
16
- end
17
-
18
- # Returns +true+ if +attribute+ is an enumerated attribute, +false+ otherwise.
19
- def has_enumerated?(attribute)
20
- return false if attribute.nil?
21
- enumerated_attributes.include? attribute.to_s
22
- end
23
-
24
- # Defines an enumerated attribute with the given name on the model. Also accepts a hash of options as an
25
- # optional second argument.
26
- #
27
- # === Supported options
28
- # [:class_name]
29
- # Name of the enum class. By default it is the camelized version of the has_enumerated attribute.
30
- # [:foreign_key]
31
- # Explicitly set the foreign key column. By default it's assumed to be your_enumerated_attribute_name_id.
32
- # [:on_lookup_failure]
33
- # The :on_lookup_failure option in has_enumerated is there because you may want to create an error handler for
34
- # situations where the argument passed to status=(arg) is invalid. By default, an invalid value will cause an
35
- # ArgumentError to be raised. Since this may not be optimal in your situation, you can do one of three
36
- # things:
37
- #
38
- # 1) You can set it to 'validation_error'. In this case, the invalid value will be cached and returned on
39
- # subsequent lookups, but the model will fail validation.
40
- # 2) You can specify an instance method to be called in the case of a lookup failure. The method signature is
41
- # as follows:
42
- # <tt>your_lookup_handler(operation, name, name_foreign_key, acts_enumerated_class_name, lookup_value)</tt>
43
- # The 'operation' arg will be either :read or :write. In the case of :read you are expected to return
44
- # something or raise an exception, while in the case of a :write you don't have to return anything. Note that
45
- # there's enough information in the method signature that you can specify one method to handle all lookup
46
- # failures for all has_enumerated fields if you happen to have more than one defined in your model.
47
- # 'NOTE': A nil is always considered to be a valid value for status=(arg) since it's assumed you're trying to
48
- # null out the foreign key. The :on_lookup_failure method will be bypassed.
49
- # 3) You can give it a lambda function. In that case, the lambda needs to accept the ActiveRecord model as
50
- # its first argument, with the rest of the arguments being identical to the signature of the lookup handler
51
- # instance method.
52
- # [:permit_empty_name]
53
- # Setting this to 'true' disables automatic conversion of empty strings to nil. Default is 'false'.
54
- # [:default]
55
- # Setting this option will generate an after_initialize callback to set a default value on the attribute
56
- # unless a non-nil one already exists.
57
- # [:create_scope]
58
- # Setting this option to 'false' will disable automatically creating 'with_enum_attribute' and
59
- # 'exclude_enum_attribute' scope.
60
- #
61
- # === Example
62
- # class Booking < ActiveRecord::Base
63
- # has_enumerated :status,
64
- # :class_name => 'BookingStatus',
65
- # :foreign_key => 'status_id',
66
- # :on_lookup_failure => :optional_instance_method,
67
- # :permit_empty_name => true,
68
- # :default => :unconfirmed,
69
- # :create_cope => false
70
- # end
71
- #
72
- # === Example 2
73
- #
74
- # class Booking < ActiveRecord::Base
75
- # has_enumerated :booking_status,
76
- # :class_name => 'BookingStatus',
77
- # :foreign_key => 'status_id',
78
- # :on_lookup_failure => lambda{ |record, op, attr, fk, cl_name, value|
79
- # # handle lookup failure
80
- # }
81
- # end
82
- def has_enumerated(part_id, options = {})
83
- options.assert_valid_keys( :class_name,
84
- :foreign_key,
85
- :on_lookup_failure,
86
- :permit_empty_name,
87
- :default,
88
- :create_scope )
89
-
90
- reflection = PowerEnum::Reflection::EnumerationReflection.new(part_id, options, self)
91
- self.reflections = self.reflections.merge(part_id => reflection)
92
-
93
- name = part_id.to_s
94
- class_name = reflection.class_name
95
- foreign_key = reflection.foreign_key
96
- failure_opt = options[:on_lookup_failure]
97
- empty_name = options[:permit_empty_name]
98
- create_scope = options[:create_scope]
99
-
100
- failure_handler = get_lookup_failure_handler(failure_opt)
101
-
102
- class_attribute "has_enumerated_#{name}_error_handler"
103
- self.send("has_enumerated_#{name}_error_handler=", failure_handler)
104
-
105
- module_eval( <<-end_eval, __FILE__, __LINE__ )
106
- def #{name}
107
- if @invalid_enum_values && @invalid_enum_values.has_key?(:#{name})
108
- return @invalid_enum_values[:#{name}]
109
- end
110
- rval = #{class_name}.lookup_id(self.#{foreign_key})
111
- if rval.nil? && #{!failure_handler.nil?}
112
- self.class.has_enumerated_#{name}_error_handler.call(self, :read, #{name.inspect}, #{foreign_key.inspect}, #{class_name.inspect}, self.#{foreign_key})
113
- else
114
- rval
115
- end
116
- end
117
-
118
- def #{name}=(arg)
119
- @invalid_enum_values ||= {}
120
-
121
- #{!empty_name ? 'arg = nil if arg.blank?' : ''}
122
- case arg
123
- when #{class_name}
124
- val = #{class_name}.lookup_id(arg.id)
125
- when String
126
- val = #{class_name}.lookup_name(arg)
127
- when Symbol
128
- val = #{class_name}.lookup_name(arg.id2name)
129
- when Fixnum
130
- val = #{class_name}.lookup_id(arg)
131
- when nil
132
- self.#{foreign_key} = nil
133
- @invalid_enum_values.delete :#{name}
134
- return nil
135
- else
136
- raise TypeError, "#{self.name}: #{name}= argument must be a #{class_name}, String, Symbol or Fixnum but got a: \#{arg.class.name}"
137
- end
138
-
139
- if val.nil?
140
- if #{failure_handler.nil?}
141
- raise ArgumentError, "#{self.name}: #{name}= can't assign a #{class_name} for a value of (\#{arg.inspect})"
142
- else
143
- @invalid_enum_values.delete :#{name}
144
- self.class.has_enumerated_#{name}_error_handler.call(self, :write, #{name.inspect}, #{foreign_key.inspect}, #{class_name.inspect}, arg)
145
- end
146
- else
147
- @invalid_enum_values.delete :#{name}
148
- self.#{foreign_key} = val.id
149
- end
150
- end
151
-
152
- alias_method :'#{name}_bak=', :'#{name}='
153
- end_eval
154
-
155
- if failure_opt.to_s == 'validation_error'
156
- module_eval( <<-end_eval, __FILE__, __LINE__ )
157
- validate do
158
- if @invalid_enum_values && @invalid_enum_values.has_key?(:#{name})
159
- errors.add(:#{name}, "is invalid")
160
- end
161
- end
162
-
163
- def validation_error(operation, name, name_foreign_key, acts_enumerated_class_name, lookup_value)
164
- @invalid_enum_values ||= {}
165
- if operation == :write
166
- @invalid_enum_values[name.to_sym] = lookup_value
167
- else
168
- nil
169
- end
170
- end
171
- private :validation_error
172
- end_eval
173
- end
174
-
175
- enumerated_attributes << name
176
-
177
- if options.has_key?(:default)
178
- default = options[:default]
179
- set_default_method = "set_default_value_for_#{name}".to_sym
180
-
181
- after_initialize set_default_method
182
-
183
- define_method set_default_method do
184
- self.send("#{name}=", default) if self.send(name).nil?
185
- end
186
- private set_default_method
187
- end
188
-
189
- unless create_scope == false
190
- module_eval( <<-end_eval, __FILE__, __LINE__)
191
- scope :with_#{name}, lambda { |*args|
192
- ids = args.map{ |arg|
193
- n = #{class_name}[arg]
194
- }
195
- where(:#{foreign_key} => ids)
196
- }
197
- scope :exclude_#{name}, lambda {|*args|
198
- ids = #{class_name}.all - args.map{ |arg|
199
- n = #{class_name}[arg]
200
- }
201
- where(:#{foreign_key} => ids)
202
- }
203
- end_eval
204
-
205
- if (name_p = name.pluralize) != name
206
- module_eval( <<-end_eval, __FILE__, __LINE__)
207
- class << self
208
- alias_method :with_#{name_p}, :with_#{name}
209
- alias_method :exclude_#{name_p}, :exclude_#{name}
210
- end
211
- end_eval
212
- end
213
- end
214
-
215
- end #has_enumerated
216
-
217
- def get_lookup_failure_handler(failure_opt)
218
- if failure_opt.nil?
219
- nil
220
- else
221
- case failure_opt
222
- when Proc
223
- failure_opt
224
- else
225
- lambda { |record, op, attr, fk, cl_name, value|
226
- record.send(failure_opt.to_s, op, attr, fk, cl_name, value)
227
- }
228
- end
229
-
230
- end
231
- end
232
- private :get_lookup_failure_handler
233
-
234
- end #module MacroMethods
235
-
236
- end #module HasEnumerated
237
- end #module Aggregations
238
- end