power_enum 0.8.6 → 0.9.1

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.
@@ -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