ar-enums 0.3.0
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/.autotest +2 -0
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +122 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/ar-enums.gemspec +71 -0
- data/examples/internal_enums.rb +27 -0
- data/lib/ar-enums.rb +4 -0
- data/lib/enum.rb +57 -0
- data/lib/enum_block.rb +17 -0
- data/lib/enum_definition.rb +42 -0
- data/lib/enum_field.rb +27 -0
- data/lib/factory.rb +60 -0
- data/lib/metaprogramming_extensions.rb +22 -0
- data/lib/options_helper.rb +23 -0
- data/spec/enum_definition_spec.rb +132 -0
- data/spec/enum_spec.rb +42 -0
- data/spec/factory_spec.rb +100 -0
- data/spec/schema.rb +5 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +29 -0
- metadata +92 -0
data/.autotest
ADDED
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Emma Nicolau
|
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 to
|
9
|
+
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
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF 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.rdoc
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
= ar-enums
|
2
|
+
|
3
|
+
This is a simple solution for defining enumerations in your Rails models.
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
It provides two forms of enumerations:
|
8
|
+
* Internal, i.e. defined within the owner of the enum, for the simple cases.
|
9
|
+
* External, i.e. defined on it's own class, for the complex ones.
|
10
|
+
|
11
|
+
In both cases the enum values are stored in-memory. Let's see some examples of both kinds.
|
12
|
+
|
13
|
+
== Internal enumerations
|
14
|
+
|
15
|
+
Can be defined in three flavours:
|
16
|
+
|
17
|
+
=== Array of values style
|
18
|
+
|
19
|
+
This is the simplest and probably the most frecuent case:
|
20
|
+
|
21
|
+
class TrafficLight < ActiveRecord::Base
|
22
|
+
enum :state, %w[red green yellow]
|
23
|
+
end
|
24
|
+
|
25
|
+
tl = TrafficLight.new(:state => :green)
|
26
|
+
tl.state # => #<TrafficLight::State @name="green", @id=2>
|
27
|
+
tl.state_id # => 2
|
28
|
+
tl.state.green? # => true
|
29
|
+
|
30
|
+
As you can see each enum value is an instance of a dinamically generated class (which inherits from ActiveRecord::Enum) and has an ordinal value, id, autogenerated staring on 1.
|
31
|
+
|
32
|
+
The enumerations array is accessed with a class method:
|
33
|
+
|
34
|
+
TrafficLight.states # => [#<TrafficLight::State @name="red", @id=1>, #<TrafficLight::State @name="green", @id=2>, ...]
|
35
|
+
|
36
|
+
By default when <tt>#to_s</tt> is called on a Enum instance it returns the name titleized, but this behaviour can be overrided. See Displaying Enums section below.
|
37
|
+
|
38
|
+
=== Array of hashes style
|
39
|
+
|
40
|
+
This allows you to define new columns in the Enum value. If the :id is not specified is generated for you.
|
41
|
+
|
42
|
+
class TrafficLight < ActiveRecord::Base
|
43
|
+
enum :state, [
|
44
|
+
{ :name => :red, :stop_traffic => true, :rgb => 0xF00 },
|
45
|
+
{ :name => :green, :stop_traffic => false, :rgb => 0x0F0 }
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
tl = TrafficLight.new(:state => :green)
|
50
|
+
tl.state_id # => 2
|
51
|
+
tl.state.stop_traffic # => false
|
52
|
+
tl.state.rgb # => 0x0F0
|
53
|
+
|
54
|
+
=== Block style
|
55
|
+
|
56
|
+
class TrafficLight < ActiveRecord::Base
|
57
|
+
enum :state do
|
58
|
+
red :stop_traffic => true, :rgb => 0xF00
|
59
|
+
green :stop_traffic => false, :rgb => 0x0F0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
== External enumerations
|
64
|
+
|
65
|
+
When you have shared enumerations or just don't want to clutter the owner model with enums definitions,
|
66
|
+
the enumeration can be on it's own class, as the State enum in the following example:
|
67
|
+
|
68
|
+
class TrafficLight < ActiveRecord::Base
|
69
|
+
enum :state
|
70
|
+
end
|
71
|
+
|
72
|
+
class State < ActiveRecord::Enum
|
73
|
+
enumeration do
|
74
|
+
red :stop_traffic => true, :rgb => 0xF00, :desc => 'Rojo'
|
75
|
+
green :stop_traffic => false, :rgb => 0x0F0, :desc => 'Verde'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
State[:red] # => #<State @name="red", @id=1, ...>
|
80
|
+
State[:red].to_s # => "Rojo"
|
81
|
+
|
82
|
+
Note the block style sintax is exactly the same as in the internal enumerations. In fact any of the 3 styles mencioned above can be used on external enumerations.
|
83
|
+
|
84
|
+
Also note the <tt>#to_s</tt> by default return the value of the <tt>:desc</tt> column. Of course you can override the <tt>#to_s</tt> method as usual.
|
85
|
+
|
86
|
+
Then you can access the enumerations collection from both places:
|
87
|
+
|
88
|
+
TrafficLight.states # => [#<State @name="red", @id=1, ...>, #<State @name="green", @id=2, ...>]
|
89
|
+
States.all # => [#<State @name="red", @id=1, ...>, #<State @name="green", @id=2, ...>]
|
90
|
+
|
91
|
+
The <tt>enum</tt> method accept a <tt>:class_method</tt> option similar to the one in <tt>belongs_to</tt> and others to specify the class of the enum.
|
92
|
+
|
93
|
+
== Displaying Enums
|
94
|
+
|
95
|
+
In the array of values style the default is the name titleized but you can pass a <tt>:label</tt> options to override:
|
96
|
+
|
97
|
+
class TrafficLight < ActiveRecord::Base
|
98
|
+
enum :state, %w[red green yellow], :label => :upcase
|
99
|
+
end
|
100
|
+
|
101
|
+
TrafficLight.states.map(&:to_s) # => ["RED", "GREEN", "YELLOW"]
|
102
|
+
|
103
|
+
If you use the block o array of hashes sintax and add a <tt>:desc</tt> column it will be used as <tt>#to_s</tt>, unless a <tt>:label</tt> option is passed.
|
104
|
+
|
105
|
+
== TODO
|
106
|
+
|
107
|
+
* Remove the dependency with ActiveRecord so it can be used outside Rails.
|
108
|
+
* Allow to store enums in the database.
|
109
|
+
|
110
|
+
== Note on Patches/Pull Requests
|
111
|
+
|
112
|
+
* Fork the project.
|
113
|
+
* Make your feature addition or bug fix.
|
114
|
+
* Add tests for it. This is important so I don't break it in a
|
115
|
+
future version unintentionally.
|
116
|
+
* Commit, do not mess with rakefile, version, or history.
|
117
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
118
|
+
* Send me a pull request. Bonus points for topic branches.
|
119
|
+
|
120
|
+
== Copyright
|
121
|
+
|
122
|
+
Copyright (c) 2010 Emmanuel Nicolau. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "ar-enums"
|
8
|
+
gem.summary = %Q{Enumerations for ActiveRecord models}
|
9
|
+
gem.description = %Q{Provides a simple way for defining enumerations in ActiveRecord models}
|
10
|
+
gem.email = "emmanicolau@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/eeng/ar-enums"
|
12
|
+
gem.authors = ["Emmanuel Nicolau"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "ar-enums #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/ar-enums.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ar-enums}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Emmanuel Nicolau"]
|
12
|
+
s.date = %q{2010-02-11}
|
13
|
+
s.description = %q{Provides a simple way for defining enumerations in ActiveRecord models}
|
14
|
+
s.email = %q{emmanicolau@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".autotest",
|
21
|
+
".document",
|
22
|
+
".gitignore",
|
23
|
+
"LICENSE",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"ar-enums.gemspec",
|
28
|
+
"examples/internal_enums.rb",
|
29
|
+
"lib/ar-enums.rb",
|
30
|
+
"lib/enum.rb",
|
31
|
+
"lib/enum_block.rb",
|
32
|
+
"lib/enum_definition.rb",
|
33
|
+
"lib/enum_field.rb",
|
34
|
+
"lib/factory.rb",
|
35
|
+
"lib/metaprogramming_extensions.rb",
|
36
|
+
"lib/options_helper.rb",
|
37
|
+
"spec/enum_definition_spec.rb",
|
38
|
+
"spec/enum_spec.rb",
|
39
|
+
"spec/factory_spec.rb",
|
40
|
+
"spec/schema.rb",
|
41
|
+
"spec/spec.opts",
|
42
|
+
"spec/spec_helper.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/eeng/ar-enums}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
48
|
+
s.summary = %q{Enumerations for ActiveRecord models}
|
49
|
+
s.test_files = [
|
50
|
+
"spec/enum_definition_spec.rb",
|
51
|
+
"spec/enum_spec.rb",
|
52
|
+
"spec/factory_spec.rb",
|
53
|
+
"spec/schema.rb",
|
54
|
+
"spec/spec_helper.rb",
|
55
|
+
"examples/internal_enums.rb"
|
56
|
+
]
|
57
|
+
|
58
|
+
if s.respond_to? :specification_version then
|
59
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
63
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
66
|
+
end
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
require 'ar-enums'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
6
|
+
load(File.dirname(__FILE__) + "/../spec/schema.rb")
|
7
|
+
|
8
|
+
class TrafficLight < ActiveRecord::Base
|
9
|
+
enum :state, %w[red green yellow]
|
10
|
+
end
|
11
|
+
|
12
|
+
tl = TrafficLight.new(:state => :green)
|
13
|
+
p tl.state # => #<TrafficLight::State @name="green", @id=2>
|
14
|
+
p tl.state_id # => 2
|
15
|
+
p TrafficLight.states.map(&:to_s)
|
16
|
+
|
17
|
+
# class TrafficLight < ActiveRecord::Base
|
18
|
+
# enum :state, [
|
19
|
+
# { :name => :red, :stop_traffic => true, :rgb => 0xF00 },
|
20
|
+
# { :name => :green, :stop_traffic => false, :rgb => 0x0F0 }
|
21
|
+
# ]
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# tl = TrafficLight.new(:state => :green)
|
25
|
+
# p tl.state_id # => 2
|
26
|
+
# p tl.state.stop_traffic # => false
|
27
|
+
# p tl.state.rgb # => 0x0F0
|
data/lib/ar-enums.rb
ADDED
data/lib/enum.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Enum
|
3
|
+
extend ActiveRecord::Enumerations::OptionsHelper
|
4
|
+
|
5
|
+
attr_reader :id, :name, :extra_columns
|
6
|
+
class_inheritable_accessor :label_method
|
7
|
+
|
8
|
+
def initialize attrs = {}
|
9
|
+
@id = attrs.delete(:id).to_i
|
10
|
+
@name = attrs.delete(:name).to_s
|
11
|
+
@extra_columns = attrs.reject { |k, _| [:enum_class, :on_style_not_matched].include?(k) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create_from value, values, options
|
15
|
+
required_attrs = case value
|
16
|
+
when String, Symbol
|
17
|
+
{ :name => value }
|
18
|
+
else
|
19
|
+
value
|
20
|
+
end
|
21
|
+
required_attrs[:id] ||= values.index(value) + 1
|
22
|
+
new options.merge(required_attrs)
|
23
|
+
end
|
24
|
+
|
25
|
+
def == other
|
26
|
+
return id == other.id if other.is_a?(Enum)
|
27
|
+
[id.to_s, name].include?(other.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
try_labelize(self, :desc) || try_labelize(name, :titleize)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_sym
|
35
|
+
name.to_sym
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.enumeration *config, &block
|
39
|
+
add_option config, :enum_class => self
|
40
|
+
define_enums_getter ActiveRecord::Enumerations::Factory.make_enums(*config, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.[] name_or_id
|
44
|
+
all.detect { |enum| enum == name_or_id }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def self.define_enums_getter enums
|
49
|
+
cattr_accessor :all
|
50
|
+
self.all = enums
|
51
|
+
end
|
52
|
+
|
53
|
+
def try_labelize object, default_method
|
54
|
+
object.respond_to?(label_method) && object.send(label_method) or object.respond_to?(default_method) && object.send(default_method)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/enum_block.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Enumerations
|
3
|
+
class EnumBlock
|
4
|
+
def initialize options = {}
|
5
|
+
@enums = []
|
6
|
+
@last_id = 0
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing method, args = {}
|
11
|
+
attrs = @options.merge(args).merge(:name => method)
|
12
|
+
attrs[:id] ||= @last_id += 1
|
13
|
+
@enums << @options[:enum_class].new(attrs)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Enumerations
|
3
|
+
def self.included(base)
|
4
|
+
base.send :extend, ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
include ActiveRecord::Enumerations::OptionsHelper
|
9
|
+
|
10
|
+
def enum field_name, *config, &block
|
11
|
+
field = EnumField.new field_name
|
12
|
+
enum_class = Class.new Enum
|
13
|
+
const_set field.name.camelize, enum_class
|
14
|
+
add_options config, :enum_class => enum_class, :on_style_not_matched => asume_external_style(field)
|
15
|
+
enums = Factory.new.make_enums *config, &block
|
16
|
+
define_enums_getter field, enums
|
17
|
+
define_enum_getter_and_setter field, enums
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def asume_external_style field
|
22
|
+
lambda { |options| field.external_class(options).all }
|
23
|
+
end
|
24
|
+
|
25
|
+
def define_enums_getter field, enums
|
26
|
+
define_class_method(field.enums_getter) { enums }
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_enum_getter_and_setter field, enums
|
30
|
+
define_method field.name do
|
31
|
+
enums.detect { |enum| enum.id == read_attribute(field.foreign_key) }
|
32
|
+
end
|
33
|
+
|
34
|
+
define_method "#{field.name}=" do |value|
|
35
|
+
write_attribute field.foreign_key, enums.detect { |enum| enum == value }.try(:id)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ActiveRecord::Base.send :include, ActiveRecord::Enumerations
|
data/lib/enum_field.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Enumerations
|
3
|
+
class EnumField
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize name
|
7
|
+
@name = name.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def enums_getter
|
11
|
+
name.pluralize
|
12
|
+
end
|
13
|
+
|
14
|
+
def enums_setter
|
15
|
+
"#{enums_getter}="
|
16
|
+
end
|
17
|
+
|
18
|
+
def foreign_key
|
19
|
+
"#{name}_id"
|
20
|
+
end
|
21
|
+
|
22
|
+
def external_class options = {}
|
23
|
+
(options.delete(:class_name) || name).camelize.constantize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/factory.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Enumerations
|
3
|
+
class Factory
|
4
|
+
include OptionsHelper
|
5
|
+
|
6
|
+
def self.make_enums *config, &block
|
7
|
+
new.make_enums *config, &block
|
8
|
+
end
|
9
|
+
|
10
|
+
def make_enums *config, &block
|
11
|
+
values, options = extract_values_and_options config
|
12
|
+
options[:enum_class].label_method = options.delete(:label) || :desc
|
13
|
+
create_enums(values, options, &block).tap do |enums|
|
14
|
+
define_question_methods options[:enum_class], enums
|
15
|
+
define_extra_columns_methods options[:enum_class], enums
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def create_enums values, options, &block
|
21
|
+
enums = if block_given?
|
22
|
+
block_style options, &block
|
23
|
+
elsif values.any?
|
24
|
+
array_of_values_or_hashes_style values, options
|
25
|
+
elsif options[:on_style_not_matched]
|
26
|
+
options[:on_style_not_matched].call options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def block_style options, &block
|
31
|
+
EnumBlock.new(options).instance_eval(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def array_of_values_or_hashes_style values, options
|
35
|
+
values.map { |value| options[:enum_class].create_from(value, values, options) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def define_question_methods enum_class, enums
|
39
|
+
enums.each do |e|
|
40
|
+
enum_class.class_eval %Q{
|
41
|
+
def #{e.name}?
|
42
|
+
self == :#{e.name}
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def define_extra_columns_methods enum_class, enums
|
49
|
+
extra_columns_names = enums.map(&:extra_columns).map(&:keys).flatten.uniq
|
50
|
+
extra_columns_names.each do |ecn|
|
51
|
+
enum_class.class_eval %Q{
|
52
|
+
def #{ecn}
|
53
|
+
extra_columns[:#{ecn}]
|
54
|
+
end
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Object
|
2
|
+
# The hidden singleton lurks behind everyone
|
3
|
+
def metaclass; class << self; self; end; end
|
4
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
5
|
+
|
6
|
+
# Adds methods to a metaclass
|
7
|
+
def meta_def name, &blk
|
8
|
+
meta_eval { define_method name, &blk }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Defines an instance method within a class
|
12
|
+
def class_def name, &blk
|
13
|
+
class_eval { define_method name, &blk }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Defines a class method
|
17
|
+
def define_class_method name, &blk
|
18
|
+
self.class.instance_eval do
|
19
|
+
define_method name, &blk
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Enumerations
|
3
|
+
module OptionsHelper
|
4
|
+
def add_option config, option
|
5
|
+
new_config = if config.first.is_a?(Array)
|
6
|
+
[config[0], (config[1] || {}).merge(option)]
|
7
|
+
else
|
8
|
+
[(config[0] || {}).merge(option)]
|
9
|
+
end
|
10
|
+
config.replace new_config
|
11
|
+
end
|
12
|
+
alias_method :add_options, :add_option
|
13
|
+
|
14
|
+
def extract_values_and_options config
|
15
|
+
if config.first.is_a?(Array)
|
16
|
+
[config[0], config[1] || {}]
|
17
|
+
else
|
18
|
+
[[], config[0] || {}]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Internal enumerations" do
|
4
|
+
def define_traffic_light *options
|
5
|
+
define_model_class 'TrafficLight' do
|
6
|
+
enum *options
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
define_traffic_light :state, %w[red green yellow]
|
12
|
+
end
|
13
|
+
|
14
|
+
context "getter and setter" do
|
15
|
+
it "getter should return an Enum" do
|
16
|
+
TrafficLight.new(:state_id => 3).state.should be_enum_with(:id => 3, :name => 'yellow')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should store ordinal by default as foreign key" do
|
20
|
+
TrafficLight.new(:state => :green).state_id.should == 2
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should store nil if enum doesn't exists" do
|
24
|
+
TrafficLight.new(:state => :black).state_id.should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should allow to set enum with symbol" do
|
28
|
+
TrafficLight.new(:state => :red).state.should == :red
|
29
|
+
TrafficLight.new(:state => :green).state.should == :green
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should allow to set enum with string" do
|
33
|
+
TrafficLight.new(:state => 'red').state.should == :red
|
34
|
+
TrafficLight.new(:state => 'green').state.should == :green
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should allow to set enum with ordinal" do
|
38
|
+
TrafficLight.new(:state_id => 1).state.should == :red
|
39
|
+
TrafficLight.new(:state_id => 2).state.should == :green
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "options" do
|
44
|
+
it "should pass the options to the factory" do
|
45
|
+
define_traffic_light :state, %w[green red], :label => :upcase
|
46
|
+
TrafficLight.new(:state => :green).state.to_s.should == 'GREEN'
|
47
|
+
TrafficLight.new(:state => :red).state.to_s.should == 'RED'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "question methods" do
|
52
|
+
before do
|
53
|
+
define_traffic_light :state, %w[green red]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should provide question method" do
|
57
|
+
TrafficLight.new(:state => :green).state.should be_green
|
58
|
+
TrafficLight.new(:state => :green).state.should_not be_red
|
59
|
+
TrafficLight.new(:state => :red).state.should_not be_green
|
60
|
+
TrafficLight.new(:state => :red).state.should be_red
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should raise error if tested with inexistant enum" do
|
64
|
+
lambda { TrafficLight.new(:state => :green).state.blue? }.should raise_error(NameError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "block style should also provide question method" do
|
68
|
+
define_model_class 'TrafficLight' do
|
69
|
+
enum :state do
|
70
|
+
green
|
71
|
+
red
|
72
|
+
end
|
73
|
+
end
|
74
|
+
TrafficLight.new(:state => :green).state.should be_green
|
75
|
+
TrafficLight.new(:state => :green).state.should_not be_red
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should be instances of a new subclass of Enum" do
|
80
|
+
TrafficLight.states.first.should be_a(TrafficLight::State)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "External enumerations" do
|
85
|
+
before do
|
86
|
+
define_model_class 'State', 'ActiveRecord::Enum' do
|
87
|
+
enumeration do
|
88
|
+
green :rgb => 0x0F0
|
89
|
+
red :rgb => 0xF00
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
define_model_class 'TrafficLight' do
|
94
|
+
enum :state
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "enums creation" do
|
99
|
+
it "should allow to define enumerations on it's own class" do
|
100
|
+
TrafficLight.new(:state => :red).state.should be_enum_with(:name => 'red', :rgb => 0xF00, :id => 2)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should be posible to access all enums from withing the owner" do
|
104
|
+
TrafficLight.states.should equal(State.all)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should accept :class_name options to override de class of the external enum" do
|
108
|
+
define_model_class 'TrafficLight' do
|
109
|
+
enum :state_on_weekdays, :class_name => 'State'
|
110
|
+
enum :state_on_weekends, :class_name => 'State'
|
111
|
+
end
|
112
|
+
TrafficLight.state_on_weekdays.should equal(State.all)
|
113
|
+
TrafficLight.state_on_weekends.should equal(State.all)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "external enums should be instances of the subclass of Enum" do
|
117
|
+
State.all.each { |s| s.should be_a(State) }
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should be posible to define new methods in Enum subclass" do
|
121
|
+
define_model_class 'State', 'ActiveRecord::Enum' do
|
122
|
+
enumeration do
|
123
|
+
green :factor => 1
|
124
|
+
red :factor => 2
|
125
|
+
end
|
126
|
+
|
127
|
+
def double_factor() factor * 2 end
|
128
|
+
end
|
129
|
+
State.all.map(&:double_factor).should == [2, 4]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/spec/enum_spec.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Enum" do
|
4
|
+
it "should provide :to_sym method returning name as symbols" do
|
5
|
+
ActiveRecord::Enum.new(:name => :green).to_sym.should == :green
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should store extra columns as a hash without the :enum_class that is passed from other classes" do
|
9
|
+
ActiveRecord::Enum.new(:name => :green, :factor => 1.5, :enum_class => Class.new).extra_columns.should == { :factor => 1.5 }
|
10
|
+
end
|
11
|
+
|
12
|
+
context "External enums" do
|
13
|
+
before do
|
14
|
+
define_model_class 'Color', 'ActiveRecord::Enum' do
|
15
|
+
enumeration do
|
16
|
+
red :rgb => 0xF00
|
17
|
+
green :rgb => 0x0F0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
define_model_class 'State', 'ActiveRecord::Enum' do
|
22
|
+
enumeration do
|
23
|
+
on :id => 80
|
24
|
+
off :id => 90
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should provide :all method to access the enums" do
|
30
|
+
Color.all[0].should be_enum_with(:name => 'red', :rgb => 0xF00)
|
31
|
+
Color.all[1].should be_enum_with(:name => 'green', :rgb => 0x0F0)
|
32
|
+
State.all[0].should be_enum_with(:name => 'on', :id => 80)
|
33
|
+
State.all[1].should be_enum_with(:name => 'off', :id => 90)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should provide [] method to access the enums" do
|
37
|
+
Color[:red].should be_enum_with(:name => 'red')
|
38
|
+
Color['green'].should be_enum_with(:name => 'green')
|
39
|
+
Color[2].should be_enum_with(:name => 'green')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "Enums creation styles" do
|
4
|
+
include ActiveRecord::Enumerations::OptionsHelper
|
5
|
+
|
6
|
+
def make_enums *config, &block
|
7
|
+
add_option config, :enum_class => ActiveRecord::Enum
|
8
|
+
ActiveRecord::Enumerations::Factory.make_enums *config, &block
|
9
|
+
end
|
10
|
+
|
11
|
+
context "array of values style" do
|
12
|
+
it "should generate ids" do
|
13
|
+
enums = make_enums %w[red green blue]
|
14
|
+
enums[0].should be_enum_with(:id => 1, :name => 'red')
|
15
|
+
enums[1].should be_enum_with(:id => 2, :name => 'green')
|
16
|
+
enums[2].should be_enum_with(:id => 3, :name => 'blue')
|
17
|
+
end
|
18
|
+
|
19
|
+
it "default to_s should be name titleized" do
|
20
|
+
make_enums(%w[green red]).map(&:to_s).should == %w[Green Red]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should override default to_s" do
|
24
|
+
make_enums(%w[green red], :label => :upcase).map(&:to_s).should == %w[GREEN RED]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "array of hashes style" do
|
29
|
+
it "should accept ids if provided" do
|
30
|
+
enums = make_enums [{ :id => 20, :name => :red }, { :id => 10, :name => :green }]
|
31
|
+
enums[0].should be_enum_with(:id => 20, :name => 'red')
|
32
|
+
enums[1].should be_enum_with(:id => 10, :name => 'green')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should generate ids if not provided" do
|
36
|
+
enums = make_enums [{ :name => :red }, { :name => :green }]
|
37
|
+
enums[0].should be_enum_with(:id => 1, :name => 'red')
|
38
|
+
enums[1].should be_enum_with(:id => 2, :name => 'green')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "default to_s should be :desc column" do
|
42
|
+
enums = make_enums [{ :name => :red, :desc => 'Rojo' }, { :name => :green, :desc => 'Verde' }]
|
43
|
+
enums.map(&:to_s).should == %w[Rojo Verde]
|
44
|
+
end
|
45
|
+
|
46
|
+
it ":label options can be a method to call on name" do
|
47
|
+
enums = make_enums [{ :name => :red }, { :name => :green }], :label => :upcase
|
48
|
+
enums.map(&:to_s).should == %w[RED GREEN]
|
49
|
+
end
|
50
|
+
|
51
|
+
it ":label option can be a enum column" do
|
52
|
+
enums = make_enums [{ :name => :red, :title => 'Rojo' }, { :name => :green, :title => 'Verde' }], :label => :title
|
53
|
+
enums.map(&:to_s).should == %w[Rojo Verde]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should accept extra columns" do
|
57
|
+
enums = make_enums [
|
58
|
+
{ :name => :red, :factor => 1.5, :stop_traffic => true },
|
59
|
+
{ :name => :green, :factor => 2.5, :stop_traffic => false }
|
60
|
+
]
|
61
|
+
enums.map(&:factor).should == [1.5, 2.5]
|
62
|
+
enums.map(&:stop_traffic).should == [true, false]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "block style" do
|
67
|
+
it "can be created with a block" do
|
68
|
+
enums = make_enums do
|
69
|
+
red :rgb => 0xF00
|
70
|
+
green :rgb => 0x0F0
|
71
|
+
end
|
72
|
+
enums[0].should be_enum_with(:id => 1, :name => 'red', :rgb => 0xF00)
|
73
|
+
enums[1].should be_enum_with(:id => 2, :name => 'green', :rgb => 0x0F0)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should accept :label option" do
|
77
|
+
enums = make_enums :label => :title do
|
78
|
+
red :title => 'Rojo'
|
79
|
+
green :title => 'Verde'
|
80
|
+
end
|
81
|
+
enums.map(&:to_s).should == %w[Rojo Verde]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should accept extra columns" do
|
85
|
+
enums = make_enums do
|
86
|
+
red :factor => 1.5
|
87
|
+
green :factor => 2.5
|
88
|
+
end
|
89
|
+
enums.map(&:factor).should == [1.5, 2.5]
|
90
|
+
end
|
91
|
+
|
92
|
+
it "when extra column is empty should return nil" do
|
93
|
+
enums = make_enums do
|
94
|
+
red :factor => 1.5
|
95
|
+
green
|
96
|
+
end
|
97
|
+
enums.map(&:factor).should == [1.5, nil]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/spec/schema.rb
ADDED
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ar-enums'
|
5
|
+
require 'spec'
|
6
|
+
require 'spec/autorun'
|
7
|
+
|
8
|
+
Spec::Runner.configure do |config|
|
9
|
+
config.before :suite do
|
10
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
11
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
def define_model_class(name = "TestClass", parent_class_name = "ActiveRecord::Base", &block)
|
15
|
+
Object.send(:remove_const, name) rescue nil
|
16
|
+
eval("class #{name} < #{parent_class_name}; end", TOPLEVEL_BINDING)
|
17
|
+
klass = eval(name, TOPLEVEL_BINDING)
|
18
|
+
klass.class_eval(&block) if block_given?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Spec::Matchers.define :be_enum_with do |expected_attrs|
|
23
|
+
match do |enum|
|
24
|
+
enum.should be_a(ActiveRecord::Enum)
|
25
|
+
expected_attrs.each do |atrib, expected_value|
|
26
|
+
enum.send(atrib).should == expected_value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ar-enums
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emmanuel Nicolau
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-11 00:00:00 -02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: Provides a simple way for defining enumerations in ActiveRecord models
|
26
|
+
email: emmanicolau@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .autotest
|
36
|
+
- .document
|
37
|
+
- .gitignore
|
38
|
+
- LICENSE
|
39
|
+
- README.rdoc
|
40
|
+
- Rakefile
|
41
|
+
- VERSION
|
42
|
+
- ar-enums.gemspec
|
43
|
+
- examples/internal_enums.rb
|
44
|
+
- lib/ar-enums.rb
|
45
|
+
- lib/enum.rb
|
46
|
+
- lib/enum_block.rb
|
47
|
+
- lib/enum_definition.rb
|
48
|
+
- lib/enum_field.rb
|
49
|
+
- lib/factory.rb
|
50
|
+
- lib/metaprogramming_extensions.rb
|
51
|
+
- lib/options_helper.rb
|
52
|
+
- spec/enum_definition_spec.rb
|
53
|
+
- spec/enum_spec.rb
|
54
|
+
- spec/factory_spec.rb
|
55
|
+
- spec/schema.rb
|
56
|
+
- spec/spec.opts
|
57
|
+
- spec/spec_helper.rb
|
58
|
+
has_rdoc: true
|
59
|
+
homepage: http://github.com/eeng/ar-enums
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options:
|
64
|
+
- --charset=UTF-8
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: "0"
|
78
|
+
version:
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.3.5
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: Enumerations for ActiveRecord models
|
86
|
+
test_files:
|
87
|
+
- spec/enum_definition_spec.rb
|
88
|
+
- spec/enum_spec.rb
|
89
|
+
- spec/factory_spec.rb
|
90
|
+
- spec/schema.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
- examples/internal_enums.rb
|