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 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