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 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: []