power_enum 0.2.3
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/LICENSE +20 -0
- data/README.md +198 -0
- data/examples/virtual_enumerations_sample.rb +76 -0
- data/lib/active_record/acts/enumerated.rb +219 -0
- data/lib/active_record/aggregations/has_enumerated.rb +61 -0
- data/lib/active_record/virtual_enumerations.rb +68 -0
- data/lib/enumerations_mixin.rb +12 -0
- metadata +146 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2005 Trevor Squires
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject
|
|
9
|
+
to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
18
|
+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
19
|
+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Power Enum
|
|
2
|
+
|
|
3
|
+
Initial Version Copyright (c) 2005 Trevor Squires
|
|
4
|
+
Rails 3 Updates Copyright (c) 2010 Pivotal Labs
|
|
5
|
+
Additional Updates Copyright (c) 2011 Arthur Shagall and Sergey Potapov
|
|
6
|
+
Released under the MIT License. See the LICENSE file for more details.
|
|
7
|
+
|
|
8
|
+
## What is this?:
|
|
9
|
+
|
|
10
|
+
Power Enum allows you to treat instances of your
|
|
11
|
+
ActiveRecord models as though they were an enumeration of values.
|
|
12
|
+
|
|
13
|
+
Power Enum is built on top of the Rails 3 modernization made by the fine folks at Protocool https://github.com/protocool/enumerations_mixin
|
|
14
|
+
to the original plugin by Trevor Squires located at https://github.com/protocool/enumerations_mixin. While the core ideas remain,
|
|
15
|
+
|
|
16
|
+
At it's most basic level, it allows you to say things along the lines of:
|
|
17
|
+
|
|
18
|
+
booking = Booking.new(:status => BookingStatus[:provisional])
|
|
19
|
+
booking.update_attribute(:status, BookingStatus[:confirmed])
|
|
20
|
+
|
|
21
|
+
Booking.find :first,
|
|
22
|
+
:conditions => ['status_id = ?', BookingStatus[:provisional].id]
|
|
23
|
+
|
|
24
|
+
BookingStatus.all.collect {|status|, [status.name, status.id]}
|
|
25
|
+
|
|
26
|
+
See "How to use it" below for more information.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
To use this version, add the gem to your Gemfile
|
|
31
|
+
|
|
32
|
+
gem 'enumerations_mixin', :git => 'git://github.com/albertosaurus/enumerations_mixin.git'
|
|
33
|
+
|
|
34
|
+
## Gem Contents
|
|
35
|
+
|
|
36
|
+
This package adds two mixins and a helper to Rails' ActiveRecord:
|
|
37
|
+
|
|
38
|
+
<code>acts_as_enumerated</code> provides capabilities to treat your model and its records as an enumeration. At a minimum, the database table for an acts_as_enumerated must contain an 'id' column and a 'name' column. All instances for the acts_as_enumerated model are cached in memory.
|
|
39
|
+
|
|
40
|
+
<code>has_enumerated</code> adds methods to your ActiveRecord model for setting and retrieving enumerated values using an associated acts_as_enumerated model.
|
|
41
|
+
|
|
42
|
+
There is also an <code>ActiveRecord::VirtualEnumerations</code> helper module to create 'virtual' acts_as_enumerated models which helps to avoid cluttering up your models directory with acts_as_enumerated classes.
|
|
43
|
+
|
|
44
|
+
## How to use it
|
|
45
|
+
|
|
46
|
+
In the following example, we'll look at a Booking that can have several types of statuses.
|
|
47
|
+
|
|
48
|
+
### migration
|
|
49
|
+
|
|
50
|
+
create_table :booking_statuses do |t|
|
|
51
|
+
t.string :name
|
|
52
|
+
|
|
53
|
+
t.timestamps
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
create_table :bookings do |t|
|
|
57
|
+
t.integer :status_id
|
|
58
|
+
|
|
59
|
+
t.timestamps
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Ideally, you would use a gem of some sort to handle foreign keys.
|
|
63
|
+
execute "ALTER TABLE bookings ADD 'bookings_bookings_status_id_fk' FOREIGN KEY (status_id) REFERENCES booking_statuses (id);"
|
|
64
|
+
|
|
65
|
+
### acts_as_enumerated
|
|
66
|
+
|
|
67
|
+
class BookingStatus < ActiveRecord::Base
|
|
68
|
+
acts_as_enumerated :conditions => 'optional_sql_conditions',
|
|
69
|
+
:order => 'optional_sql_orderby',
|
|
70
|
+
:on_lookup_failure => :optional_class_method,
|
|
71
|
+
:name_column => 'optional_name_column' #If required, may override the default name column
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
With that, your BookingStatus class will have the following methods defined:
|
|
75
|
+
|
|
76
|
+
#### Class Methods
|
|
77
|
+
|
|
78
|
+
##### []
|
|
79
|
+
|
|
80
|
+
<code>BookingStatus[arg]</code> performs a lookup the BookingStatus instance for arg. The arg value can be a 'string' or a :symbol, in which case the lookup will be against the BookingStatus.name field. Alternatively arg can be a Fixnum, in which case the lookup will be against the BookingStatus.id field.
|
|
81
|
+
|
|
82
|
+
The <code>:on_lookup_failure</code> option specifies the name of a class method to invoke when the [] method is unable to locate a BookingStatus record for arg. The default is the built-in :enforce_none which returns nil. There are also built-ins for :enforce_strict (raise and exception regardless of the type for arg), :enforce_strict_literals (raises an exception if the arg is a Fixnum or Symbol), :enforce_strict_ids (raises and exception if the arg is a Fixnum) and :enforce_strict_symbols (raises an exception if the arg is a Symbol).
|
|
83
|
+
|
|
84
|
+
The purpose of the :on_lookup_failure option is that a) under some circumstances a lookup failure is a Bad Thing and action should be taken, therefore b) a fallback action should be easily configurable.
|
|
85
|
+
|
|
86
|
+
##### all
|
|
87
|
+
|
|
88
|
+
<code>BookingStatus.all</code> returns an array of all BookingStatus records that match the :conditions specified in acts_as_enumerated, in the order specified by :order.
|
|
89
|
+
|
|
90
|
+
#### Instance Methods
|
|
91
|
+
|
|
92
|
+
Each enumeration model gets the following instance methods.
|
|
93
|
+
|
|
94
|
+
##### ===(arg)
|
|
95
|
+
|
|
96
|
+
<code>BookingStatus[:foo] === arg</code> returns true if <code>BookingStatus[:foo] === BookingStatus[arg]</code> returns true if arg is Fixnum, String, or Symbol. If arg is an Array, will compare every element of the array and return true if any element return true for ===.
|
|
97
|
+
|
|
98
|
+
You should note that defining an :on_lookup_failure method that raises an exception will cause <code>===</code> to also raise an exception for any lookup failure of <code>BookingStatus</arg>.
|
|
99
|
+
|
|
100
|
+
<code>like?</code> is aliased to <code>===<code>
|
|
101
|
+
|
|
102
|
+
##### in?(*list)
|
|
103
|
+
|
|
104
|
+
Returns true if any element in the list returns true for <code>===(arg)</code>, false otherwise.
|
|
105
|
+
|
|
106
|
+
##### name
|
|
107
|
+
|
|
108
|
+
Returns the 'name' of the enum, i.e. the value in the <code>:name_column</code> attribute of the enumeration model.
|
|
109
|
+
|
|
110
|
+
##### name_sym
|
|
111
|
+
|
|
112
|
+
Returns the symbol representation of the name of the enum. <code>BookingStatus[:foo].name_sym</code> returns :foo.
|
|
113
|
+
|
|
114
|
+
#### Notes
|
|
115
|
+
|
|
116
|
+
acts_as_enumerated records are considered immutable. By default you cannot create/alter/destroy instances because they are cached in memory. Because of Rails' process-based model it is not safe to allow updating acts_as_enumerated records as the caches will get out of sync.
|
|
117
|
+
|
|
118
|
+
However, one instance where updating the models *should* be allowed is if you are using seeds.rb to seed initial values into the database.
|
|
119
|
+
|
|
120
|
+
Using the above example you would do the following:
|
|
121
|
+
|
|
122
|
+
BookingStatus.enumeration_model_updates_permitted = true
|
|
123
|
+
['pending', 'confirmed', 'canceled'].each do | status_name |
|
|
124
|
+
BookingStatus.create( :name => status_name )
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
A <code>:presence</code> and <code>:uniqueness</code> validation is automatically defined on each model.
|
|
128
|
+
|
|
129
|
+
### has_enumerated
|
|
130
|
+
|
|
131
|
+
First of all, note that you *could* specify the relationship to an acts_as_enumerated class using the belongs_to association. However, has_enumerated is preferable because you aren't really associated to the enumerated value, you are *aggregating* it. As such, the has_enumerated macro behaves more like an aggregation than an association.
|
|
132
|
+
|
|
133
|
+
class Booking < ActiveRecord::Base
|
|
134
|
+
has_enumerated :status, :class_name => 'BookingStatus',
|
|
135
|
+
:foreign_key => 'status_id',
|
|
136
|
+
:on_lookup_failure => :optional_instance_method
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
By default, the foreign key is interpreted to be the name of your has_enumerated field (in this case 'status') plus '_id'. Additionally, the default value for :class_name is the camel-ized version of the name for your has_enumerated field. :on_lookup_failure is explained below.
|
|
140
|
+
|
|
141
|
+
With that, your Booking class will have the following methods defined:
|
|
142
|
+
|
|
143
|
+
#### status
|
|
144
|
+
|
|
145
|
+
Returns the BookingStatus with an id that matches the value in the Booking.status_id.
|
|
146
|
+
|
|
147
|
+
#### status=
|
|
148
|
+
|
|
149
|
+
Sets the value for Booking.status_id using the id of the BookingStatus instance passed as an argument. As a short-hand, you can also pass it the 'name' of a BookingStatus instance, either as a 'string' or :symbol, or pass in the id directly.
|
|
150
|
+
|
|
151
|
+
example:
|
|
152
|
+
|
|
153
|
+
mybooking.status = :confirmed
|
|
154
|
+
|
|
155
|
+
The <code>:on_lookup_failure</code> option in has_enumerated is there because you may want to create an error handler for situations where the argument passed to status= is invalid. By default, an invalid value will cause an ArgumentError to be raised.
|
|
156
|
+
|
|
157
|
+
Of course, this may not be optimal in your situation. In this case you can specify an *instance* method to be called in the case of a lookup failure. The method signature is as follows:
|
|
158
|
+
|
|
159
|
+
your_lookup_handler(operation, name, name_foreign_key, acts_enumerated_class_name, lookup_value)
|
|
160
|
+
|
|
161
|
+
The 'operation' arg will be either :read or :write. In the case of :read you are expected to return something or raise an exception, while in the case of a :write you don't have to return anything.
|
|
162
|
+
|
|
163
|
+
Note that there's enough information in the method signature that you can specify one method to handle all lookup failures for all has_enumerated fields if you happen to have more than one defined in your model.
|
|
164
|
+
|
|
165
|
+
NOTE: A nil is always considered to be a valid value for status= since it's assumed you're trying to null out the foreign key, therefore the <code>:on_lookup_failure</code> will be bypassed.
|
|
166
|
+
|
|
167
|
+
### ActiveRecord::VirtualEnumerations
|
|
168
|
+
|
|
169
|
+
For the most part, your acts_as_enumerated classes will do nothing more than just act as enumerated.
|
|
170
|
+
|
|
171
|
+
In that case there isn't much point cluttering up your models directory with those class files. You can use ActiveRecord::VirtualEnumerations to reduce that clutter.
|
|
172
|
+
|
|
173
|
+
Copy virtual_enumerations_sample.rb to Rails.root/config/initializers/virtual_enumerations.rb and configure it accordingly.
|
|
174
|
+
|
|
175
|
+
See virtual_enumerations_sample.rb in the examples directory of this gem for a full description.
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
## How to run tests
|
|
179
|
+
|
|
180
|
+
Go to dummy project:
|
|
181
|
+
|
|
182
|
+
cd ./spec/dummy
|
|
183
|
+
|
|
184
|
+
Run migrations for test environment:
|
|
185
|
+
|
|
186
|
+
RAILS_ENV=test rake db:migrate
|
|
187
|
+
|
|
188
|
+
If you're using Rails 3.1, you should do this instead:
|
|
189
|
+
|
|
190
|
+
RAILS_ENV=test bundle exec rake db:migrate
|
|
191
|
+
|
|
192
|
+
Go back to gem root directory:
|
|
193
|
+
|
|
194
|
+
cd ../../
|
|
195
|
+
|
|
196
|
+
And finally run tests:
|
|
197
|
+
|
|
198
|
+
rake spec
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Copyright (c) 2005 Trevor Squires
|
|
2
|
+
# Released under the MIT License. See the LICENSE file for more details.
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Acts
|
|
6
|
+
module Enumerated
|
|
7
|
+
def self.append_features(base)
|
|
8
|
+
super
|
|
9
|
+
base.extend(MacroMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module MacroMethods
|
|
13
|
+
def acts_as_enumerated(options = {})
|
|
14
|
+
valid_keys = [:conditions, :order, :on_lookup_failure, :name_column]
|
|
15
|
+
options.assert_valid_keys(*valid_keys)
|
|
16
|
+
|
|
17
|
+
valid_keys.each do |key|
|
|
18
|
+
write_inheritable_attribute("acts_enumerated_#{key.to_s}".to_sym, options[key]) if options.has_key? key
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
name_column = if options.has_key?(:name_column) then
|
|
22
|
+
options[:name_column].to_s.to_sym
|
|
23
|
+
else
|
|
24
|
+
:name
|
|
25
|
+
end
|
|
26
|
+
write_inheritable_attribute(:acts_enumerated_name_column, name_column)
|
|
27
|
+
|
|
28
|
+
unless self.is_a? ActiveRecord::Acts::Enumerated::ClassMethods
|
|
29
|
+
extend ActiveRecord::Acts::Enumerated::ClassMethods
|
|
30
|
+
|
|
31
|
+
class_eval do
|
|
32
|
+
include ActiveRecord::Acts::Enumerated::InstanceMethods
|
|
33
|
+
|
|
34
|
+
before_save :enumeration_model_update
|
|
35
|
+
before_destroy :enumeration_model_update
|
|
36
|
+
validates name_column, :presence => true, :uniqueness => true
|
|
37
|
+
|
|
38
|
+
define_method :name do
|
|
39
|
+
read_attribute( name_column )
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module ClassMethods
|
|
47
|
+
attr_accessor :enumeration_model_updates_permitted
|
|
48
|
+
|
|
49
|
+
def all
|
|
50
|
+
return @all if @all
|
|
51
|
+
@all = find(:all,
|
|
52
|
+
:conditions => read_inheritable_attribute(:acts_enumerated_conditions),
|
|
53
|
+
:order => read_inheritable_attribute(:acts_enumerated_order)
|
|
54
|
+
).collect{|val| val.freeze}.freeze
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Enum lookup by Symbol, String, or id.
|
|
58
|
+
def [](arg)
|
|
59
|
+
case arg
|
|
60
|
+
when Symbol
|
|
61
|
+
return_val = lookup_name(arg.id2name) and return return_val
|
|
62
|
+
when String
|
|
63
|
+
return_val = lookup_name(arg) and return return_val
|
|
64
|
+
when Fixnum
|
|
65
|
+
return_val = lookup_id(arg) and return return_val
|
|
66
|
+
when nil
|
|
67
|
+
nil
|
|
68
|
+
else
|
|
69
|
+
raise TypeError, "#{self.name}[]: argument should be a String, Symbol or Fixnum but got a: #{arg.class.name}"
|
|
70
|
+
end
|
|
71
|
+
self.send((read_inheritable_attribute(:acts_enumerated_on_lookup_failure) || :enforce_none), arg)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Enum lookup by id
|
|
75
|
+
def lookup_id(arg)
|
|
76
|
+
all_by_id[arg]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Enum lookup by String
|
|
80
|
+
def lookup_name(arg)
|
|
81
|
+
all_by_name[arg]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Returns true if the enum lookup by the given Symbol, String or id would have returned a value, false otherwise.
|
|
85
|
+
def include?(arg)
|
|
86
|
+
case arg
|
|
87
|
+
when Symbol
|
|
88
|
+
!lookup_name(arg.id2name).nil?
|
|
89
|
+
when String
|
|
90
|
+
!lookup_name(arg).nil?
|
|
91
|
+
when Fixnum
|
|
92
|
+
!lookup_id(arg).nil?
|
|
93
|
+
when self
|
|
94
|
+
possible_match = lookup_id(arg.id)
|
|
95
|
+
!possible_match.nil? && possible_match == arg
|
|
96
|
+
else
|
|
97
|
+
false
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# NOTE: purging the cache is sort of pointless because
|
|
102
|
+
# of the per-process rails model.
|
|
103
|
+
# By default this blows up noisily just in case you try to be more
|
|
104
|
+
# clever than rails allows.
|
|
105
|
+
# For those times (like in Migrations) when you really do want to
|
|
106
|
+
# alter the records you can silence the carping by setting
|
|
107
|
+
# enumeration_model_updates_permitted to true.
|
|
108
|
+
def purge_enumerations_cache
|
|
109
|
+
unless self.enumeration_model_updates_permitted
|
|
110
|
+
raise "#{self.name}: cache purging disabled for your protection"
|
|
111
|
+
end
|
|
112
|
+
@all = @all_by_name = @all_by_id = nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns the name of the column this enum uses as the basic underlying value.
|
|
116
|
+
def name_column
|
|
117
|
+
@name_column ||= read_inheritable_attribute( :acts_enumerated_name_column )
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def all_by_id
|
|
123
|
+
return @all_by_id if @all_by_id
|
|
124
|
+
@all_by_id = all.inject({}) { |memo, item| memo[item.id] = item; memo }.freeze
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def all_by_name
|
|
128
|
+
return @all_by_name if @all_by_name
|
|
129
|
+
begin
|
|
130
|
+
@all_by_name = all.inject({}) { |memo, item| memo[item.name] = item; memo }.freeze
|
|
131
|
+
rescue NoMethodError => err
|
|
132
|
+
if err.name == name_column
|
|
133
|
+
raise TypeError, "#{self.name}: you need to define a '#{name_column}' column in the table '#{table_name}'"
|
|
134
|
+
end
|
|
135
|
+
raise
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def enforce_none(arg)
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def enforce_strict(arg)
|
|
144
|
+
raise_record_not_found(arg)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def enforce_strict_literals(arg)
|
|
148
|
+
raise_record_not_found(arg) if (Fixnum === arg) || (Symbol === arg)
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def enforce_strict_ids(arg)
|
|
153
|
+
raise_record_not_found(arg) if Fixnum === arg
|
|
154
|
+
nil
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def enforce_strict_symbols(arg)
|
|
158
|
+
raise_record_not_found(arg) if Symbol === arg
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def raise_record_not_found(arg)
|
|
163
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find a #{self.name} identified by (#{arg.inspect})"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
module InstanceMethods
|
|
169
|
+
def ===(arg)
|
|
170
|
+
case arg
|
|
171
|
+
when nil
|
|
172
|
+
false
|
|
173
|
+
when Symbol, String, Fixnum
|
|
174
|
+
return self == self.class[arg]
|
|
175
|
+
when Array
|
|
176
|
+
return self.in?(*arg)
|
|
177
|
+
else
|
|
178
|
+
super
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
alias_method :like?, :===
|
|
183
|
+
|
|
184
|
+
# Returns true if any element in the list returns true for ===(arg), false otherwise.
|
|
185
|
+
def in?(*list)
|
|
186
|
+
for item in list
|
|
187
|
+
self === item and return true
|
|
188
|
+
end
|
|
189
|
+
false
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns the symbol representation of the name of the enum. BookingStatus[:foo].name_sym returns :foo.
|
|
193
|
+
def name_sym
|
|
194
|
+
self.name.to_sym
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
# NOTE: updating the models that back an acts_as_enumerated is
|
|
200
|
+
# rather dangerous because of rails' per-process model.
|
|
201
|
+
# The cached values could get out of synch between processes
|
|
202
|
+
# and rather than completely disallow changes I make you jump
|
|
203
|
+
# through an extra hoop just in case you're defining your enumeration
|
|
204
|
+
# values in Migrations. I.e. set enumeration_model_updates_permitted = true
|
|
205
|
+
def enumeration_model_update
|
|
206
|
+
if self.class.enumeration_model_updates_permitted
|
|
207
|
+
self.class.purge_enumerations_cache
|
|
208
|
+
true
|
|
209
|
+
else
|
|
210
|
+
# Ugh. This just seems hack-ish. I wonder if there's a better way.
|
|
211
|
+
self.errors.add(self.class.name_column, "changes to acts_as_enumeration model instances are not permitted")
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Copyright (c) 2005 Trevor Squires
|
|
2
|
+
# Released under the MIT License. See the LICENSE file for more details.
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Aggregations # :nodoc:
|
|
6
|
+
module HasEnumerated # :nodoc:
|
|
7
|
+
def self.append_features(base)
|
|
8
|
+
super
|
|
9
|
+
base.extend(MacroMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module MacroMethods
|
|
13
|
+
def has_enumerated(part_id, options = {})
|
|
14
|
+
options.assert_valid_keys(:class_name, :foreign_key, :on_lookup_failure)
|
|
15
|
+
|
|
16
|
+
name = part_id.id2name
|
|
17
|
+
class_name = (options[:class_name] || name).to_s.camelize
|
|
18
|
+
foreign_key = (options[:foreign_key] || "#{name}_id").to_s
|
|
19
|
+
failure = options[:on_lookup_failure]
|
|
20
|
+
|
|
21
|
+
module_eval <<-end_eval
|
|
22
|
+
def #{name}
|
|
23
|
+
rval = #{class_name}.lookup_id(self.#{foreign_key})
|
|
24
|
+
if rval.nil? && #{!failure.nil?}
|
|
25
|
+
return self.send(#{failure.inspect}, :read, #{name.inspect}, #{foreign_key.inspect}, #{class_name.inspect}, self.#{foreign_key})
|
|
26
|
+
end
|
|
27
|
+
return rval
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def #{name}=(arg)
|
|
31
|
+
case arg
|
|
32
|
+
when #{class_name}
|
|
33
|
+
val = #{class_name}.lookup_id(arg.id)
|
|
34
|
+
when String
|
|
35
|
+
val = #{class_name}.lookup_name(arg)
|
|
36
|
+
when Symbol
|
|
37
|
+
val = #{class_name}.lookup_name(arg.id2name)
|
|
38
|
+
when Fixnum
|
|
39
|
+
val = #{class_name}.lookup_id(arg)
|
|
40
|
+
when nil
|
|
41
|
+
self.#{foreign_key} = nil
|
|
42
|
+
return nil
|
|
43
|
+
else
|
|
44
|
+
raise TypeError, "#{self.name}: #{name}= argument must be a #{class_name}, String, Symbol or Fixnum but got a: \#{arg.class.name}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if val.nil?
|
|
48
|
+
if #{failure.nil?}
|
|
49
|
+
raise ArgumentError, "#{self.name}: #{name}= can't assign a #{class_name} for a value of (\#{arg.inspect})"
|
|
50
|
+
end
|
|
51
|
+
self.send(#{failure.inspect}, :write, #{name.inspect}, #{foreign_key.inspect}, #{class_name.inspect}, arg)
|
|
52
|
+
else
|
|
53
|
+
self.#{foreign_key} = val.id
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end_eval
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) 2005 Trevor Squires
|
|
2
|
+
# Released under the MIT License. See the LICENSE file for more details.
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module VirtualEnumerations # :nodoc:
|
|
6
|
+
class << self
|
|
7
|
+
def define
|
|
8
|
+
raise ArgumentError, "#{self.name}: must pass a block to define()" unless block_given?
|
|
9
|
+
config = ActiveRecord::VirtualEnumerations::Config.new
|
|
10
|
+
yield config
|
|
11
|
+
@config = config # we only overwrite config if no exceptions were thrown
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def synthesize_if_defined(const)
|
|
15
|
+
options = @config[const]
|
|
16
|
+
return nil unless options
|
|
17
|
+
class_def = <<-end_eval
|
|
18
|
+
class #{const} < #{options[:extends]}
|
|
19
|
+
acts_as_enumerated :conditions => #{options[:conditions].inspect},
|
|
20
|
+
:order => #{options[:order].inspect},
|
|
21
|
+
:on_lookup_failure => #{options[:on_lookup_failure].inspect}
|
|
22
|
+
set_table_name(#{options[:table_name].inspect}) unless #{options[:table_name].nil?}
|
|
23
|
+
end
|
|
24
|
+
end_eval
|
|
25
|
+
eval(class_def, TOPLEVEL_BINDING)
|
|
26
|
+
rval = const_get(const)
|
|
27
|
+
if options[:post_synth_block]
|
|
28
|
+
rval.class_eval(&options[:post_synth_block])
|
|
29
|
+
end
|
|
30
|
+
return rval
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Config
|
|
35
|
+
def initialize
|
|
36
|
+
@enumeration_defs = {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def define(arg, options = {}, &synth_block)
|
|
40
|
+
(arg.is_a?(Array) ? arg : [arg]).each do |class_name|
|
|
41
|
+
camel_name = class_name.to_s.camelize
|
|
42
|
+
raise ArgumentError, "ActiveRecord::VirtualEnumerations.define - invalid class_name argument (#{class_name.inspect})" if camel_name.blank?
|
|
43
|
+
raise ArgumentError, "ActiveRecord::VirtualEnumerations.define - class_name already defined (#{camel_name})" if @enumeration_defs[camel_name.to_sym]
|
|
44
|
+
options.assert_valid_keys(:table_name, :extends, :conditions, :order, :on_lookup_failure)
|
|
45
|
+
enum_def = options.clone
|
|
46
|
+
enum_def[:extends] ||= "ActiveRecord::Base"
|
|
47
|
+
enum_def[:post_synth_block] = synth_block
|
|
48
|
+
@enumeration_defs[camel_name.to_sym] = enum_def
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def [](arg)
|
|
53
|
+
@enumeration_defs[arg]
|
|
54
|
+
end
|
|
55
|
+
end #class Config
|
|
56
|
+
end #module VirtualEnumerations
|
|
57
|
+
end #module ActiveRecord
|
|
58
|
+
|
|
59
|
+
class Module # :nodoc:
|
|
60
|
+
alias_method :enumerations_original_const_missing, :const_missing
|
|
61
|
+
def const_missing(const_id)
|
|
62
|
+
# let rails have a go at loading it
|
|
63
|
+
enumerations_original_const_missing(const_id)
|
|
64
|
+
rescue NameError
|
|
65
|
+
# now it's our turn
|
|
66
|
+
ActiveRecord::VirtualEnumerations.synthesize_if_defined(const_id) or raise
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "rails"
|
|
2
|
+
|
|
3
|
+
class EnumerationsMixin < Rails::Engine
|
|
4
|
+
config.autoload_paths << File.expand_path(File.join(__FILE__, "../"))
|
|
5
|
+
|
|
6
|
+
initializer 'enumerations_mixin' do
|
|
7
|
+
ActiveSupport.on_load(:active_record) do
|
|
8
|
+
include ActiveRecord::Acts::Enumerated
|
|
9
|
+
include ActiveRecord::Aggregations::HasEnumerated
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: power_enum
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.3
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Trevor Squires
|
|
9
|
+
- Pivotal Labs
|
|
10
|
+
- Arthur Shagall
|
|
11
|
+
- Sergey Potapov
|
|
12
|
+
autorequire:
|
|
13
|
+
bindir: bin
|
|
14
|
+
cert_chain: []
|
|
15
|
+
date: 2011-09-18 00:00:00.000000000Z
|
|
16
|
+
dependencies:
|
|
17
|
+
- !ruby/object:Gem::Dependency
|
|
18
|
+
name: rails
|
|
19
|
+
requirement: &23271100 !ruby/object:Gem::Requirement
|
|
20
|
+
none: false
|
|
21
|
+
requirements:
|
|
22
|
+
- - ! '>='
|
|
23
|
+
- !ruby/object:Gem::Version
|
|
24
|
+
version: 3.0.0
|
|
25
|
+
type: :runtime
|
|
26
|
+
prerelease: false
|
|
27
|
+
version_requirements: *23271100
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: jeweler
|
|
30
|
+
requirement: &23270620 !ruby/object:Gem::Requirement
|
|
31
|
+
none: false
|
|
32
|
+
requirements:
|
|
33
|
+
- - ! '>='
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '0'
|
|
36
|
+
type: :development
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: *23270620
|
|
39
|
+
- !ruby/object:Gem::Dependency
|
|
40
|
+
name: rspec
|
|
41
|
+
requirement: &23270100 !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ! '>='
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: *23270100
|
|
50
|
+
- !ruby/object:Gem::Dependency
|
|
51
|
+
name: sqlite3
|
|
52
|
+
requirement: &23269600 !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ! '>='
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '0'
|
|
58
|
+
type: :development
|
|
59
|
+
prerelease: false
|
|
60
|
+
version_requirements: *23269600
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: rails
|
|
63
|
+
requirement: &23269080 !ruby/object:Gem::Requirement
|
|
64
|
+
none: false
|
|
65
|
+
requirements:
|
|
66
|
+
- - ! '>='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 3.0.0
|
|
69
|
+
type: :runtime
|
|
70
|
+
prerelease: false
|
|
71
|
+
version_requirements: *23269080
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: sqlite3
|
|
74
|
+
requirement: &23268540 !ruby/object:Gem::Requirement
|
|
75
|
+
none: false
|
|
76
|
+
requirements:
|
|
77
|
+
- - ! '>='
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
type: :development
|
|
81
|
+
prerelease: false
|
|
82
|
+
version_requirements: *23268540
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rspec
|
|
85
|
+
requirement: &23267980 !ruby/object:Gem::Requirement
|
|
86
|
+
none: false
|
|
87
|
+
requirements:
|
|
88
|
+
- - ! '>='
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '0'
|
|
91
|
+
type: :development
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: *23267980
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: jeweler
|
|
96
|
+
requirement: &23267260 !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ! '>='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: *23267260
|
|
105
|
+
description: Allows you to treat instances of your ActiveRecord models as though they
|
|
106
|
+
were an enumeration of values
|
|
107
|
+
email: arthur.shagall@gmail.com
|
|
108
|
+
executables: []
|
|
109
|
+
extensions: []
|
|
110
|
+
extra_rdoc_files:
|
|
111
|
+
- LICENSE
|
|
112
|
+
- README.md
|
|
113
|
+
files:
|
|
114
|
+
- examples/virtual_enumerations_sample.rb
|
|
115
|
+
- lib/active_record/acts/enumerated.rb
|
|
116
|
+
- lib/active_record/aggregations/has_enumerated.rb
|
|
117
|
+
- lib/active_record/virtual_enumerations.rb
|
|
118
|
+
- lib/enumerations_mixin.rb
|
|
119
|
+
- LICENSE
|
|
120
|
+
- README.md
|
|
121
|
+
homepage: http://github.com/albertosaurus/enumerations_mixin
|
|
122
|
+
licenses: []
|
|
123
|
+
post_install_message:
|
|
124
|
+
rdoc_options: []
|
|
125
|
+
require_paths:
|
|
126
|
+
- lib
|
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
|
+
none: false
|
|
129
|
+
requirements:
|
|
130
|
+
- - ! '>='
|
|
131
|
+
- !ruby/object:Gem::Version
|
|
132
|
+
version: '0'
|
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
|
+
none: false
|
|
135
|
+
requirements:
|
|
136
|
+
- - ! '>='
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
requirements: []
|
|
140
|
+
rubyforge_project:
|
|
141
|
+
rubygems_version: 1.8.10
|
|
142
|
+
signing_key:
|
|
143
|
+
specification_version: 3
|
|
144
|
+
summary: Allows you to treat instances of your ActiveRecord models as though they
|
|
145
|
+
were an enumeration of values
|
|
146
|
+
test_files: []
|