classy_enum 3.0.1 → 3.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # ClassyEnum Changelog
2
2
 
3
+ ## 3.1.0
4
+
5
+ * ClassyEnum::Base now extends Enumerable to provide enum collection
6
+ methods. All objects in the collection are instances of the enum
7
+ members. .find is overridden to provide custom find functionality.
8
+ * ClassyEnum::Base.find has been reintroduced, with aliases of .detect
9
+ and [].
10
+ * Introducing I18n support and providing a ClassyEnum::Base#text method
11
+ that will automatically translate text values.
12
+ * Translation support was added to ClassyEnum::Base.select_options.
13
+ * Equality can now be determined using strings and symbols. The
14
+ following will return true:
15
+
16
+ Priority::Low.new == :low # => true
17
+ Priority::Low.new == 'low' # => true
18
+
3
19
  ## 3.0.0
4
20
 
5
21
  * Removing ClassyEnum::Base.enum_classes in favor of using enum
data/README.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  ClassyEnum is a Ruby on Rails gem that adds class-based enumerator functionality to ActiveRecord attributes.
6
6
 
7
+ ## README Topics
8
+
9
+ * [Example Usage](https://github.com/beerlington/classy_enum#example-usage)
10
+ * [Internationalization](https://github.com/beerlington/classy_enum#internationalization)
11
+ * [Using Enum as a Collection](https://github.com/beerlington/classy_enum#using-enum-as-a-collection)
12
+ * [Reference to Owning Object](https://github.com/beerlington/classy_enum#back-reference-to-owning-object)
13
+ * [Serializing as JSON](https://github.com/beerlington/classy_enum#serializing-as-json)
14
+ * [Special Cases](https://github.com/beerlington/classy_enum#special-cases)
15
+ * [Built-in Model Validation](https://github.com/beerlington/classy_enum#model-validation)
16
+ * [Using Enums Outside of ActiveRecord](https://github.com/beerlington/classy_enum#working-with-classyenum-outside-of-activerecord)
17
+ * [Formtastic Support](https://github.com/beerlington/classy_enum#formtastic-support)
18
+
7
19
  ## Rails & Ruby Versions Supported
8
20
 
9
21
  *Rails:* 3.0.x - 3.2.x
@@ -17,8 +29,6 @@ Note: This branch is no longer maintained and will not get bug fixes or new feat
17
29
 
18
30
  The gem is hosted at [rubygems.org](https://rubygems.org/gems/classy_enum)
19
31
 
20
- You will also need to add `app/enums` as an autoloadable path. This configuration will depend on which version of rails you are using.
21
-
22
32
  ## Upgrading?
23
33
 
24
34
  See the [wiki](https://github.com/beerlington/classy_enum/wiki/Upgrading) for notes about upgrading from previous versions.
@@ -59,7 +69,7 @@ The generator creates a default setup, but each enum member can be changed to fi
59
69
 
60
70
  I have defined three priority levels: low, medium, and high. Each priority level can have different properties and methods associated with it.
61
71
 
62
- I would like to add a method called `send_email?` that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.
72
+ I would like to add a method called `#send_email?` that all member subclasses respond to. By default this method will return false, but will be overridden for high priority alarms to return true.
63
73
 
64
74
  ```ruby
65
75
  class Priority < ClassyEnum::Base
@@ -95,7 +105,7 @@ end
95
105
  Note: Alternatively, you may use an enum type if your database supports it. See
96
106
  [this issue](https://github.com/beerlington/classy_enum/issues/12) for more information.
97
107
 
98
- Then in my model I've added a line that calls `classy_enum_attr` with a single argument representing the enum I want to associate with my model. I am also delegating the send_email? method to my Priority enum class.
108
+ Then in my model I've added a line that calls `classy_enum_attr` with a single argument representing the enum I want to associate with my model. I am also delegating the `#send_email?` method to my Priority enum class.
99
109
 
100
110
  ```ruby
101
111
  class Alarm < ActiveRecord::Base
@@ -121,7 +131,62 @@ With this setup, I can now do the following:
121
131
  @alarm.send_email? # => true
122
132
  ```
123
133
 
124
- The enum field works like any other model attribute. It can be mass-assigned using `update_attribute(s)`.
134
+ The enum field works like any other model attribute. It can be mass-assigned using `#update_attributes`.
135
+
136
+ ## Internationalization
137
+
138
+ ClassyEnum provides built-in support for translations using Ruby's I18n
139
+ library. The translated values are provided via a `#text` method on each
140
+ enum object. Translations are automatically applied when a key is found
141
+ at `locale.classy_enum.enum_parent_class.enum_value`, or a default value
142
+ is used that is equivalent to `#to_s.titleize`.
143
+
144
+ Given the following file *config/locales/es.yml*
145
+
146
+ ```yml
147
+ es:
148
+ classy_enum:
149
+ priority:
150
+ low: 'Bajo'
151
+ medium: 'Medio'
152
+ high: 'Alto'
153
+ ```
154
+
155
+ You can now do the following:
156
+
157
+ ```ruby
158
+ @alarm.priority = :low
159
+ @alarm.priority.text # => 'Low'
160
+
161
+ I18n.locale = :es
162
+
163
+ @alarm.priority.text # => 'Bajo'
164
+ ```
165
+
166
+ ## Using Enum as a Collection
167
+
168
+ ClassyEnum::Base extends the [Enumerable module](http://ruby-doc.org/core-1.9.3/Enumerable.html)
169
+ which provides several traversal and searching methods. This can
170
+ be useful for situations where you are working with the collection,
171
+ as opposed to the attributes on an ActiveRecord object.
172
+
173
+ ```ruby
174
+ # Find the priority based on string or symbol:
175
+ Priority.find(:low) # => Priority::Low.new
176
+ Priority.find('medium') # => Priority::Medium.new
177
+
178
+ # Find the lowest priority that can send email:
179
+ Priority.find(&:send_email?) # => Priority::High.new
180
+
181
+ # Find the priorities that are lower than Priority::High
182
+ high_priority = Priority::High.new
183
+ Priority.select {|p| p < high_priority } # => [Priority::Low.new, Priority::Medium.new]
184
+
185
+ # Iterate over each priority:
186
+ Priority.each do |priority|
187
+ puts priority.send_email?
188
+ end
189
+ ```
125
190
 
126
191
  ## Back reference to owning object
127
192
 
@@ -129,11 +194,11 @@ In some cases you may want an enum class to reference the owning object
129
194
  (an instance of the active record model). Think of it as a `belongs_to`
130
195
  relationship, where the enum belongs to the model.
131
196
 
132
- By default, the back reference can be called using `owner`.
197
+ By default, the back reference can be called using `#owner`.
133
198
  If you want to refer to the owner by a different name, you must explicitly declare
134
- the owner name in the classy_enum parent class using the `owner` class method.
199
+ the owner name in the classy_enum parent class using the `.owner` class method.
135
200
 
136
- Example using the default `owner` method:
201
+ Example using the default `#owner` method:
137
202
 
138
203
  ```ruby
139
204
  class Priority < ClassyEnum::Base
@@ -210,7 +275,7 @@ end
210
275
 
211
276
  ## Model Validation
212
277
 
213
- An ActiveRecord validator `validates_inclusion_of :field, :in => ENUM.all` is automatically added to your model when you use `classy_enum_attr`.
278
+ An ActiveRecord validator `validates_inclusion_of :field, :in => ENUM` is automatically added to your model when you use `classy_enum_attr`.
214
279
 
215
280
  If your enum only has members low, medium, and high, then the following validation behavior would be expected:
216
281
 
@@ -240,8 +305,8 @@ Instantiate an enum member subclass *Priority::Low*
240
305
 
241
306
  ```ruby
242
307
  # These statements are all equivalent
243
- low = Priority.build(:low)
244
- low = Priority.build('low')
308
+ low = Priority.find(:low)
309
+ low = Priority.find('low')
245
310
  low = Priority::Low.new
246
311
  ```
247
312
 
@@ -27,12 +27,9 @@ module ClassyEnum
27
27
  allow_nil = options[:allow_nil] || false
28
28
  serialize_as_json = options[:serialize_as_json] || false
29
29
 
30
- error_message = "must be #{enum.all.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ')}"
31
-
32
30
  # Add ActiveRecord validation to ensure it won't be saved unless it's an option
33
31
  validates_inclusion_of attribute,
34
- :in => enum.all,
35
- :message => error_message,
32
+ :in => enum,
36
33
  :allow_blank => allow_blank,
37
34
  :allow_nil => allow_nil
38
35
 
@@ -5,6 +5,7 @@ module ClassyEnum
5
5
  include Comparable
6
6
  include Conversion
7
7
  include Predicate
8
+ include Translation
8
9
  include Collection
9
10
 
10
11
  class_attribute :base_class
@@ -56,12 +57,10 @@ module ClassyEnum
56
57
  # Priority.build(:low) # => Priority::Low.new
57
58
  # Priority.build(:invalid_option) # => :invalid_option
58
59
  def build(value, options={})
59
- return value if value.blank? && options[:allow_blank]
60
+ object = find(value)
60
61
 
61
- # Return the value if it is not a valid member
62
- return value unless all.map(&:to_s).include? value.to_s
62
+ return value if object.nil? || (options[:allow_blank] && object.nil?)
63
63
 
64
- object = "#{base_class}::#{value.to_s.camelize}".constantize.new
65
64
  object.owner = options[:owner]
66
65
  object.serialize_as_json = options[:serialize_as_json]
67
66
  object.allow_blank = options[:allow_blank]
@@ -19,6 +19,10 @@ module ClassyEnum
19
19
  # priorities.max # => @high
20
20
  # priorities.min # => @low
21
21
  def <=> other
22
+ if other.is_a?(Symbol) || other.is_a?(String)
23
+ other = self.class.find(other)
24
+ end
25
+
22
26
  index <=> other.index
23
27
  end
24
28
 
@@ -27,6 +31,9 @@ module ClassyEnum
27
31
  end
28
32
 
29
33
  module ClassMethods
34
+ include Enumerable
35
+ alias all to_a
36
+
30
37
  def inherited(klass)
31
38
  if self == ClassyEnum::Base
32
39
  klass.class_attribute :enum_options
@@ -39,7 +46,7 @@ module ClassyEnum
39
46
  super
40
47
  end
41
48
 
42
- # Returns an array of all instantiated enums
49
+ # Iterates over instances of each enum in the collection
43
50
  #
44
51
  # ==== Example
45
52
  # # Create an Enum with some elements
@@ -50,11 +57,42 @@ module ClassyEnum
50
57
  # class Priority::Medium < Priority; end
51
58
  # class Priority::High < Priority; end
52
59
  #
53
- # Priority.all # => [Priority::Low.new, Priority::Medium.new, Priority::High.new]
54
- def all
55
- enum_options.map(&:new)
60
+ # Priority.each do |priority|
61
+ # puts priority # => 'Low', 'Medium', 'High'
62
+ # end
63
+ def each
64
+ enum_options.each {|e| yield e.new }
56
65
  end
57
66
 
67
+ # Finds an enum instance by symbol, string, or block.
68
+ #
69
+ # If a block is given, it passes each entry in enum to block, and returns
70
+ # the first enum for which block is not false. If no enum matches, it
71
+ # returns nil.
72
+ #
73
+ # ==== Example
74
+ # # Create an Enum with some elements
75
+ # class Priority < ClassyEnum::Base
76
+ # end
77
+ #
78
+ # class Priority::Low < Priority; end
79
+ # class Priority::Medium < Priority; end
80
+ # class Priority::High < Priority; end
81
+ #
82
+ # Priority.find(:high) # => Priority::High.new
83
+ # Priority.find('high') # => Priority::High.new
84
+ # Priority.find {|e| e.to_sym == :high } # => Priority::High.new
85
+ def find(key=nil)
86
+ if block_given?
87
+ super
88
+ elsif map(&:to_s).include? key.to_s
89
+ super { |e| e.to_s == key.to_s }
90
+ end
91
+ end
92
+
93
+ alias detect find
94
+ alias [] find
95
+
58
96
  # Returns a 2D array for Rails select helper options.
59
97
  # Also used internally for Formtastic support
60
98
  #
@@ -68,7 +106,7 @@ module ClassyEnum
68
106
  #
69
107
  # Priority.select_options # => [["Low", "low"], ["Really High", "really_high"]]
70
108
  def select_options
71
- all.map {|e| [e.to_s.titleize, e.to_s] }
109
+ map {|e| [e.text, e.to_s] }
72
110
  end
73
111
  end
74
112
 
@@ -4,7 +4,7 @@ module ClassyEnum
4
4
  # Define attribute methods like two?
5
5
  def self.define_predicate_method(klass, enum)
6
6
  klass.base_class.class_eval do
7
- define_method "#{enum}?", lambda { attribute?(enum.to_s) }
7
+ define_method "#{enum}?", lambda { attribute?(enum) }
8
8
  end
9
9
  end
10
10
 
@@ -29,7 +29,7 @@ module ClassyEnum
29
29
  # @dog.breed.snoop? # => true
30
30
  # @dog.breed.golden_retriever? # => false
31
31
  def attribute?(attribute)
32
- to_s == attribute
32
+ self == attribute
33
33
  end
34
34
  end
35
35
  end
@@ -0,0 +1,38 @@
1
+ require 'i18n'
2
+
3
+ module ClassyEnum
4
+ module Translation
5
+
6
+ # Returns a translated string of the enum type. Used internally to create
7
+ # the select_options array.
8
+ #
9
+ # Translation location is:
10
+ # locale.classy_enum.base_class.enum_string
11
+ #
12
+ # ==== Example
13
+ # # Create an Enum with some elements
14
+ # class Priority < ClassyEnum::Base
15
+ # end
16
+ #
17
+ # class Priority::Low < Priority; end
18
+ # class Priority::ReallyHigh < Priority; end
19
+ #
20
+ # # Default translations are `to_s.titlieze`
21
+ # Priority::Low.new.text # => 'Low'
22
+ # Priority::ReallyHigh.new.text # => 'Really High'
23
+ #
24
+ # # Assuming we have a translation defined for:
25
+ # # es.classy_enum.priority.low # => 'Bajo'
26
+ #
27
+ # Priority::Low.new.text # => 'Bajo'
28
+ def text
29
+ I18n.translate to_s, :scope => i18n_scope, :default => to_s.titleize
30
+ end
31
+
32
+ private
33
+
34
+ def i18n_scope
35
+ [:classy_enum, base_class.name.underscore]
36
+ end
37
+ end
38
+ end
@@ -1,3 +1,3 @@
1
1
  module ClassyEnum
2
- VERSION = "3.0.1"
2
+ VERSION = "3.1.0"
3
3
  end
data/lib/classy_enum.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'classy_enum/translation'
1
2
  require 'classy_enum/collection'
2
3
  require 'classy_enum/conversion'
3
4
  require 'classy_enum/predicate'
@@ -43,10 +43,12 @@ describe Dog do
43
43
  specify { Dog.new(:breed => '').should_not be_valid }
44
44
 
45
45
  context "with valid breed options" do
46
- subject { Dog.new(:breed => :golden_retriever) }
47
- it { should be_valid }
48
- its(:breed) { should be_a(Breed::GoldenRetriever) }
49
- its('breed.allow_blank') { should be_false }
46
+ [:golden_retriever, 'golden_retriever', Breed::GoldenRetriever.new].each do |option|
47
+ subject { Dog.new(:breed => option) }
48
+ it { should be_valid }
49
+ its(:breed) { should be_a(Breed::GoldenRetriever) }
50
+ its('breed.allow_blank') { should be_false }
51
+ end
50
52
  end
51
53
 
52
54
  context "with invalid breed options" do
@@ -1,6 +1,7 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  class ClassyEnumCollection < ClassyEnum::Base
4
+ delegate :odd?, :to => :to_i
4
5
  end
5
6
 
6
7
  class ClassyEnumCollection::One < ClassyEnumCollection
@@ -13,11 +14,57 @@ class ClassyEnumCollection::Three < ClassyEnumCollection
13
14
  end
14
15
 
15
16
  describe ClassyEnum::Collection do
16
- subject { ClassyEnumCollection }
17
+ subject(:enum) { ClassyEnumCollection }
17
18
 
18
19
  its(:enum_options) { should == [ClassyEnumCollection::One, ClassyEnumCollection::Two, ClassyEnumCollection::Three] }
19
20
  its(:all) { should == [ClassyEnumCollection::One.new, ClassyEnumCollection::Two.new, ClassyEnumCollection::Three.new] }
20
21
  its(:select_options) { should == [['One', 'one'],['Two', 'two'], ['Three', 'three']] }
22
+
23
+ context '.map' do
24
+ it 'should behave like an enumerable' do
25
+ enum.map(&:to_s).should == %w(one two three)
26
+ end
27
+ end
28
+
29
+ context '#<=> (equality)' do
30
+ its(:first) { should == ClassyEnumCollection::One.new }
31
+ its(:first) { should == :one }
32
+ its(:first) { should == 'one' }
33
+ its(:first) { should_not == :two }
34
+ its(:first) { should_not == :not_found }
35
+
36
+ its(:max) { should == :three }
37
+ end
38
+
39
+ context '.find, .detect, []' do
40
+ let(:expected_enum) { ClassyEnumCollection::Two.new }
41
+
42
+ [:find, :detect, :[]].each do |method|
43
+ it 'should return an instance when searching by symbol' do
44
+ enum.send(method, :two).should == expected_enum
45
+ end
46
+
47
+ it 'should return an instance when searching by string' do
48
+ enum.send(method, 'two').should == expected_enum
49
+ end
50
+
51
+ it 'should behave like an enumerable when using a block' do
52
+ enum.send(method) {|e| e.to_s == 'two'}.should == expected_enum
53
+ end
54
+
55
+ it 'should return nil if item cannot be found' do
56
+ enum.send(method, :not_found).should be_nil
57
+ end
58
+ end
59
+ end
60
+
61
+ context '.select' do
62
+ let(:expected_enum) { ClassyEnumCollection::Two.new }
63
+
64
+ it 'returns an array with each item where the block returns true' do
65
+ enum.select(&:odd?).should == [ClassyEnumCollection::One.new, ClassyEnumCollection::Three.new]
66
+ end
67
+ end
21
68
  end
22
69
 
23
70
  describe ClassyEnum::Collection, Comparable do
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ class ClassyEnumTranslation < ClassyEnum::Base
4
+ end
5
+
6
+ class ClassyEnumTranslation::One < ClassyEnumTranslation
7
+ end
8
+
9
+ class ClassyEnumTranslation::Two < ClassyEnumTranslation
10
+ end
11
+
12
+ describe ClassyEnum::Translation do
13
+
14
+ before do
15
+ I18n.reload!
16
+ I18n.backend.store_translations :en, :classy_enum => {:classy_enum_translation => {:one => 'One!', :two => 'Two!' } }
17
+ I18n.backend.store_translations :es, :classy_enum => {:classy_enum_translation => {:one => 'Uno', :two => 'Dos' } }
18
+ end
19
+
20
+ context '#text' do
21
+ subject { ClassyEnumTranslation::One.new }
22
+
23
+ context 'default' do
24
+ before { I18n.reload! }
25
+ its(:text) { should == 'One' }
26
+ end
27
+
28
+ context 'en' do
29
+ before { I18n.locale = :en }
30
+ its(:text) { should == 'One!' }
31
+ end
32
+
33
+ context 'es' do
34
+ before { I18n.locale = :es }
35
+ its(:text) { should == 'Uno' }
36
+ end
37
+ end
38
+
39
+ context '.select_options' do
40
+ subject { ClassyEnumTranslation }
41
+
42
+ context 'default' do
43
+ before { I18n.reload! }
44
+ its(:select_options) { should == [["One", "one"], ["Two", "two"]] }
45
+ end
46
+
47
+ context 'en' do
48
+ before { I18n.locale = :en }
49
+ its(:select_options) { should == [["One!", "one"], ["Two!", "two"]] }
50
+ end
51
+
52
+ context 'es' do
53
+ before { I18n.locale = :es }
54
+ its(:select_options) { should == [["Uno", "one"], ["Dos", "two"]] }
55
+ end
56
+ end
57
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: classy_enum
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-03 00:00:00.000000000 Z
12
+ date: 2012-08-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -100,6 +100,7 @@ files:
100
100
  - lib/classy_enum/collection.rb
101
101
  - lib/classy_enum/conversion.rb
102
102
  - lib/classy_enum/predicate.rb
103
+ - lib/classy_enum/translation.rb
103
104
  - lib/classy_enum/version.rb
104
105
  - lib/generators/classy_enum/classy_enum_generator.rb
105
106
  - lib/generators/classy_enum/templates/enum.rb
@@ -108,6 +109,7 @@ files:
108
109
  - spec/classy_enum/collection_spec.rb
109
110
  - spec/classy_enum/conversion_spec.rb
110
111
  - spec/classy_enum/predicate_spec.rb
112
+ - spec/classy_enum/translation_spec.rb
111
113
  - spec/classy_enum_inheritance_spec.rb
112
114
  - spec/spec_helper.rb
113
115
  homepage: http://github.com/beerlington/classy_enum
@@ -124,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
126
  version: '0'
125
127
  segments:
126
128
  - 0
127
- hash: 4077796099842311388
129
+ hash: 4485240533001760978
128
130
  required_rubygems_version: !ruby/object:Gem::Requirement
129
131
  none: false
130
132
  requirements:
@@ -133,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
135
  version: '0'
134
136
  segments:
135
137
  - 0
136
- hash: 4077796099842311388
138
+ hash: 4485240533001760978
137
139
  requirements: []
138
140
  rubyforge_project:
139
141
  rubygems_version: 1.8.24
@@ -146,5 +148,6 @@ test_files:
146
148
  - spec/classy_enum/collection_spec.rb
147
149
  - spec/classy_enum/conversion_spec.rb
148
150
  - spec/classy_enum/predicate_spec.rb
151
+ - spec/classy_enum/translation_spec.rb
149
152
  - spec/classy_enum_inheritance_spec.rb
150
153
  - spec/spec_helper.rb