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.
- data/{README.md → README.markdown} +315 -157
- data/lib/active_record/virtual_enumerations.rb +132 -48
- data/lib/power_enum/enumerated.rb +405 -0
- data/lib/power_enum/has_enumerated.rb +234 -0
- data/lib/power_enum.rb +18 -2
- metadata +7 -8
- data/examples/virtual_enumerations_sample.rb +0 -76
- data/lib/active_record/acts/enumerated.rb +0 -395
- data/lib/active_record/aggregations/has_enumerated.rb +0 -238
@@ -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
|
10
|
-
include
|
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.
|
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-
|
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.
|
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.
|
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: -
|
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
|