yinum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ *.sassc
4
+ .sass-cache
5
+ capybara-*.html
6
+ .rspec
7
+ /.bundle
8
+ /vendor/bundle
9
+ /log/*
10
+ /tmp/*
11
+ /db/*.sqlite3
12
+ /public/system/*
13
+ /coverage/
14
+ /spec/tmp/*
15
+ **.orig
16
+ rerun.txt
17
+ pickle-email-*.html
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in commod.gemspec
4
+ gemspec
5
+ gem 'rspec'
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'
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'yinum'
5
+
6
+ RSpec.configure do |config|
7
+ end
@@ -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