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