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.
@@ -0,0 +1,234 @@
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 PowerEnum::HasEnumerated # :nodoc:
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ # Returns a list of all the attributes on the ActiveRecord model which are enumerated.
12
+ def enumerated_attributes
13
+ @enumerated_attributes ||= []
14
+ end
15
+
16
+ # Returns +true+ if +attribute+ is an enumerated attribute, +false+ otherwise.
17
+ def has_enumerated?(attribute)
18
+ return false if attribute.nil?
19
+ enumerated_attributes.include? attribute.to_s
20
+ end
21
+
22
+ # Defines an enumerated attribute with the given name on the model. Also accepts a hash of options as an
23
+ # optional second argument.
24
+ #
25
+ # === Supported options
26
+ # [:class_name]
27
+ # Name of the enum class. By default it is the camelized version of the has_enumerated attribute.
28
+ # [:foreign_key]
29
+ # Explicitly set the foreign key column. By default it's assumed to be your_enumerated_attribute_name_id.
30
+ # [:on_lookup_failure]
31
+ # The :on_lookup_failure option in has_enumerated is there because you may want to create an error handler for
32
+ # situations where the argument passed to status=(arg) is invalid. By default, an invalid value will cause an
33
+ # ArgumentError to be raised. Since this may not be optimal in your situation, you can do one of three
34
+ # things:
35
+ #
36
+ # 1) You can set it to 'validation_error'. In this case, the invalid value will be cached and returned on
37
+ # subsequent lookups, but the model will fail validation.
38
+ # 2) You can specify an instance method to be called in the case of a lookup failure. The method signature is
39
+ # as follows:
40
+ # <tt>your_lookup_handler(operation, name, name_foreign_key, acts_enumerated_class_name, lookup_value)</tt>
41
+ # The 'operation' arg will be either :read or :write. In the case of :read you are expected to return
42
+ # something or raise an exception, while in the case of a :write you don't have to return anything. Note that
43
+ # there's enough information in the method signature that you can specify one method to handle all lookup
44
+ # failures for all has_enumerated fields if you happen to have more than one defined in your model.
45
+ # 'NOTE': A nil is always considered to be a valid value for status=(arg) since it's assumed you're trying to
46
+ # null out the foreign key. The :on_lookup_failure method will be bypassed.
47
+ # 3) You can give it a lambda function. In that case, the lambda needs to accept the ActiveRecord model as
48
+ # its first argument, with the rest of the arguments being identical to the signature of the lookup handler
49
+ # instance method.
50
+ # [:permit_empty_name]
51
+ # Setting this to 'true' disables automatic conversion of empty strings to nil. Default is 'false'.
52
+ # [:default]
53
+ # Setting this option will generate an after_initialize callback to set a default value on the attribute
54
+ # unless a non-nil one already exists.
55
+ # [:create_scope]
56
+ # Setting this option to 'false' will disable automatically creating 'with_enum_attribute' and
57
+ # 'exclude_enum_attribute' scope.
58
+ #
59
+ # === Example
60
+ # class Booking < ActiveRecord::Base
61
+ # has_enumerated :status,
62
+ # :class_name => 'BookingStatus',
63
+ # :foreign_key => 'status_id',
64
+ # :on_lookup_failure => :optional_instance_method,
65
+ # :permit_empty_name => true,
66
+ # :default => :unconfirmed,
67
+ # :create_cope => false
68
+ # end
69
+ #
70
+ # === Example 2
71
+ #
72
+ # class Booking < ActiveRecord::Base
73
+ # has_enumerated :booking_status,
74
+ # :class_name => 'BookingStatus',
75
+ # :foreign_key => 'status_id',
76
+ # :on_lookup_failure => lambda{ |record, op, attr, fk, cl_name, value|
77
+ # # handle lookup failure
78
+ # }
79
+ # end
80
+ def has_enumerated(part_id, options = {})
81
+ options.assert_valid_keys( :class_name,
82
+ :foreign_key,
83
+ :on_lookup_failure,
84
+ :permit_empty_name,
85
+ :default,
86
+ :create_scope )
87
+
88
+ reflection = PowerEnum::Reflection::EnumerationReflection.new(part_id, options, self)
89
+ self.reflections = self.reflections.merge(part_id => reflection)
90
+
91
+ name = part_id.to_s
92
+ class_name = reflection.class_name
93
+ foreign_key = reflection.foreign_key
94
+ failure_opt = options[:on_lookup_failure]
95
+ empty_name = options[:permit_empty_name]
96
+ create_scope = options[:create_scope]
97
+
98
+ failure_handler = get_lookup_failure_handler(failure_opt)
99
+
100
+ class_attribute "has_enumerated_#{name}_error_handler"
101
+ self.send("has_enumerated_#{name}_error_handler=", failure_handler)
102
+
103
+ module_eval( <<-end_eval, __FILE__, __LINE__ )
104
+ def #{name}
105
+ if @invalid_enum_values && @invalid_enum_values.has_key?(:#{name})
106
+ return @invalid_enum_values[:#{name}]
107
+ end
108
+ rval = #{class_name}.lookup_id(self.#{foreign_key})
109
+ if rval.nil? && #{!failure_handler.nil?}
110
+ self.class.has_enumerated_#{name}_error_handler.call(self, :read, #{name.inspect}, #{foreign_key.inspect}, #{class_name.inspect}, self.#{foreign_key})
111
+ else
112
+ rval
113
+ end
114
+ end
115
+
116
+ def #{name}=(arg)
117
+ @invalid_enum_values ||= {}
118
+
119
+ #{!empty_name ? 'arg = nil if arg.blank?' : ''}
120
+ case arg
121
+ when #{class_name}
122
+ val = #{class_name}.lookup_id(arg.id)
123
+ when String
124
+ val = #{class_name}.lookup_name(arg)
125
+ when Symbol
126
+ val = #{class_name}.lookup_name(arg.id2name)
127
+ when Fixnum
128
+ val = #{class_name}.lookup_id(arg)
129
+ when nil
130
+ self.#{foreign_key} = nil
131
+ @invalid_enum_values.delete :#{name}
132
+ return nil
133
+ else
134
+ raise TypeError, "#{self.name}: #{name}= argument must be a #{class_name}, String, Symbol or Fixnum but got a: \#{arg.class.name}"
135
+ end
136
+
137
+ if val.nil?
138
+ if #{failure_handler.nil?}
139
+ raise ArgumentError, "#{self.name}: #{name}= can't assign a #{class_name} for a value of (\#{arg.inspect})"
140
+ else
141
+ @invalid_enum_values.delete :#{name}
142
+ self.class.has_enumerated_#{name}_error_handler.call(self, :write, #{name.inspect}, #{foreign_key.inspect}, #{class_name.inspect}, arg)
143
+ end
144
+ else
145
+ @invalid_enum_values.delete :#{name}
146
+ self.#{foreign_key} = val.id
147
+ end
148
+ end
149
+
150
+ alias_method :'#{name}_bak=', :'#{name}='
151
+ end_eval
152
+
153
+ if failure_opt.to_s == 'validation_error'
154
+ module_eval( <<-end_eval, __FILE__, __LINE__ )
155
+ validate do
156
+ if @invalid_enum_values && @invalid_enum_values.has_key?(:#{name})
157
+ errors.add(:#{name}, "is invalid")
158
+ end
159
+ end
160
+
161
+ def validation_error(operation, name, name_foreign_key, acts_enumerated_class_name, lookup_value)
162
+ @invalid_enum_values ||= {}
163
+ if operation == :write
164
+ @invalid_enum_values[name.to_sym] = lookup_value
165
+ else
166
+ nil
167
+ end
168
+ end
169
+ private :validation_error
170
+ end_eval
171
+ end
172
+
173
+ enumerated_attributes << name
174
+
175
+ if options.has_key?(:default)
176
+ default = options[:default]
177
+ set_default_method = "set_default_value_for_#{name}".to_sym
178
+
179
+ after_initialize set_default_method
180
+
181
+ define_method set_default_method do
182
+ self.send("#{name}=", default) if self.send(name).nil?
183
+ end
184
+ private set_default_method
185
+ end
186
+
187
+ unless create_scope == false
188
+ module_eval( <<-end_eval, __FILE__, __LINE__)
189
+ scope :with_#{name}, lambda { |*args|
190
+ ids = args.map{ |arg|
191
+ n = #{class_name}[arg]
192
+ }
193
+ where(:#{foreign_key} => ids)
194
+ }
195
+ scope :exclude_#{name}, lambda {|*args|
196
+ ids = #{class_name}.all - args.map{ |arg|
197
+ n = #{class_name}[arg]
198
+ }
199
+ where(:#{foreign_key} => ids)
200
+ }
201
+ end_eval
202
+
203
+ if (name_p = name.pluralize) != name
204
+ module_eval( <<-end_eval, __FILE__, __LINE__)
205
+ class << self
206
+ alias_method :with_#{name_p}, :with_#{name}
207
+ alias_method :exclude_#{name_p}, :exclude_#{name}
208
+ end
209
+ end_eval
210
+ end
211
+ end
212
+
213
+ end #has_enumerated
214
+
215
+ def get_lookup_failure_handler(failure_opt) # :nodoc:
216
+ if failure_opt.nil?
217
+ nil
218
+ else
219
+ case failure_opt
220
+ when Proc
221
+ failure_opt
222
+ else
223
+ lambda { |record, op, attr, fk, cl_name, value|
224
+ record.send(failure_opt.to_s, op, attr, fk, cl_name, value)
225
+ }
226
+ end
227
+
228
+ end
229
+ end
230
+ private :get_lookup_failure_handler
231
+
232
+ end #module MacroMethods
233
+
234
+ end #module PowerEnum::HasEnumerated
data/lib/power_enum.rb CHANGED
@@ -6,8 +6,8 @@ class PowerEnum < Rails::Engine
6
6
 
7
7
  initializer 'power_enum' do
8
8
  ActiveSupport.on_load(:active_record) do
9
- include ActiveRecord::Acts::Enumerated
10
- include ActiveRecord::Aggregations::HasEnumerated
9
+ include PowerEnum::Enumerated
10
+ include PowerEnum::HasEnumerated
11
11
  include PowerEnum::Reflection
12
12
 
13
13
  ActiveRecord::ConnectionAdapters.module_eval do
@@ -19,6 +19,22 @@ class PowerEnum < Rails::Engine
19
19
  include PowerEnum::Migration::CommandRecorder
20
20
  end
21
21
  end
22
+
23
+ # patch Module to support VirtualEnumerations
24
+ ::Module.module_eval do
25
+
26
+ alias_method :enumerations_original_const_missing, :const_missing
27
+
28
+ # Override const_missing to see if VirtualEnumerations can create it.
29
+ def const_missing(const_id)
30
+ # let rails have a go at loading it
31
+ enumerations_original_const_missing(const_id)
32
+ rescue NameError
33
+ # now it's our turn
34
+ ActiveRecord::VirtualEnumerations.synthesize_if_defined(const_id) or raise
35
+ end
36
+
37
+ end
22
38
  end
23
39
 
24
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: power_enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.9.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2012-08-29 00:00:00.000000000 Z
15
+ date: 2012-09-11 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: rails
@@ -111,11 +111,8 @@ executables: []
111
111
  extensions: []
112
112
  extra_rdoc_files:
113
113
  - LICENSE
114
- - README.md
114
+ - README.markdown
115
115
  files:
116
- - examples/virtual_enumerations_sample.rb
117
- - lib/active_record/acts/enumerated.rb
118
- - lib/active_record/aggregations/has_enumerated.rb
119
116
  - lib/active_record/virtual_enumerations.rb
120
117
  - lib/generators/enum/USAGE
121
118
  - lib/generators/enum/enum_generator.rb
@@ -123,12 +120,14 @@ files:
123
120
  - lib/generators/enum/templates/rails30_migration.rb.erb
124
121
  - lib/generators/enum/templates/rails31_migration.rb.erb
125
122
  - lib/power_enum.rb
123
+ - lib/power_enum/enumerated.rb
124
+ - lib/power_enum/has_enumerated.rb
126
125
  - lib/power_enum/migration/command_recorder.rb
127
126
  - lib/power_enum/reflection.rb
128
127
  - lib/power_enum/schema/schema_statements.rb
129
128
  - lib/testing/rspec.rb
130
129
  - LICENSE
131
- - README.md
130
+ - README.markdown
132
131
  homepage: http://github.com/albertosaurus/power_enum
133
132
  licenses: []
134
133
  post_install_message:
@@ -143,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
143
142
  version: '0'
144
143
  segments:
145
144
  - 0
146
- hash: -1167487358875782212
145
+ hash: -1309889999666641931
147
146
  required_rubygems_version: !ruby/object:Gem::Requirement
148
147
  none: false
149
148
  requirements:
@@ -1,76 +0,0 @@
1
- # Copyright (c) 2005 Trevor Squires
2
- # Released under the MIT License. See the LICENSE file for more details.
3
-
4
- # Copy this file to Rails.root/config/initializers/virtual_enumerations.rb
5
- # and configure it accordingly.
6
- ActiveRecord::VirtualEnumerations.define do |config|
7
- ###
8
- # USAGE (and don't worry, it doesn't have to be as ugly as this):
9
- # config.define 'ClassName',
10
- # :table_name => 'table', :extends => 'SuperclassName',
11
- # :conditions => ['something = ?', "value"], :order => 'column ASC',
12
- # :on_lookup_failure => :enforce_strict_literals do
13
- # class_evaled_functions
14
- # end
15
- #
16
- # 'ClassName', :table_name and :extends are used to define your virtual class.
17
- # Note that 'ClassName' can be a :symbol or a 'CamelString'.
18
- #
19
- # The :conditions, :order and :on_lookup_failure arguments are passed to
20
- # acts_as_enumerated in your virtual class. See README_ENUMERATIONS for info
21
- # on how acts_as_enumerated works.
22
- #
23
- # The 'do' block will be class_eval'd by your virtual class after it has
24
- # been loaded-on-demand.
25
- #
26
- ###
27
- # Okay, that's pretty long-winded.
28
- # Everything after the initial 'class_name' is optional so for most applications, this
29
- # is about as verbose as you're likely to get:
30
- #
31
- # config.define :booking_status, :order => 'position ASC'
32
- #
33
- # In the above example, ActiveRecord assumes the table will be called 'booking_statuses'
34
- # and the table should have a 'position' column defined.
35
- #
36
- # If you've got a bunch of enumeration classes that share the same optional parameters
37
- # you can pass an array of names as the first argument, saving your fingers from typing
38
- # config.define over and over again:
39
- #
40
- # config.define [:booking_status, :card_issuer], :order => 'position ASC'
41
- #
42
- # You can also take advantage of ActiveRecord's STI:
43
- #
44
- # config.define :enum_record, :order => 'position ASC', :table_name => 'enumrecords'
45
- # config.define [:booking_status, :card_issuer], :extends => 'EnumRecord'
46
- #
47
- # In the above example, all of the records are stored in the table called 'enumrecords'
48
- # and all acts_as_enumerated parameters are automatically inherited by the
49
- # subclasses (although you can override them if you want).
50
- # You can also use :extends to extend a non-virtual model class (that's already in
51
- # your models directory) if that floats your boat.
52
- #
53
- # Finally, that strange optional 'do' block.
54
- # You may be struck by the need to tamper with your virtual enumeration class after
55
- # it has been loaded-on-demand. This can be as simple as blessing it with a
56
- # certain 'act':
57
- #
58
- # config.define :enum_record, :order => 'position ASC' do
59
- # acts_as_list # but see README_ENUMERATIONS for rules about modifying your records
60
- # end
61
- #
62
- # or you may be experimenting with the dark-side... singleton methods
63
- #
64
- # config.define :card_issuer do
65
- # class << self[:visa]; def verify_number(arg); some_code_here; end; end
66
- # class << self[:master_card]; def verify_number(arg); some_other_code_here; end; end
67
- # end
68
- #
69
- # For virtual enumerations, this sort of tampering *has* to be defined in the
70
- # config.define do block. This is because in development mode, rails loads and
71
- # subsequently clobbers your model classes for each request. The 'do' block will
72
- # be evaluated each time your model class is loaded-on-demand.
73
- #
74
- ###
75
-
76
- end