yinum 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/README.md +155 -0
- data/Rakefile +1 -0
- data/lib/enum_value.rb +72 -0
- data/lib/helpers.rb +49 -0
- data/lib/yinum.rb +52 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/yinum_spec.rb +57 -0
- data/yinum.gemspec +19 -0
- metadata +59 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
yinum (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
rspec (2.12.0)
|
11
|
+
rspec-core (~> 2.12.0)
|
12
|
+
rspec-expectations (~> 2.12.0)
|
13
|
+
rspec-mocks (~> 2.12.0)
|
14
|
+
rspec-core (2.12.1)
|
15
|
+
rspec-expectations (2.12.0)
|
16
|
+
diff-lcs (~> 1.1.3)
|
17
|
+
rspec-mocks (2.12.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
yinum!
|
24
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Enum
|
2
|
+
|
3
|
+
A straightforward implementation of an enum for Ruby (on Rails).
|
4
|
+
|
5
|
+
The straightforward usage looks like this:
|
6
|
+
|
7
|
+
COLORS = Enum.new(:COLORS, :red => 1, :green => 2, :blue => 3)
|
8
|
+
COLORS.red
|
9
|
+
=> COLORS.red
|
10
|
+
COLORS.red == 1 && COLORS.red == :red
|
11
|
+
=> true
|
12
|
+
|
13
|
+
## Getting started
|
14
|
+
|
15
|
+
$ gem install enum
|
16
|
+
|
17
|
+
Or in your Gemfile:
|
18
|
+
|
19
|
+
gem "enum"
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Creating an enum:
|
24
|
+
|
25
|
+
FRUITS = Enum.new(:FRUITS, :apple => 1, :orange => 2)
|
26
|
+
=> FRUITS(:apple => 1, :orange => 2)
|
27
|
+
|
28
|
+
Creating an enum with a parent class:
|
29
|
+
|
30
|
+
class Car
|
31
|
+
COLORS = Enum.new(:COLORS, Car, :red => 1, :black => 2)
|
32
|
+
end
|
33
|
+
=> Car::COLORS(:red => 1, :black => 2)
|
34
|
+
|
35
|
+
Another way, with the helper method:
|
36
|
+
|
37
|
+
class Car
|
38
|
+
enum :COLORS, 1 => :red, 2 => :black
|
39
|
+
end
|
40
|
+
=> Car::COLORS(:red => 1, :black => 2)
|
41
|
+
|
42
|
+
(Can go either KEY => VALUE or VALUE => KEY as long as the key is a Symbol or the value is a Numeric).
|
43
|
+
|
44
|
+
Getting enum values:
|
45
|
+
|
46
|
+
FRUITS.apple
|
47
|
+
=> FRUITS.apple
|
48
|
+
FRUITS[:apple]
|
49
|
+
=> FRUITS.apple
|
50
|
+
FRUITS[1]
|
51
|
+
=> FRUITS.apple
|
52
|
+
|
53
|
+
Comparing enum values:
|
54
|
+
|
55
|
+
fruit = FRUITS.orange
|
56
|
+
=> FRUITS.orange
|
57
|
+
fruit == 2
|
58
|
+
=> true
|
59
|
+
fruit == :orange
|
60
|
+
=> true
|
61
|
+
fruit.apple?
|
62
|
+
=> false
|
63
|
+
|
64
|
+
The enum value is actually a delegate to the value with a special inspect and comparison:
|
65
|
+
|
66
|
+
fruit = FRUITS.orange
|
67
|
+
=> FRUITS.orange
|
68
|
+
fruit.to_i
|
69
|
+
=> 2
|
70
|
+
fruit + 1
|
71
|
+
=> 3
|
72
|
+
fruit > FRUITS.apple
|
73
|
+
=> true
|
74
|
+
|
75
|
+
How the enum gets along with Rails, assuming the following model:
|
76
|
+
|
77
|
+
# == Schema Info
|
78
|
+
#
|
79
|
+
# Table name: cars
|
80
|
+
#
|
81
|
+
# id :integer(11) not null, primary key
|
82
|
+
# color :integer(11)
|
83
|
+
#
|
84
|
+
|
85
|
+
class Car < ActiveRecord::Base
|
86
|
+
enum_column :color, :COLORS, :red => 1, :black => 2
|
87
|
+
end
|
88
|
+
|
89
|
+
Now the `color` column is always read and written using the enum value feature:
|
90
|
+
|
91
|
+
Car.create! :color => :red
|
92
|
+
=> #<Car id: 1, color: 1>
|
93
|
+
Car.last.color.red?
|
94
|
+
=> true
|
95
|
+
|
96
|
+
This also creates a `validates\_inclusion\_of` with `allow\_nil => true` to prevent having bad values.
|
97
|
+
|
98
|
+
Adding another `:scoped => true` option before the enum values allows automatic generation of scopes and question
|
99
|
+
methods on the model as follows:
|
100
|
+
|
101
|
+
class Car < ActiveRecord::Base
|
102
|
+
enum_column :color, :COLORS, { :scoped => true }, :red => 1, :black => 2
|
103
|
+
end
|
104
|
+
Car.red.to_sql
|
105
|
+
=> "SELECT `cars`.* FROM `cars` WHERE `cars`.`color` = 1"
|
106
|
+
Car.last.red?
|
107
|
+
=> true
|
108
|
+
|
109
|
+
Last but not least, automatic translation lookup.
|
110
|
+
Given the following `config/locales/en.yml`:
|
111
|
+
|
112
|
+
en:
|
113
|
+
enums:
|
114
|
+
fruits:
|
115
|
+
apple: Green Apple
|
116
|
+
orange: Orange Color
|
117
|
+
colors:
|
118
|
+
red: Shiny Red
|
119
|
+
black: Blackest Black
|
120
|
+
car:
|
121
|
+
colors:
|
122
|
+
black: Actually, white
|
123
|
+
|
124
|
+
The following will occur:
|
125
|
+
|
126
|
+
FRUITS.apple.t
|
127
|
+
=> "Green Apple"
|
128
|
+
FRUITS[2].t
|
129
|
+
=> "Orange Color"
|
130
|
+
Car::COLORS.red
|
131
|
+
=> "Shiny Red"
|
132
|
+
Car::COLORS.black
|
133
|
+
=> "Actually, white"
|
134
|
+
|
135
|
+
When the enum is given a parent, the class's name is used in the translation.
|
136
|
+
If the translation is missing, it fallbacks to the translation without the class's name.
|
137
|
+
|
138
|
+
All enum names (usually CONSTANT\_LIKE) and parent class names are converted to snakecase.
|
139
|
+
|
140
|
+
== Limitations
|
141
|
+
|
142
|
+
Since the `===` operator is called on the when value, this syntax cannot be used:
|
143
|
+
|
144
|
+
case fruit
|
145
|
+
when :red then "This will never happen!"
|
146
|
+
end
|
147
|
+
|
148
|
+
The following should be used instead:
|
149
|
+
|
150
|
+
case fruit
|
151
|
+
when FRUITS.red then "This is better..."
|
152
|
+
end
|
153
|
+
|
154
|
+
This is because I had trouble overriding the `===` operator of the Symbol class.
|
155
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/enum_value.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
class EnumValue < BasicObject
|
2
|
+
attr_reader :enum, :name, :value
|
3
|
+
|
4
|
+
def initialize(enum, name, value)
|
5
|
+
@enum, @name, @value = enum, name, value
|
6
|
+
end
|
7
|
+
|
8
|
+
def enum_value?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def inspect
|
13
|
+
"#{@enum.to_s}.#{@name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
@name == other or @value == other
|
18
|
+
end
|
19
|
+
|
20
|
+
def ===(other)
|
21
|
+
@name === other or @value === other
|
22
|
+
end
|
23
|
+
|
24
|
+
def t(options={})
|
25
|
+
scope_without_klass = "enums.#{const_to_translation(@enum.name)}"
|
26
|
+
if @enum.klass
|
27
|
+
scope_with_klass = "enums.#{const_to_translation(@enum.klass.name)}.#{const_to_translation(@enum.name)}"
|
28
|
+
return "I18n not available: #{scope_with_klass}.#{@name}" unless defined?(::I18n)
|
29
|
+
|
30
|
+
::I18n.t(@name,
|
31
|
+
# prefer scope with klass
|
32
|
+
options.reverse_merge(:scope => scope_with_klass,
|
33
|
+
:default => ::I18n.t(@name,
|
34
|
+
# but if not, use without
|
35
|
+
options.reverse_merge(:scope => scope_without_klass,
|
36
|
+
# but! if not, return scope with klass error
|
37
|
+
:default => ::I18n.t(@name,
|
38
|
+
options.reverse_merge(:scope => scope_with_klass))))))
|
39
|
+
else
|
40
|
+
return "I18n not available: #{scope_without_klass}.#{@name}" unless defined?(::I18n)
|
41
|
+
|
42
|
+
::I18n.t(@name, options.reverse_merge(:scope => scope_without_klass))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def methods
|
47
|
+
super + @value.methods
|
48
|
+
end
|
49
|
+
|
50
|
+
def method_missing(method, *args, &block)
|
51
|
+
match = method.to_s.match(/^(.+)\?$/)
|
52
|
+
enum_name = match[1].to_sym if match
|
53
|
+
if @enum.names.include?(enum_name)
|
54
|
+
@name.==(enum_name, *args, &block)
|
55
|
+
elsif @value.respond_to?(method)
|
56
|
+
@value.send(method, *args, &block)
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def const_to_translation(name)
|
64
|
+
# From Rails' ActiveSupport String#underscore
|
65
|
+
name.to_s.
|
66
|
+
gsub(/::/, '.').
|
67
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
68
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
69
|
+
tr("-", "_").
|
70
|
+
downcase
|
71
|
+
end
|
72
|
+
end
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module EnumGenerator
|
2
|
+
def enum(name, hash)
|
3
|
+
const_set name, Enum.new(name, self, hash)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class Object
|
8
|
+
include EnumGenerator
|
9
|
+
end
|
10
|
+
|
11
|
+
module EnumColumns
|
12
|
+
# Bind a column to an enum by:
|
13
|
+
# Generating attribute reader and writer to convert to EnumValue.
|
14
|
+
# Creating a validation for the attribute so it must have valid enum values (allowing nil).
|
15
|
+
# If :scoped => true, generates scopes and questioning methods for every name in the enum.
|
16
|
+
# If given a enum name (a symbol) and hash, also creates the enum.
|
17
|
+
def enum_column(attr, name_or_enum, options={}, hash=nil)
|
18
|
+
# generating or getting the enum
|
19
|
+
if name_or_enum.is_a?(Symbol)
|
20
|
+
# the first hash is either options or the hash if the options are missing
|
21
|
+
hash, options = options, {} if hash.nil?
|
22
|
+
# generating the enum if the hash is not empty
|
23
|
+
enum name_or_enum, hash if hash.any?
|
24
|
+
|
25
|
+
e = const_get(name_or_enum)
|
26
|
+
else
|
27
|
+
e = name_or_enum
|
28
|
+
end
|
29
|
+
# attribute reader
|
30
|
+
define_method(attr) { v = super(); (v.nil? or e.values.exclude?(v)) ? v : e[v] }
|
31
|
+
# attribute writer
|
32
|
+
define_method("#{attr}=") { |v| v.nil? ? super(v) : super(e[v]) }
|
33
|
+
# validation
|
34
|
+
validates_inclusion_of attr, :in => e.values, :allow_nil => true
|
35
|
+
if options[:scoped]
|
36
|
+
# generating scopes and questioning methods
|
37
|
+
e.by_name.each do |n, ev|
|
38
|
+
scope n, where(attr => ev)
|
39
|
+
define_method("#{n}?") { self[attr] == ev }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if defined?(ActiveRecord)
|
46
|
+
class ActiveRecord::Base
|
47
|
+
extend EnumColumns
|
48
|
+
end
|
49
|
+
end
|
data/lib/yinum.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'enum_value'
|
2
|
+
|
3
|
+
class Enum
|
4
|
+
attr_reader :klass, :name, :by_name, :by_value
|
5
|
+
|
6
|
+
def initialize(name, klass=nil, hash={})
|
7
|
+
klass, hash = nil, klass if klass.is_a?(Hash)
|
8
|
+
@name, @klass = name, klass
|
9
|
+
map_hash(hash)
|
10
|
+
generate_methods
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@klass ? "#{@klass.name}::#{@name}" : @name.to_s
|
15
|
+
end
|
16
|
+
def inspect
|
17
|
+
"#{to_s}(#{@by_name.map { |n,ev| "#{n.inspect} => #{ev.value.inspect}"}.join(", ")})"
|
18
|
+
end
|
19
|
+
|
20
|
+
def names
|
21
|
+
@by_name.keys
|
22
|
+
end
|
23
|
+
def values
|
24
|
+
@by_value.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](name_or_value)
|
28
|
+
ev = @by_name[name_or_value] || @by_value[name_or_value]
|
29
|
+
raise "#{inspect} does not know #{name_or_value.inspect}" if ev.nil?
|
30
|
+
ev
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def map_hash(hash)
|
35
|
+
@by_name = {}
|
36
|
+
@by_value = {}
|
37
|
+
hash.each do |n, v|
|
38
|
+
n, v = v, n if v.is_a?(Symbol) or n.is_a?(Numeric)
|
39
|
+
raise "duplicate enum name #{n} for #{to_s}" if @by_name.has_key?(n)
|
40
|
+
raise "duplicate enum value #{v} for #{to_s}.#{n}" if @by_value.has_key?(v)
|
41
|
+
raise "value can't be nil for #{to_s}.#{n}" if v.nil?
|
42
|
+
@by_name[n] = @by_value[v] = EnumValue.new(self, n, v)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
def generate_methods
|
46
|
+
names.each do |name|
|
47
|
+
define_singleton_method(name) { self[name] }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
require 'helpers'
|
data/spec/spec_helper.rb
ADDED
data/spec/yinum_spec.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Enum do
|
4
|
+
context "keys on left and right" do
|
5
|
+
before { @enum = Enum.new(:MY_COLORS, :red => 1, 2 => :blue) }
|
6
|
+
subject { @enum }
|
7
|
+
|
8
|
+
its(:names) { should have(2).items }
|
9
|
+
its(:names) { should include(:red, :blue) }
|
10
|
+
its(:values) { should have(2).items }
|
11
|
+
its(:values) { should include(1, 2) }
|
12
|
+
end # context "keys on left and right"
|
13
|
+
|
14
|
+
context "no parent" do
|
15
|
+
before { @enum = Enum.new(:MY_COLORS, :red => 1, :blue => 2) }
|
16
|
+
subject { @enum }
|
17
|
+
|
18
|
+
its(:to_s) { should == "MY_COLORS" }
|
19
|
+
its(:inspect) { should == "MY_COLORS(:red => 1, :blue => 2)" }
|
20
|
+
|
21
|
+
context "enum value" do
|
22
|
+
specify { @enum.red.inspect.should == "MY_COLORS.red" }
|
23
|
+
specify { @enum.red.t.should == "I18n not available: enums.my_colors.red" }
|
24
|
+
end # context "enum value"
|
25
|
+
end # context "no parent"
|
26
|
+
|
27
|
+
context "with parent" do
|
28
|
+
before { @enum = Enum.new(:MY_COLORS, Object, :red => 1, :blue => 2) }
|
29
|
+
subject { @enum }
|
30
|
+
|
31
|
+
its(:to_s) { should == "Object::MY_COLORS" }
|
32
|
+
its(:inspect) { should == "Object::MY_COLORS(:red => 1, :blue => 2)" }
|
33
|
+
|
34
|
+
context "enum value" do
|
35
|
+
specify { @enum.red.inspect.should == "Object::MY_COLORS.red" }
|
36
|
+
specify { @enum.red.t.should == "I18n not available: enums.object.my_colors.red" }
|
37
|
+
end # context "enum value"
|
38
|
+
end # context "with parent"
|
39
|
+
|
40
|
+
context "enum value" do
|
41
|
+
before { @enum = Enum.new(:MY_COLORS, :red => 1, :blue => 2) }
|
42
|
+
subject { @enum }
|
43
|
+
|
44
|
+
its(:red) { should == 1 }
|
45
|
+
its(:blue) { should_not == 1 }
|
46
|
+
its(:red) { should == :red }
|
47
|
+
its(:blue) { should_not == :red }
|
48
|
+
its(:red) { should === 1 }
|
49
|
+
its(:blue) { should_not === 1 }
|
50
|
+
its(:red) { should === :red }
|
51
|
+
its(:blue) { should_not === :red }
|
52
|
+
its(:red) { should be_red }
|
53
|
+
its(:blue) { should_not be_red }
|
54
|
+
specify { @enum.red.object_id.should == @enum[:red].object_id }
|
55
|
+
specify { @enum.red.object_id.should == @enum[1].object_id }
|
56
|
+
end # context "enum value"
|
57
|
+
end # describe Enum
|
data/yinum.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "yinum"
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.authors = ["Oded Niv"]
|
8
|
+
s.email = ["oded.niv@gmail.com"]
|
9
|
+
s.homepage = "https://github.com/toplex/enum"
|
10
|
+
s.summary = %q{Enum implementation}
|
11
|
+
s.description = %q{Awesome enum implementation that gives integer values with a special wrapping.}
|
12
|
+
|
13
|
+
s.rubyforge_project = "yinum"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yinum
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Oded Niv
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-06 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Awesome enum implementation that gives integer values with a special
|
15
|
+
wrapping.
|
16
|
+
email:
|
17
|
+
- oded.niv@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- lib/enum_value.rb
|
28
|
+
- lib/helpers.rb
|
29
|
+
- lib/yinum.rb
|
30
|
+
- spec/spec_helper.rb
|
31
|
+
- spec/yinum_spec.rb
|
32
|
+
- yinum.gemspec
|
33
|
+
homepage: https://github.com/toplex/enum
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project: yinum
|
53
|
+
rubygems_version: 1.8.24
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Enum implementation
|
57
|
+
test_files:
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- spec/yinum_spec.rb
|