extended_has_enumeration 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,41 @@
1
+ source :rubygems
2
+
3
+ version = ENV['AR_VERSION'] || '3.2.x'
4
+
5
+ if version.end_with? 'x'
6
+ # fuzzy version support
7
+ version = version.gsub /x$/, '0'
8
+ gem 'activerecord', '~> ' +version, :require => 'active_record'
9
+ elsif version == '3'
10
+ version = '3.0.0'
11
+ gem 'activerecord', '~> 3.0', :require => 'active_record'
12
+ else
13
+ gem 'activerecord', version, :require => 'active_record'
14
+ end
15
+
16
+ gem 'builder'
17
+
18
+ #if version >= '3.0.0'
19
+ # group :meta_where do
20
+ # gem 'squeel'
21
+ # end
22
+ #end
23
+
24
+ group :development do
25
+ gem 'jeweler'
26
+ end
27
+
28
+ group :test do
29
+ gem 'rspec', '~> 2.13.0'
30
+ gem 'cucumber'
31
+
32
+ gem 'ruby-debug19'
33
+ platforms :ruby do
34
+ gem 'sqlite3-ruby', :require => 'sqlite3'
35
+ end
36
+
37
+ platforms :jruby do
38
+ gem 'jdbc-sqlite3'
39
+ gem 'activerecord-jdbcsqlite3-adapter', '~> 0.9.7'
40
+ end
41
+ end
data/HISTORY.txt ADDED
@@ -0,0 +1,44 @@
1
+ Version 1.0.2:
2
+ - No code changes
3
+ - Fixed problem with gem dependencies
4
+
5
+ Version 1.0.1:
6
+ - Added support for Arel 2, which is required by ActiveRecord >= 3.0.3
7
+
8
+ Version 1.0.0:
9
+ - Added #to_sym for generated enumeration class. #value is deprecated.
10
+ - Raises ArgumentError when assigning, or querying, with a symbol that is
11
+ not part of the enumeration
12
+ - A value of nil in underlying attribute results in a nil value for the
13
+ enumeration
14
+ - Assigning nil to the enumeration results in a value of nil for the underlying
15
+ attribute
16
+
17
+ Version 0.5.0:
18
+ - Default name of underlying attribute can be overridden with the :attribute
19
+ option
20
+ - Internal cleanup: removed some unnecessary code and patching for Rails 3
21
+
22
+ Version 0.4.0:
23
+ - MetaWhere interoperability
24
+ - Tested with Rails 3.0.0
25
+
26
+ Version 0.3.1:
27
+ - Re-release of 0.3.0 to pick up missed files
28
+
29
+ Version 0.3.0:
30
+ - Support for Rails 3.0.0.rc2
31
+ - NOTE: no longer compatible with Rails 3.0.0.rc
32
+
33
+ Version 0.2.2:
34
+ - Cosmetic change: simply gemfile description
35
+
36
+ Version 0.2.1:
37
+ - Support for JRuby 1.5.1
38
+ - Support for ActiveRecord 2.3.8
39
+
40
+ Version 0.2.0:
41
+ - Support for Arel predicates
42
+
43
+ Version 0.1.0:
44
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2010 Greg Spurrier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # has_enumeration
2
+ ## Overview
3
+ `has_enumeration` adds support for enumerated types to ActiveRecord. The values
4
+ of an enumerated attribute are represented as symbols regardless of the
5
+ attribute's underlying representation in the database. Predicates
6
+ (e.g. `car.color.red?`) are provided for each value of the enumerated type.
7
+
8
+ ## Defining Enumerated Attributes
9
+ Enumerated attributes are declared in the model's class definition with the
10
+ `has_enumeration` class method. For example:
11
+
12
+ class Car < ActiveRecord::Base
13
+ has_enumeration :color, [:red, :green, :blue]
14
+ end
15
+
16
+ The above definition assumes that the `color` attribute is stored as a string
17
+ and that its values in the database match those obtained by calling `to_s` on
18
+ their corresponding symbols. I.e., :red is stored as 'red'.
19
+
20
+ If the underlying column is not a string or its values do not match the string
21
+ version of the enumeration's symbols, then the mapping between symbols and their
22
+ underlying values may be provided as a hash:
23
+
24
+ class Car < ActiveRecord::Base
25
+ has_enumeration :color, :red => 1, :green => 2, :blue => 3
26
+ end
27
+
28
+ By default, the underlying attribute is assumed to have the same name as the
29
+ enumeration. If this is not the case, the name of the underlying attibute may
30
+ be provided with the `:attribute` option:
31
+
32
+ class Car < ActiveRecord::Base
33
+ has_enumeration :color, {:red => 1, :green => 2, :blue => 3},
34
+ :attribute => :hue
35
+ end
36
+
37
+ ## Using Enumerated Attributes
38
+ ### Assignment
39
+ Enumerated attributes are assigned using symbols:
40
+
41
+ car = Car.new(:color => :red)
42
+ car.color = :blue
43
+
44
+ The symbols are coerced into an instance of a nested class (`Car::Color` in
45
+ this example) that is created by `has_enumeration`. If for some reason you
46
+ need to avoid the type coercion, you can assign a value of that class directly:
47
+
48
+ car.color = Car::Color.from_sym(:green)
49
+
50
+ ### Querying
51
+ When constructing queries referencing the enumerated attribute, use the symbol
52
+ as its value:
53
+
54
+ Car.find(:all, :conditions => {:color => :red})
55
+
56
+ `has_enumeration` supports Rails 3 and is aware of the model's underlying Arel
57
+ representation:
58
+
59
+ Car.where(:color => :red)
60
+ Car.where(Car.arel_table[:color].not_in([:red, :green]))
61
+
62
+ If you are using MetaWhere, `has_enumeration` plays nicely with it:
63
+
64
+ # This example requires the meta_where:
65
+ Car.where(:color.not_in => [:red, :green])
66
+
67
+ ### Testing Values
68
+ The primary means of interacting with enumerated attributes is through the
69
+ predicate methods that are automatically generated for each value in the
70
+ enumeration:
71
+
72
+ car = Car.new(:color => :red)
73
+ car.color.red?
74
+ # => true
75
+ car.color.green?
76
+ # => false
77
+
78
+ If the value of the attribute is needed as a symbol, e.g., for direct
79
+ comparison, it can be retrieved with `to_sym`:
80
+
81
+ car.color.to_sym
82
+ # => :red
83
+
84
+ ## Installation
85
+ `has_enumeration` is packaged as a gem:
86
+
87
+ gem install has_enumeration
88
+
89
+ ### Rails 3
90
+ To use `has_enumeration` with Rails 3, simply add it to your application's
91
+ Gemfile:
92
+
93
+ gem 'has_enumeration'
94
+
95
+ ### Rails 2.x
96
+ To use `has_enumeration` with Rails 2, add it to your application's
97
+ environment.rb file:
98
+
99
+ config.gem 'has_enumeration'
100
+
101
+ ## Supported Configurations
102
+ `has_enumeration` has been tested with the following versions of ActiveRecord:
103
+
104
+ * 2.3.10
105
+ * 3.0.1
106
+ * 3.0.3
107
+
108
+ and the following Ruby implementations:
109
+
110
+ * 1.8.7-p302
111
+ * 1.9.2-p0
112
+ * JRuby 1.5.5
113
+ * Rubinius 1.1.0
114
+
115
+
116
+ ## Getting the Latest
117
+ `has_enumeration` is hosted on github at
118
+ [http://github.com/gregspurrier/has_enumeration](http://github.com/gregspurrier/has_enumeration).
119
+
120
+ You can make a local clone of the repository with the following command:
121
+
122
+ git clone git://github.com/gregspurrier/has_enumeration.git
123
+
124
+ ## License
125
+ `has_enumeration` is Copyright 2010 by Greg Spurrier. It is released under
126
+ the MIT License. Please see LICENSE.txt for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'cucumber'
3
+ require 'cucumber/rake/task'
4
+ require 'rspec'
5
+ require 'rspec/core/rake_task'
6
+
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "extended_has_enumeration"
12
+ gemspec.summary = "Support for symbol-based enumerations in ActiveRecord"
13
+ gemspec.description = <<-EOF
14
+ Extends ActiveRecord with the has_enumeration method allowing a symbolic
15
+ enumeration to be stored in an ActiveRecord attribute. The enumeration is
16
+ specified as a mapping between symbols and their underlying representation
17
+ in the database. Predicates are provided for each symbol in the enumeration
18
+ and the symbols may be used in finder methods. When using ActiveRecord 3,
19
+ the symbols may also be used when interacting with the underlying Arel attribute
20
+ for the enumeration. has_enumeration has been tested with Ruby 1.8.7,
21
+ Ruby 1.9.2, JRuby 1.5.5, Rubinius 1.1.0, ActiveRecord 2.3.10, and ActiveRecord
22
+ 3.0.3.
23
+ EOF
24
+ gemspec.email = "maxtsap@gmail.com"
25
+ gemspec.homepage = "http://github.com/maxtsap/has_enumeration"
26
+ gemspec.author = "Greg Spurrier and Maxim Tsaplin"
27
+ end
28
+ Jeweler::GemcutterTasks.new
29
+ rescue LoadError
30
+ puts "Jeweler not available. Install it with: gem install jeweler"
31
+ end
32
+
33
+ task :features => 'features:all'
34
+ namespace :features do
35
+ task :all => [:common, :rails3]
36
+
37
+ Cucumber::Rake::Task.new(:common) do |t|
38
+ features = %w(explicitly_mapped_enumeration implicitly_mapped_enumeration
39
+ nonstandard_attribute_enumeration)
40
+ feature_files = features.map {|f| "features/#{f}.feature"}.join(' ')
41
+ t.cucumber_opts = feature_files
42
+ end
43
+
44
+ Cucumber::Rake::Task.new(:rails3) do |t|
45
+ # the ../ hack is because of a gherkin bug encountered when the file paths
46
+ # have exactly two / characters in some interpreters (coughjrubycough)
47
+ features = %w(../features/arel_attributes meta_where_queries)
48
+ feature_files = features.map {|f| "features/#{f}.feature"}.join(' ')
49
+
50
+ # With the ../ hack we have to specify the features directory via -r
51
+ t.cucumber_opts = '-r features ' +feature_files
52
+ end
53
+ end
54
+
55
+ desc "Run all specs"
56
+ RSpec::Core::RakeTask.new('spec')
data/TODO.md ADDED
@@ -0,0 +1,8 @@
1
+ # Release 1.1
2
+ * Support string assignment
3
+ * Intended for bulk assignment from form posts
4
+ * Unlike symbol assignment, will not raise an exception when a value
5
+ is invalid
6
+ * Invalid values will fail validation and add an error on the attribute
7
+ * Expose the accepted values for an enumeration via something like
8
+ `Car::Color.values`
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
data/all_tests.sh ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ AR_VERSION=3.0.x rvm 1.8.7@ar_3.0.x,1.9.2@ar_3.0.x,jruby@ar_3.0.x,rbx@ar_3.0.x --yaml do rake features spec
3
+ AR_VERSION=2.3.10 rvm 1.8.7@ar_2.3.10,1.9.2@ar_2.3.10,jruby@ar_2.3.10,rbx@ar_2.3.10 --yaml do rake features:common spec
4
+
@@ -0,0 +1,20 @@
1
+ Feature:
2
+
3
+ As a programmer using has_enumeration in Rails 3
4
+ I want to be able to refer to the enumeration symbolically when working with
5
+ the underlying arel attributes for a model
6
+ So that I can write pretty, concise code.
7
+
8
+
9
+ Scenario Outline: find objects with a specific enumerated value via arel
10
+ Given a model with an explicitly-mapped enumeration of red, green, and blue
11
+ And a set of objects with a variety of values for the enumeration
12
+ When I query for objects with the value <value> via arel
13
+ Then I should get all of the objects having that value
14
+ And I should not get any objects having other values
15
+
16
+ Scenarios: querying various values
17
+ | value |
18
+ | :red |
19
+ | :green |
20
+ | :blue |
@@ -0,0 +1,51 @@
1
+ Feature:
2
+
3
+ As a programmer using an ActiveRecord model with an integer column
4
+ representing an enumerated value
5
+ I want to be able to refer to the enumeration symbolically and have
6
+ predicates to test its value
7
+ So that I can write pretty, concise code.
8
+
9
+ Scenario Outline: set an enumerated value on an unsaved object
10
+ Given a model with an explicitly-mapped enumeration of red, green, and blue
11
+ And an unsaved instance of that model
12
+ When I assign a symbolic value <assigned> to the enumeration
13
+ Then it should have the assigned value as its value
14
+ And the red? predicate should be <red?>
15
+ And the green? predicate should be <green?>
16
+ And the blue? predicate should be <blue?>
17
+
18
+ Scenarios: setting various values
19
+ | assigned | red? | green? | blue? |
20
+ | :red | true | false | false |
21
+ | :green | false | true | false |
22
+ | :blue | false | false | true |
23
+
24
+ Scenario Outline: set an enumerated value then save and reload
25
+ Given a model with an explicitly-mapped enumeration of red, green, and blue
26
+ And an unsaved instance of that model
27
+ When I assign a symbolic value <assigned> to the enumeration
28
+ And I save and reload the object
29
+ Then it should have the assigned value as its value
30
+ And the red? predicate should be <red?>
31
+ And the green? predicate should be <green?>
32
+ And the blue? predicate should be <blue?>
33
+
34
+ Scenarios: setting various values
35
+ | assigned | red? | green? | blue? |
36
+ | :red | true | false | false |
37
+ | :green | false | true | false |
38
+ | :blue | false | false | true |
39
+
40
+ Scenario Outline: find objects with a specific enumerated value
41
+ Given a model with an explicitly-mapped enumeration of red, green, and blue
42
+ And a set of objects with a variety of values for the enumeration
43
+ When I query for objects with the value <value>
44
+ Then I should get all of the objects having that value
45
+ And I should not get any objects having other values
46
+
47
+ Scenarios: querying various values
48
+ | value |
49
+ | :red |
50
+ | :green |
51
+ | :blue |
@@ -0,0 +1,51 @@
1
+ Feature:
2
+
3
+ As a programmer using an ActiveRecord model with a string column
4
+ representing an enumerated value
5
+ I want to be able to refer to the enumeration symbolically and have
6
+ predicates to test its value
7
+ So that I can write pretty, concise code.
8
+
9
+ Scenario Outline: set an enumerated value on an unsaved object
10
+ Given a model with an implicitly-mapped enumeration of red, green, and blue
11
+ And an unsaved instance of that model
12
+ When I assign a symbolic value <assigned> to the enumeration
13
+ Then it should have the assigned value as its value
14
+ And the red? predicate should be <red?>
15
+ And the green? predicate should be <green?>
16
+ And the blue? predicate should be <blue?>
17
+
18
+ Scenarios: setting various values
19
+ | assigned | red? | green? | blue? |
20
+ | :red | true | false | false |
21
+ | :green | false | true | false |
22
+ | :blue | false | false | true |
23
+
24
+ Scenario Outline: set an enumerated value then save and reload
25
+ Given a model with an implicitly-mapped enumeration of red, green, and blue
26
+ And an unsaved instance of that model
27
+ When I assign a symbolic value <assigned> to the enumeration
28
+ And I save and reload the object
29
+ Then it should have the assigned value as its value
30
+ And the red? predicate should be <red?>
31
+ And the green? predicate should be <green?>
32
+ And the blue? predicate should be <blue?>
33
+
34
+ Scenarios: setting various values
35
+ | assigned | red? | green? | blue? |
36
+ | :red | true | false | false |
37
+ | :green | false | true | false |
38
+ | :blue | false | false | true |
39
+
40
+ Scenario Outline: find objects with a specific enumerated value
41
+ Given a model with an implicitly-mapped enumeration of red, green, and blue
42
+ And a set of objects with a variety of values for the enumeration
43
+ When I query for objects with the value <value>
44
+ Then I should get all of the objects having that value
45
+ And I should not get any objects having other values
46
+
47
+ Scenarios: querying various values
48
+ | value |
49
+ | :red |
50
+ | :green |
51
+ | :blue |
@@ -0,0 +1,20 @@
1
+ Feature:
2
+
3
+ As a programmer using has_enumeration in Rails 3
4
+ I want to be able to refer to the enumeration symbolically when working with
5
+ the MetaWhere queries for a model
6
+ So that I can write pretty, concise code.
7
+
8
+
9
+ Scenario Outline: find objects with a specific enumerated value via MetaWhere
10
+ Given a model with an explicitly-mapped enumeration of red, green, and blue
11
+ And a set of objects with a variety of values for the enumeration
12
+ When I query for objects with the value <value> via MetaWhere
13
+ Then I should get all of the objects having that value
14
+ And I should not get any objects having other values
15
+
16
+ Scenarios: querying various values
17
+ | value |
18
+ | :red |
19
+ | :green |
20
+ | :blue |
@@ -0,0 +1,51 @@
1
+ Feature:
2
+
3
+ As a programmer using an ActiveRecord model with an integer column
4
+ representing an enumerated value
5
+ I want to be able to refer to the enumeration symbolically and have
6
+ predicates to test its value
7
+ So that I can write pretty, concise code.
8
+
9
+ Scenario Outline: set an enumerated value on an unsaved object
10
+ Given a model with an nonstandard-attribute enumeration of red, green, and blue
11
+ And an unsaved instance of that model
12
+ When I assign a symbolic value <assigned> to the enumeration
13
+ Then it should have the assigned value as its value
14
+ And the red? predicate should be <red?>
15
+ And the green? predicate should be <green?>
16
+ And the blue? predicate should be <blue?>
17
+
18
+ Scenarios: setting various values
19
+ | assigned | red? | green? | blue? |
20
+ | :red | true | false | false |
21
+ | :green | false | true | false |
22
+ | :blue | false | false | true |
23
+
24
+ Scenario Outline: set an enumerated value then save and reload
25
+ Given a model with an nonstandard-attribute enumeration of red, green, and blue
26
+ And an unsaved instance of that model
27
+ When I assign a symbolic value <assigned> to the enumeration
28
+ And I save and reload the object
29
+ Then it should have the assigned value as its value
30
+ And the red? predicate should be <red?>
31
+ And the green? predicate should be <green?>
32
+ And the blue? predicate should be <blue?>
33
+
34
+ Scenarios: setting various values
35
+ | assigned | red? | green? | blue? |
36
+ | :red | true | false | false |
37
+ | :green | false | true | false |
38
+ | :blue | false | false | true |
39
+
40
+ Scenario Outline: find objects with a specific enumerated value
41
+ Given a model with an nonstandard-attribute enumeration of red, green, and blue
42
+ And a set of objects with a variety of values for the enumeration
43
+ When I query for objects with the value <value>
44
+ Then I should get all of the objects having that value
45
+ And I should not get any objects having other values
46
+
47
+ Scenarios: querying various values
48
+ | value |
49
+ | :red |
50
+ | :green |
51
+ | :blue |
@@ -0,0 +1,77 @@
1
+ Given /^a model with an explicitly-mapped enumeration of red, green, and blue$/ do
2
+ @model_class = ExplicitlyMappedModel
3
+ end
4
+
5
+ Given /^a model with an implicitly-mapped enumeration of red, green, and blue$/ do
6
+ @model_class = ImplicitlyMappedModel
7
+ end
8
+
9
+ Given /^a model with an nonstandard-attribute enumeration of red, green, and blue$/ do
10
+ @model_class = NonstandardAttributeModel
11
+ end
12
+
13
+ Given /^an unsaved instance of that model$/ do
14
+ @object = @model_class.new
15
+ end
16
+
17
+ When /^I assign a symbolic value :([a-z_]+) to the enumeration$/ do |value|
18
+ @assigned = value.to_sym
19
+ @object.color = @assigned
20
+ end
21
+
22
+ When /^I save and reload the object$/ do
23
+ @object.save!
24
+ @object = @model_class.find(@object.id)
25
+ end
26
+
27
+ Then /^it should have the assigned value as its value$/ do
28
+ @object.color.to_sym.should == @assigned
29
+ end
30
+
31
+ Then /^the ([a-z_]+\?) predicate should be (true|false)$/ do |predicate, value|
32
+ if value == 'true'
33
+ expected_value = true
34
+ else
35
+ expected_value = false
36
+ end
37
+ @object.color.send(predicate).should == expected_value
38
+ end
39
+
40
+
41
+ Given /^a set of objects with a variety of values for the enumeration$/ do
42
+ a = ExplicitlyMappedModel.new(:color => :red)
43
+ a.color = :red
44
+ p a
45
+ @model_class.delete_all
46
+
47
+ 2.times do
48
+ [:red, :green, :blue].each do |color|
49
+ @model_class.create!(:color => color)
50
+ end
51
+ end
52
+ @all_objects = @model_class.all(:order => :id)
53
+ end
54
+
55
+ When /^I query for objects with the value :([a-z_]+)$/ do |value|
56
+ @desired_color = value.to_sym
57
+ @results = @model_class.all(:conditions => {:color => @desired_color}, :order => :id)
58
+ end
59
+
60
+ When /^I query for objects with the value :([a-z_]+) via arel$/ do |value|
61
+ @desired_color = value.to_sym
62
+ arel_attr = @model_class.arel_table[:color]
63
+ @results = @model_class.where(arel_attr.eq(@desired_color)).order(:id)
64
+ end
65
+
66
+ When /^I query for objects with the value :([a-z_]+) via MetaWhere$/ do |value|
67
+ @desired_color = value.to_sym
68
+ @results = @model_class.where(:color.eq => @desired_color).order(:id)
69
+ end
70
+
71
+ Then /^I should get all of the objects having that value$/ do
72
+ @results.should == @all_objects.select {|x| x.color.raw_value == @desired_color}
73
+ end
74
+
75
+ Then /^I should not get any objects having other values$/ do
76
+ @results.reject {|x| x.color.to_sym == @desired_color}.should be_empty
77
+ end
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ Bundler.require(:default, :test)
6
+
7
+ require 'extended_has_enumeration'
8
+
9
+ ActiveRecord::Base.establish_connection(
10
+ :adapter => defined?(JRUBY_VERSION) ? 'jdbcsqlite3': 'sqlite3',
11
+ :database => File.expand_path('../database', __FILE__)
12
+ )
13
+
14
+ if ActiveRecord::VERSION::MAJOR >= 3
15
+ Bundler.require(:meta_where)
16
+ end
17
+
18
+ class CreateTables < ActiveRecord::Migration
19
+ create_table :explicitly_mapped_models, :force => true do |t|
20
+ t.integer :color
21
+ end
22
+
23
+ create_table :implicitly_mapped_models, :force => true do |t|
24
+ t.string :color
25
+ end
26
+
27
+ create_table :nonstandard_attribute_models, :force => true do |t|
28
+ t.integer :hue
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ class ExplicitlyMappedModel < ActiveRecord::Base
2
+ has_enumeration :color, :red => 'Red color', :green => 2, :blue => 3
3
+ end
@@ -0,0 +1,3 @@
1
+ class ImplicitlyMappedModel < ActiveRecord::Base
2
+ has_enumeration :color, [:red, :green, :blue]
3
+ end
@@ -0,0 +1,4 @@
1
+ class NonstandardAttributeModel < ActiveRecord::Base
2
+ has_enumeration :color, {:red => 1, :green => 2, :blue => 3},
3
+ :attribute => :hue
4
+ end
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ source ~/.rvm/scripts/rvm
3
+ for ruby in 1.8.7 1.9.2 jruby rbx
4
+ do
5
+ rvm use $ruby
6
+ for ar_rev in 2.3.10 3.0.x
7
+ do
8
+ gemset=ar_$ar_rev
9
+ echo yes | rvm gemset delete $gemset
10
+ rvm gemset create $gemset
11
+ rvm gemset use $gemset
12
+ AR_VERSION=$ar_rev bundle install
13
+ done
14
+ done
@@ -0,0 +1,16 @@
1
+ module ExtendedHasEnumeration
2
+ module AggregateConditionsOverride
3
+ # Override the aggregate hash conditions behavior to coerce has_enumeration
4
+ # attributes that show up in finder options as symbols into instances of
5
+ # the aggregate class before hash expansion.
6
+ def expand_hash_conditions_for_aggregates(attrs)
7
+ expanded_attrs = attrs.dup
8
+ attr_enumeration_mapping_classes.each do |attr, klass|
9
+ if expanded_attrs[attr].is_a?(Symbol)
10
+ expanded_attrs[attr] = klass.from_sym(expanded_attrs[attr])
11
+ end
12
+ end
13
+ super(expanded_attrs)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,66 @@
1
+ module ExtendedHasEnumeration
2
+ module Arel
3
+ module TableExtensions
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :columns, :has_enumeration
7
+
8
+ def self.has_enumeration_mappings
9
+ @has_enumeration_mappings ||= Hash.new {|h,k| h[k] = Hash.new}
10
+ end
11
+ end
12
+ end
13
+
14
+ def columns_with_has_enumeration
15
+ return @columns if @columns
16
+ columns = columns_without_has_enumeration
17
+ mappings = self.class.has_enumeration_mappings[name]
18
+ if mappings
19
+ mappings.each do |attr_name, mapping|
20
+ attr = columns.detect {|c| c.name.to_s == attr_name.to_s}
21
+ install_has_enumeration_attribute_mapping(attr, mapping)
22
+ end
23
+ end
24
+ columns
25
+ end
26
+
27
+ private
28
+ def install_has_enumeration_attribute_mapping(arel_attr, mapping)
29
+ # For this attribute only, override all of the methods defined
30
+ # in Arel::Attribute::PREDICATES so that they will perform the
31
+ # symbol-to-underlying-value mapping before proceeding with their work.
32
+ (class <<arel_attr;self;end).class_eval do
33
+ define_method :map_enumeration_arg do |arg|
34
+ if arg.is_a?(Symbol)
35
+ unless mapping.has_key?(arg)
36
+ raise ArgumentError.new(
37
+ "#{arg.inspect} is not one of {#{mapping.keys.map(&:inspect).sort.join(', ')}}"
38
+ )
39
+ end
40
+ mapping[arg]
41
+ elsif arg.is_a?(Array)
42
+ arg.map {|a| map_enumeration_arg(a)}
43
+ else
44
+ arg
45
+ end
46
+ end
47
+
48
+ predicates = ::Arel::Predications.instance_methods.map &:to_sym
49
+ predicates.each do |method_name|
50
+ arity = ::Arel::Attribute.instance_method(method_name).arity
51
+ case arity
52
+ when 1
53
+ define_method method_name do |arg|
54
+ super(map_enumeration_arg(arg))
55
+ end
56
+ when 0
57
+ # No-op
58
+ else
59
+ raise "Unexpected arity #{arity} for #{method_name}"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,70 @@
1
+ # Support for Arel 1. Will eventually be removed once most people are on
2
+ # Arel 2.
3
+ module ExtendedHasEnumeration
4
+ module Arel
5
+ module TableExtensionsArelOne
6
+ def self.included(base)
7
+ base.class_eval do
8
+ alias_method_chain :attributes, :has_enumeration
9
+
10
+ def self.has_enumeration_mappings
11
+ @has_enumeration_mappings ||= Hash.new {|h,k| h[k] = Hash.new}
12
+ end
13
+ end
14
+ end
15
+
16
+ def attributes_with_has_enumeration
17
+ return @attributes if @attributes
18
+ attrs = attributes_without_has_enumeration
19
+ mappings = self.class.has_enumeration_mappings[name]
20
+ if mappings
21
+ mappings.each do |attr_name, mapping|
22
+ attr = attrs[attr_name]
23
+ install_has_enumeration_attribute_mapping(attr, mapping)
24
+ end
25
+ end
26
+ attrs
27
+ end
28
+
29
+ private
30
+ def install_has_enumeration_attribute_mapping(arel_attr, mapping)
31
+ # For this attribute only, override all of the methods defined
32
+ # in Arel::Attribute::PREDICATES so that they will perform the
33
+ # symbol-to-underlying-value mapping before proceeding with their work.
34
+ (class <<arel_attr;self;end).class_eval do
35
+ define_method :map_enumeration_arg do |arg|
36
+ if arg.is_a?(Symbol)
37
+ unless mapping.has_key?(arg)
38
+ raise ArgumentError.new(
39
+ "#{arg.inspect} is not one of {#{mapping.keys.map(&:inspect).sort.join(', ')}}"
40
+ )
41
+ end
42
+ mapping[arg]
43
+ elsif arg.is_a?(Array)
44
+ arg.map {|a| map_enumeration_arg(a)}
45
+ else
46
+ arg
47
+ end
48
+ end
49
+
50
+ ::Arel::Attribute::PREDICATES.each do |method_name|
51
+ # Preserve the arity of the method we are overriding
52
+ arity = ::Arel::Attribute.instance_method(method_name).arity
53
+ case arity
54
+ when 1
55
+ define_method method_name do |arg|
56
+ super(map_enumeration_arg(arg))
57
+ end
58
+ when -1
59
+ define_method method_name do |*args|
60
+ super(map_enumeration_arg(args))
61
+ end
62
+ else
63
+ raise "Unexpected arity #{arity} for #{method_name}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,122 @@
1
+ module ExtendedHasEnumeration
2
+ module ClassMethods
3
+ # Declares an enumerated attribute called +enumeration+ consisting of
4
+ # the symbols defined in +mapping+.
5
+ #
6
+ # When the database representation of the attribute is a string, +mapping+
7
+ # can be an array of symbols. The string representation of the symbol
8
+ # will be stored in the databased. E.g.:
9
+ #
10
+ # has_enumeration :color, [:red, :green, :blue]
11
+ #
12
+ # When the database representation of the attribute is not a string, or
13
+ # if its values do not match up with the string versions of its symbols,
14
+ # an hash mapping symbols to their underlying values may be used:
15
+ #
16
+ # has_enumeration :color, :red => 1, :green => 2, :blue => 3
17
+ #
18
+ # By default, has_enumeration assumes that the column in the database
19
+ # has the same name as the enumeration. If this is not the case, the
20
+ # column can be specified with the :attribute option:
21
+ #
22
+ # has_enumeration :color, [:red, :green, :blue], :attribute => :hue
23
+ #
24
+ def has_enumeration(enumeration, mapping, options = {})
25
+ unless mapping.is_a?(Hash)
26
+ # Recast the mapping as a symbol -> string hash
27
+ mapping_hash = {}
28
+ mapping.each {|m| mapping_hash[m] = m.to_s}
29
+ mapping = mapping_hash
30
+ end
31
+
32
+ # The underlying attribute
33
+ attribute = options[:attribute] || enumeration
34
+
35
+ # ActiveRecord's composed_of method will do most of the work for us.
36
+ # All we have to do is cons up a class that implements the bidirectional
37
+ # mapping described by the provided hash.
38
+ klass = create_enumeration_mapping_class(mapping)
39
+ attr_enumeration_mapping_classes[enumeration] = klass
40
+
41
+ # Bind the class to a name within the scope of this class
42
+ mapping_class_name = enumeration.to_s.camelize
43
+ const_set(mapping_class_name, klass)
44
+ scoped_class_name = [self.name, mapping_class_name].join('::')
45
+
46
+ composed_of(enumeration,
47
+ :class_name => scoped_class_name,
48
+ :mapping => [attribute.to_s, 'raw_value'],
49
+ :converter => :from_sym,
50
+ :allow_nil => true
51
+ )
52
+
53
+ if ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR == 0
54
+ # Install this attributes mapping for use later when extending
55
+ # Arel attributes on the fly.
56
+ ::Arel::Table.has_enumeration_mappings[table_name][attribute] = mapping
57
+ else
58
+ # Install our aggregate condition handling override, but only once
59
+ unless @aggregate_conditions_override_installed
60
+ extend ExtendedHasEnumeration::AggregateConditionsOverride
61
+ @aggregate_conditions_override_installed = true
62
+ end
63
+ end
64
+ end
65
+
66
+ private
67
+ def attr_enumeration_mapping_classes
68
+ @attr_enumeration_mapping_classes ||= {}
69
+ end
70
+
71
+ def create_enumeration_mapping_class(mapping)
72
+ mapping = mapping.with_indifferent_access
73
+ Class.new do
74
+ attr_reader :raw_value
75
+ alias_method :humanize, :raw_value
76
+
77
+ define_method :initialize do |raw_value|
78
+ @raw_value = raw_value
79
+ @value = mapping[raw_value]
80
+ end
81
+
82
+ define_method :to_sym do
83
+ @value
84
+ end
85
+
86
+ define_method :value do
87
+ @values
88
+ end
89
+
90
+ define_method :to_s do
91
+ @value.to_s
92
+ end
93
+
94
+ define_method :humanize do
95
+ @raw_value.to_s.humanize
96
+ end
97
+
98
+ mapping.keys.each do |sym|
99
+ predicate = "#{sym}?".to_sym
100
+ value = mapping[sym]
101
+ define_method predicate do
102
+ @raw_value.to_sym == sym.to_sym
103
+ end
104
+ end
105
+
106
+ (class <<self;self;end).class_eval do
107
+ define_method :source do
108
+ mapping
109
+ end
110
+ define_method :from_sym do |sym|
111
+ unless mapping.has_key?(sym)
112
+ raise ArgumentError.new(
113
+ "#{sym.inspect} is not one of {#{mapping.keys.map(&:inspect).sort.join(', ')}}"
114
+ )
115
+ end
116
+ new(sym)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,8 +1,8 @@
1
1
  require 'active_record'
2
2
 
3
3
  # Install our common ActiveRecord extentions
4
- require 'has_enumeration/class_methods'
5
- ActiveRecord::Base.extend(HasEnumeration::ClassMethods)
4
+ require 'extended_has_enumeration/class_methods'
5
+ ActiveRecord::Base.extend(ExtendedHasEnumeration::ClassMethods)
6
6
 
7
7
  # We have two primary use cases, versions of ActiveRecord which define the column mapping in Arel,
8
8
  # and those which do not. For ActiveRecord 2.x.x and versions ActiveRecord which rely on
@@ -12,14 +12,14 @@ ActiveRecord::Base.extend(HasEnumeration::ClassMethods)
12
12
  # whereas older versions of Arel need the code in HasEnumeration::Arel::TableExtensionsArelOne.
13
13
  if ActiveRecord::VERSION::MAJOR >= 3
14
14
  if Arel::VERSION >= "3.0.0"
15
- require 'has_enumeration/aggregate_conditions_override'
15
+ require 'extended_has_enumeration/aggregate_conditions_override'
16
16
  elsif Arel::VERSION >= '2.0.0'
17
- require 'has_enumeration/arel/table_extensions'
17
+ require 'extended_has_enumeration/arel/table_extensions'
18
18
  Arel::Table.send(:include, HasEnumeration::Arel::TableExtensions)
19
19
  else
20
- require 'has_enumeration/arel/table_extensions_arel_one'
20
+ require 'extended_has_enumeration/arel/table_extensions_arel_one'
21
21
  Arel::Table.send(:include, HasEnumeration::Arel::TableExtensionsArelOne)
22
22
  end
23
23
  else
24
- require 'has_enumeration/aggregate_conditions_override'
24
+ require 'extended_has_enumeration/aggregate_conditions_override'
25
25
  end
@@ -0,0 +1,100 @@
1
+ require File.expand_path('../spec_helper', File.dirname(__FILE__))
2
+
3
+ describe ExtendedHasEnumeration, 'with invalid values' do
4
+ before(:each) do
5
+ @model = ExplicitlyMappedModel.new
6
+ end
7
+
8
+ it 'raises an exception when assigned an invalid value' do
9
+ lambda do
10
+ @model.color = :beige
11
+ end.should raise_error(ArgumentError, ':beige is not one of {"blue", "green", "red"}')
12
+ end
13
+
14
+ if ActiveRecord::VERSION::MAJOR >= 3
15
+ context 'with ActiveRecord 3.x' do
16
+ it 'raises an exception when finding with an invalid value' do
17
+ lambda do
18
+ ExplicitlyMappedModel.where(:color => :beige).all
19
+ end.should raise_error(ArgumentError, ':beige is not one of {"blue", "green", "red"}')
20
+ end
21
+
22
+ #TODO: handle porting to some other meta_where equivalent that is forward compatible with ActiveRecord 3.1.x+
23
+ #it 'raises an exception when finding with an invalid value via meta_where' do
24
+ # lambda do
25
+ # ExplicitlyMappedModel.where(:color.not_eq => :beige).all
26
+ # end.should raise_error(ArgumentError, ':beige is not one of {:blue, :green, :red}')
27
+ #end
28
+ end
29
+ else
30
+ context 'With ActiveRecord 2.x' do
31
+ it 'raises an exception when finding with an invalid value' do
32
+ lambda do
33
+ ExplicitlyMappedModel.find(:all, :conditions => {:color => :beige})
34
+ end.should raise_error(ArgumentError, ':beige is not one of {"blue", "green", "red"}')
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ describe ExtendedHasEnumeration, 'with an uninitialied value' do
41
+ context 'in a newly-created object' do
42
+ it 'returns nil for the value of the enumeration' do
43
+ ExplicitlyMappedModel.new.color.should be_nil
44
+ end
45
+ end
46
+
47
+ context 'in an existing object' do
48
+ it 'returns nil for the value of the enumeration' do
49
+ object = ExplicitlyMappedModel.find(ExplicitlyMappedModel.create!.id)
50
+ object.color.should be_nil
51
+ end
52
+ end
53
+ end
54
+
55
+ describe ExtendedHasEnumeration, 'assignment of nil' do
56
+ it 'sets the enumeration to nil' do
57
+ object = ExplicitlyMappedModel.new(:color => :red)
58
+ object.color = nil
59
+ object.color.should be_nil
60
+ end
61
+
62
+ it 'persists across a trip to the database' do
63
+ object = ExplicitlyMappedModel.create!(:color => :red)
64
+ object.color = nil
65
+ object.save!
66
+ ExplicitlyMappedModel.find(object.id).color.should be_nil
67
+ end
68
+ end
69
+
70
+ describe ExtendedHasEnumeration, 'string formatting' do
71
+ it 'returns the value as a string if to_s is called on it' do
72
+ object = ExplicitlyMappedModel.new(:color => :red)
73
+ object.color.to_s.should == 'Red color'
74
+ end
75
+ end
76
+
77
+ describe ExtendedHasEnumeration, 'hash value' do
78
+ it 'returns the raw value as a string if raw_value is called on it' do
79
+ object = ExplicitlyMappedModel.new(:color => :red)
80
+ object.color.raw_value.should == :red
81
+ end
82
+
83
+ it 'returns the raw value as a string if humanize is called on it' do
84
+ object = ExplicitlyMappedModel.new(:color => :red)
85
+ object.color.humanize.should == 'Red'
86
+ end
87
+ end
88
+
89
+ describe ExtendedHasEnumeration, 'source' do
90
+ it 'returns the passed hash' do
91
+ ExplicitlyMappedModel::Color.source.should == {'red'=>'Red color', 'green'=>2, 'blue'=>3}
92
+ end
93
+ end
94
+
95
+ describe ExtendedHasEnumeration, 'has hash with with_indifferent_access' do
96
+ it 'it allows assign string' do
97
+ object = ExplicitlyMappedModel.create!(:color => 'red')
98
+ object.color.raw_value.should == 'red'
99
+ end
100
+ end
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require(:default, :test)
4
+
5
+ require 'rspec'
6
+
7
+ require File.expand_path('../lib/extended_has_enumeration', File.dirname(__FILE__))
8
+ require File.expand_path('../features/support/explicitly_mapped_model', File.dirname(__FILE__))
9
+
10
+ ActiveRecord::Base.establish_connection(
11
+ :adapter => defined?(JRUBY_VERSION) ? 'jdbcsqlite3': 'sqlite3',
12
+ :database => File.expand_path('../database', __FILE__)
13
+ )
14
+
15
+ if ActiveRecord::VERSION::MAJOR >= 3
16
+ Bundler.require(:meta_where)
17
+ end
18
+
19
+ class CreateTables < ActiveRecord::Migration
20
+ create_table :explicitly_mapped_models, :force => true do |t|
21
+ t.integer :color
22
+ end
23
+ end
metadata CHANGED
@@ -1,32 +1,101 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extended_has_enumeration
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
- - Evan Dowling
9
- - Maxim Tsaplin
8
+ - Greg Spurrier
10
9
  autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2013-06-16 00:00:00.000000000 Z
14
- dependencies: []
15
- description: Extends ActiveRecord with the has_enumeration method allowing a symbolic
16
- enumeration to be stored in an ActiveRecord attribute. The enumeration is specified
17
- as a mapping between symbols and their underlying representation in the database.
18
- Predicates are provided for each symbol in the enumeration and the symbols may be
19
- used in finder methods. When using ActiveRecord 3, the symbols may also be used
20
- when interacting with the underlying Arel attribute for the enumeration. has_enumeration
21
- has been tested with Ruby 1.8.7, Ruby 1.9.2, JRuby 1.5.5, Rubinius 1.1.0, ActiveRecord
22
- 2.3.10, and ActiveRecord 3.0.3.
23
- email: maxtsap@gamil.com
12
+ date: 2012-04-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.3.10
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.3.10
30
+ - !ruby/object:Gem::Dependency
31
+ name: builder
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: ! 'Extends ActiveRecord with the has_enumeration method allowing a symbolic
47
+
48
+ enumeration to be stored in an ActiveRecord attribute. The enumeration is
49
+
50
+ specified as a mapping between symbols and their underlying representation
51
+
52
+ in the database. Predicates are provided for each symbol in the enumeration
53
+
54
+ and the symbols may be used in finder methods. When using ActiveRecord 3,
55
+
56
+ the symbols may also be used when interacting with the underlying Arel attribute
57
+
58
+ for the enumeration. has_enumeration has been tested with Ruby 1.8.7,
59
+
60
+ Ruby 1.9.2, JRuby 1.5.5, Rubinius 1.1.0, ActiveRecord 2.3.10, and ActiveRecord
61
+
62
+ 3.0.3.
63
+
64
+ '
65
+ email: greg@rujubu.com
24
66
  executables: []
25
67
  extensions: []
26
- extra_rdoc_files: []
68
+ extra_rdoc_files:
69
+ - LICENSE.txt
70
+ - README.md
27
71
  files:
28
- - lib/has_enumeration.rb
29
- homepage: http://rubygems.org/gems/extended_has_enumeration
72
+ - Gemfile
73
+ - HISTORY.txt
74
+ - LICENSE.txt
75
+ - README.md
76
+ - Rakefile
77
+ - TODO.md
78
+ - VERSION
79
+ - all_tests.sh
80
+ - features/arel_attributes.feature
81
+ - features/explicitly_mapped_enumeration.feature
82
+ - features/implicitly_mapped_enumeration.feature
83
+ - features/meta_where_queries.feature
84
+ - features/nonstandard_attribute_enumeration.feature
85
+ - features/step_definitions/has_enumeration_steps.rb
86
+ - features/support/env.rb
87
+ - features/support/explicitly_mapped_model.rb
88
+ - features/support/implicitly_mapped_model.rb
89
+ - features/support/nonstandard_attribute_model.rb
90
+ - install_gemsets.sh
91
+ - lib/extended_has_enumeration.rb
92
+ - lib/extended_has_enumeration/aggregate_conditions_override.rb
93
+ - lib/extended_has_enumeration/arel/table_extensions.rb
94
+ - lib/extended_has_enumeration/arel/table_extensions_arel_one.rb
95
+ - lib/extended_has_enumeration/class_methods.rb
96
+ - spec/extended_has_enumeration/extended_has_enumeration_spec.rb
97
+ - spec/spec_helper.rb
98
+ homepage: http://github.com/maxtsap/has_enumeration
30
99
  licenses: []
31
100
  post_install_message:
32
101
  rdoc_options: []
@@ -49,5 +118,5 @@ rubyforge_project:
49
118
  rubygems_version: 1.8.25
50
119
  signing_key:
51
120
  specification_version: 3
52
- summary: Extended has_enumeration
121
+ summary: Support for symbol-based enumerations in ActiveRecord
53
122
  test_files: []