pluginaweek-enumerate_by 0.4.2
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.rdoc +111 -0
- data/LICENSE +20 -0
- data/README.rdoc +117 -0
- data/Rakefile +96 -0
- data/init.rb +1 -0
- data/lib/enumerate_by/extensions/associations.rb +109 -0
- data/lib/enumerate_by/extensions/base_conditions.rb +130 -0
- data/lib/enumerate_by/extensions/serializer.rb +117 -0
- data/lib/enumerate_by/extensions/xml_serializer.rb +41 -0
- data/lib/enumerate_by.rb +395 -0
- data/test/app_root/app/models/car.rb +4 -0
- data/test/app_root/app/models/color.rb +3 -0
- data/test/app_root/db/migrate/001_create_colors.rb +12 -0
- data/test/app_root/db/migrate/002_create_cars.rb +13 -0
- data/test/factory.rb +48 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/assocations_test.rb +70 -0
- data/test/unit/base_conditions_test.rb +46 -0
- data/test/unit/enumerate_by_test.rb +442 -0
- data/test/unit/json_serializer_test.rb +18 -0
- data/test/unit/serializer_test.rb +166 -0
- data/test/unit/xml_serializer_test.rb +71 -0
- metadata +87 -0
data/CHANGELOG.rdoc
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
== master
|
|
2
|
+
|
|
3
|
+
* Improve compatibility with the stable branch of Rails 2.3 [Michael Schuerig]
|
|
4
|
+
|
|
5
|
+
== 0.4.2 / 2009-05-03
|
|
6
|
+
|
|
7
|
+
* Fix bootstrapping without ids not working on update for certain database adapters
|
|
8
|
+
|
|
9
|
+
== 0.4.1 / 2009-05-01
|
|
10
|
+
|
|
11
|
+
* Improve #fast_bootstrap speed by 50% by using the connection directly
|
|
12
|
+
|
|
13
|
+
== 0.4.0 / 2009-04-30
|
|
14
|
+
|
|
15
|
+
* Allow cache to be cleared on a per-enumeration basis
|
|
16
|
+
* Add #fast_bootstrap for bootstrapping large numbers of records
|
|
17
|
+
* Don't require an id during bootstrap
|
|
18
|
+
* Allow bootstrapping to be easily added to non-enumerations
|
|
19
|
+
* Cache results for #exists? / #calculate
|
|
20
|
+
* Add the ability to skip the internal cache via Model#uncached
|
|
21
|
+
* Add Model#find_all_by_enumerator
|
|
22
|
+
* Don't internally rely on Model#[] being available since it may conflict with other plugins
|
|
23
|
+
* Enable caching by default for all enumerations
|
|
24
|
+
* Allow caching to be turned off on an application-wide basis
|
|
25
|
+
* Allow cache store to be configurable
|
|
26
|
+
* Automatically trigger in-memory caching of the enumeration's table when bootstrapping
|
|
27
|
+
* Add #bootstrap for automatically synchronizing the records in an enumeration's table
|
|
28
|
+
* Improve serialization performance
|
|
29
|
+
* No longer use tableless models
|
|
30
|
+
* Re-brand under the enumerate_by name
|
|
31
|
+
|
|
32
|
+
== 0.3.0 / 2008-12-14
|
|
33
|
+
|
|
34
|
+
* Remove the PluginAWeek namespace
|
|
35
|
+
|
|
36
|
+
== 0.2.6 / 2008-11-29
|
|
37
|
+
|
|
38
|
+
* Fix enumeration collections not being able to convert to JSON
|
|
39
|
+
* Add support for multiple enumeration values in finder conditions, e.g. Car.find_all_by_color(%w(red blue))
|
|
40
|
+
|
|
41
|
+
== 0.2.5 / 2008-10-26
|
|
42
|
+
|
|
43
|
+
* Fix non-ActiveRecord associations (e.g. ActiveResource) failing
|
|
44
|
+
* Fix reloading of associations not working
|
|
45
|
+
* Raise an exception if equality is performed with an invalid enumeration identifier
|
|
46
|
+
* Change how the base module is included to prevent namespacing conflicts
|
|
47
|
+
|
|
48
|
+
== 0.2.4 / 2008-08-31
|
|
49
|
+
|
|
50
|
+
* Add support for serialization in JSON/XML
|
|
51
|
+
|
|
52
|
+
== 0.2.3 / 2008-06-29
|
|
53
|
+
|
|
54
|
+
* Fix named scope for enumerations that belong_to other enumerations
|
|
55
|
+
|
|
56
|
+
== 0.2.2 / 2008-06-23
|
|
57
|
+
|
|
58
|
+
* Remove log files from gems
|
|
59
|
+
|
|
60
|
+
== 0.2.1 / 2008-06-22
|
|
61
|
+
|
|
62
|
+
* Improve documentation
|
|
63
|
+
|
|
64
|
+
== 0.2.0 / 2008-06-22
|
|
65
|
+
|
|
66
|
+
* Improve performance by disabling unnecessary ActiveRecord hooks including callbacks, dirty attributes, timestamps, and transactions (important for enumerations with large sets of values)
|
|
67
|
+
* Don't let #create silently fail
|
|
68
|
+
* Remove ability to reset the cache
|
|
69
|
+
* Improve performance by adding pre-indexing of enumeration attributes (important for enumerations with large sets of values)
|
|
70
|
+
* Remove support for array comparison
|
|
71
|
+
* Remove support for multiple enumeration attributes
|
|
72
|
+
|
|
73
|
+
== 0.1.2 / 2008-06-15
|
|
74
|
+
|
|
75
|
+
* Avoid string evaluation for dynamic methods
|
|
76
|
+
* Fix has_many/has_one associations improperly loading classes too early
|
|
77
|
+
* Add support for string and array comparison
|
|
78
|
+
* Use after_create/after_destroy callbacks instead of defining the callback method itself
|
|
79
|
+
|
|
80
|
+
== 0.1.1 / 2008-05-14
|
|
81
|
+
|
|
82
|
+
* Fix automatically clearing association cache when it shouldn't be
|
|
83
|
+
|
|
84
|
+
== 0.1.0 / 2008-05-05
|
|
85
|
+
|
|
86
|
+
* Add support for overriding the unique attribute that defines an enumeration e.g.
|
|
87
|
+
|
|
88
|
+
acts_as_enumeration :title
|
|
89
|
+
acts_as_enumeration :controller, :action
|
|
90
|
+
|
|
91
|
+
* Add support for using enumerations in has_many/has_one associations
|
|
92
|
+
* Add support for Rails 2.0
|
|
93
|
+
* Use has_finder to auto-generate finders for each enumeration value after defining a belongs_to association
|
|
94
|
+
* Removed support for database-backed enumerations in favor of always using virtual enumerations
|
|
95
|
+
* Fix enumerations failing when being reloaded
|
|
96
|
+
* Fix problems with setting enumeration attributes to nil
|
|
97
|
+
* Add inheritance support for virtual enumerations
|
|
98
|
+
* Add support for converting unsafe identifier names (like "red!") to their safe symbol equivalent ("red")
|
|
99
|
+
* Add ability to use truth accessors for determing the identifier name
|
|
100
|
+
* Add support for virtual enumerations that don't need to be backed by the database
|
|
101
|
+
|
|
102
|
+
== 0.0.2 / 2007-09-26
|
|
103
|
+
|
|
104
|
+
* Move test fixtures out of the test application root directory
|
|
105
|
+
* Convert dos newlines to unix newlines
|
|
106
|
+
|
|
107
|
+
== 0.0.1 / 2007-08-04
|
|
108
|
+
|
|
109
|
+
* Initial public release
|
|
110
|
+
* Add/refactor unit tests
|
|
111
|
+
* Add documentation
|
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2006-2009 Aaron Pfeifer
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
= enumerate_by
|
|
2
|
+
|
|
3
|
+
+enumerate_by+ adds support for declaring an ActiveRecord class as an
|
|
4
|
+
enumeration.
|
|
5
|
+
|
|
6
|
+
== Resources
|
|
7
|
+
|
|
8
|
+
API
|
|
9
|
+
|
|
10
|
+
* http://api.pluginaweek.org/enumerate_by
|
|
11
|
+
|
|
12
|
+
Bugs
|
|
13
|
+
|
|
14
|
+
* http://pluginaweek.lighthouseapp.com/projects/13265-enumerate_by
|
|
15
|
+
|
|
16
|
+
Development
|
|
17
|
+
|
|
18
|
+
* http://github.com/pluginaweek/enumerate_by
|
|
19
|
+
|
|
20
|
+
Source
|
|
21
|
+
|
|
22
|
+
* git://github.com/pluginaweek/enumerate_by.git
|
|
23
|
+
|
|
24
|
+
== Description
|
|
25
|
+
|
|
26
|
+
Support for enumerations is dependent on the type of database you use. For
|
|
27
|
+
example, MySQL has native support for the enum data type. However, there is
|
|
28
|
+
no native Rails support for defining enumerations and the associations between
|
|
29
|
+
it and other models in the application.
|
|
30
|
+
|
|
31
|
+
In addition, enumerations may often have more complex data and/or functionality
|
|
32
|
+
associated with it that cannot simply be describe in a single column.
|
|
33
|
+
|
|
34
|
+
enumerate_by adds support for pseudo-enumerations in Rails by allowing the
|
|
35
|
+
enumeration's records to be defined in code, but continuing to express the
|
|
36
|
+
relationship between an enumeration and other models using ActiveRecord
|
|
37
|
+
associations. The important thing to remember, however, is that while the
|
|
38
|
+
associations exist, the enumerator is always used outside of the application,
|
|
39
|
+
while either the enumerator or the enumerator's record would be referenced
|
|
40
|
+
*within* the application. This means that you would reference a Color record
|
|
41
|
+
via it's enumerator (such as "red") everywhere in the code (conditions,
|
|
42
|
+
assigning associations, forms, etc.), but it would always be stored in the
|
|
43
|
+
database as a true association with the integer value of 1.
|
|
44
|
+
|
|
45
|
+
== Usage
|
|
46
|
+
|
|
47
|
+
class Color < ActiveRecord::Base
|
|
48
|
+
enumerate_by :name
|
|
49
|
+
|
|
50
|
+
belongs_to :group, :class_name => 'ColorGroup'
|
|
51
|
+
has_many :cars
|
|
52
|
+
|
|
53
|
+
bootstrap(
|
|
54
|
+
{:id => 1, :name => 'red', :group => 'RGB'},
|
|
55
|
+
{:id => 2, :name => 'blue', :group => 'RGB'},
|
|
56
|
+
{:id => 3, :name => 'green', :group => 'RGB'},
|
|
57
|
+
{:id => 4, :name => 'cyan', :group => 'CMYK'}
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class ColorGroup < ActiveRecord::Base
|
|
62
|
+
enumerate_by :name
|
|
63
|
+
|
|
64
|
+
has_many :colors, :foreign_key => 'group_id'
|
|
65
|
+
|
|
66
|
+
bootstrap(
|
|
67
|
+
{:id => 1, :name => 'RGB'},
|
|
68
|
+
{:id => 2, :name => 'CMYK'}
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class Car < ActiveRecord::Base
|
|
73
|
+
belongs_to :color
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
Each of the above models is backed by the database with its own table. Both
|
|
77
|
+
the Color and ColorGroup enumerations automatically synchronize with the
|
|
78
|
+
records in the database based on what's define in their bootstrap data.
|
|
79
|
+
|
|
80
|
+
The enumerations and their associations can be then be used like so:
|
|
81
|
+
|
|
82
|
+
car = Car.create(:color => 'red') # => #<Car id: 1, color_id: 1>
|
|
83
|
+
car.color # => #<Color id: 1, name: "red">
|
|
84
|
+
car.color == 'red' # => true
|
|
85
|
+
car.color.name # => "red"
|
|
86
|
+
|
|
87
|
+
car.color = 'blue'
|
|
88
|
+
car.save # => true
|
|
89
|
+
car # => #<Car id: 1, color_id: 2>
|
|
90
|
+
|
|
91
|
+
# Serialization
|
|
92
|
+
car.to_json # => "{id: 1, color: \"blue\"}"
|
|
93
|
+
car.to_xml # => "<car><id type=\"integer\">1</id><color>blue</color></car>"
|
|
94
|
+
|
|
95
|
+
# Lookup
|
|
96
|
+
Car.with_color('blue') # => [#<Car id: 1, color_id: 2>]
|
|
97
|
+
car = Car.find_by_color('blue') # => #<Car id: 1, color_id: 2>
|
|
98
|
+
car.color == Color['blue'] # => true
|
|
99
|
+
|
|
100
|
+
As mentioned previously, the important thing to note from the above example is
|
|
101
|
+
that from an external perspective, "color" is simply an attribute on the Car.
|
|
102
|
+
However, it's backed by a more complex association and model that allows Color
|
|
103
|
+
to include advanced functionality that would normally not be possible with a
|
|
104
|
+
simple attribute.
|
|
105
|
+
|
|
106
|
+
== Testing
|
|
107
|
+
|
|
108
|
+
Before you can run any tests, the following gem must be installed:
|
|
109
|
+
* plugin_test_helper[http://github.com/pluginaweek/plugin_test_helper]
|
|
110
|
+
|
|
111
|
+
To run against a specific version of Rails:
|
|
112
|
+
|
|
113
|
+
rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
|
|
114
|
+
|
|
115
|
+
== Dependencies
|
|
116
|
+
|
|
117
|
+
* Rails 2.1 or later
|
data/Rakefile
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'rake/testtask'
|
|
2
|
+
require 'rake/rdoctask'
|
|
3
|
+
require 'rake/gempackagetask'
|
|
4
|
+
require 'rake/contrib/sshpublisher'
|
|
5
|
+
|
|
6
|
+
spec = Gem::Specification.new do |s|
|
|
7
|
+
s.name = 'enumerate_by'
|
|
8
|
+
s.version = '0.4.2'
|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
|
10
|
+
s.summary = 'Adds support for declaring an ActiveRecord class as an enumeration'
|
|
11
|
+
s.description = s.summary
|
|
12
|
+
|
|
13
|
+
s.files = FileList['{lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
|
14
|
+
s.require_path = 'lib'
|
|
15
|
+
s.has_rdoc = true
|
|
16
|
+
s.test_files = Dir['test/**/*_test.rb']
|
|
17
|
+
|
|
18
|
+
s.author = 'Aaron Pfeifer'
|
|
19
|
+
s.email = 'aaron@pluginaweek.org'
|
|
20
|
+
s.homepage = 'http://www.pluginaweek.org'
|
|
21
|
+
s.rubyforge_project = 'pluginaweek'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'Default: run all tests.'
|
|
25
|
+
task :default => :test
|
|
26
|
+
|
|
27
|
+
desc "Test the #{spec.name} plugin."
|
|
28
|
+
Rake::TestTask.new(:test) do |t|
|
|
29
|
+
t.libs << 'lib'
|
|
30
|
+
t.test_files = spec.test_files
|
|
31
|
+
t.verbose = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
require 'rcov/rcovtask'
|
|
36
|
+
namespace :test do
|
|
37
|
+
desc "Test the #{spec.name} plugin with Rcov."
|
|
38
|
+
Rcov::RcovTask.new(:rcov) do |t|
|
|
39
|
+
t.libs << 'lib'
|
|
40
|
+
t.test_files = spec.test_files
|
|
41
|
+
t.rcov_opts << '--exclude="^(?!lib/)"'
|
|
42
|
+
t.verbose = true
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
rescue LoadError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "Generate documentation for the #{spec.name} plugin."
|
|
49
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
51
|
+
rdoc.title = spec.name
|
|
52
|
+
rdoc.template = '../rdoc_template.rb'
|
|
53
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
|
54
|
+
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc 'Generate a gemspec file.'
|
|
58
|
+
task :gemspec do
|
|
59
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
|
60
|
+
f.write spec.to_ruby
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Rake::GemPackageTask.new(spec) do |p|
|
|
65
|
+
p.gem_spec = spec
|
|
66
|
+
p.need_tar = true
|
|
67
|
+
p.need_zip = true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc 'Publish the beta gem.'
|
|
71
|
+
task :pgem => [:package] do
|
|
72
|
+
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
desc 'Publish the API documentation.'
|
|
76
|
+
task :pdoc => [:rdoc] do
|
|
77
|
+
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc 'Publish the API docs and gem'
|
|
81
|
+
task :publish => [:pgem, :pdoc, :release]
|
|
82
|
+
|
|
83
|
+
desc 'Publish the release files to RubyForge.'
|
|
84
|
+
task :release => [:gem, :package] do
|
|
85
|
+
require 'rubyforge'
|
|
86
|
+
|
|
87
|
+
ruby_forge = RubyForge.new.configure
|
|
88
|
+
ruby_forge.login
|
|
89
|
+
|
|
90
|
+
%w(gem tgz zip).each do |ext|
|
|
91
|
+
file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
|
|
92
|
+
puts "Releasing #{File.basename(file)}..."
|
|
93
|
+
|
|
94
|
+
ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
|
|
95
|
+
end
|
|
96
|
+
end
|
data/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'enumerate_by'
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module EnumerateBy
|
|
2
|
+
module Extensions #:nodoc:
|
|
3
|
+
# Adds a set of helpers for using enumerations in associations, including
|
|
4
|
+
# named scopes and assignment via enumerators.
|
|
5
|
+
#
|
|
6
|
+
# The examples below assume the following models have been defined:
|
|
7
|
+
#
|
|
8
|
+
# class Color < ActiveRecord::Base
|
|
9
|
+
# enumerate_by :name
|
|
10
|
+
#
|
|
11
|
+
# bootstrap(
|
|
12
|
+
# {:id => 1, :name => 'red'},
|
|
13
|
+
# {:id => 2, :name => 'blue'},
|
|
14
|
+
# {:id => 3, :name => 'green'}
|
|
15
|
+
# )
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# class Car < ActiveRecord::Base
|
|
19
|
+
# belongs_to :color
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# == Named scopes
|
|
23
|
+
#
|
|
24
|
+
# A pair of named scopes are generated for each +belongs_to+ association
|
|
25
|
+
# that is identified by an enumeration. In this case, the following
|
|
26
|
+
# named scopes get generated:
|
|
27
|
+
# * +with_color+ / +with_colors+ - Finds all cars with the given color(s)
|
|
28
|
+
# * +without_color+ / +without_colors+ - Finds all cars without the given color(s)
|
|
29
|
+
#
|
|
30
|
+
# For example,
|
|
31
|
+
#
|
|
32
|
+
# Car.with_color('red') # Cars with the color name "red"
|
|
33
|
+
# Car.without_color('red') # Cars without the color name "red"
|
|
34
|
+
# Car.with_colors('red', 'blue') # Cars with either the color names "red" or "blue"
|
|
35
|
+
#
|
|
36
|
+
# == Association assignment
|
|
37
|
+
#
|
|
38
|
+
# Normally, +belongs_to+ associations are assigned with either the actual
|
|
39
|
+
# record or through its primary key. When used with enumerations, support
|
|
40
|
+
# is added for assigning these associations through the enumerators
|
|
41
|
+
# defined for the class.
|
|
42
|
+
#
|
|
43
|
+
# For example,
|
|
44
|
+
#
|
|
45
|
+
# # With valid enumerator
|
|
46
|
+
# car = Car.new # => #<Car id: nil, color_id: nil>
|
|
47
|
+
# car.color = 'red'
|
|
48
|
+
# car.color_id # => 1
|
|
49
|
+
# car.color # => #<Color id: 1, name: "red">
|
|
50
|
+
#
|
|
51
|
+
# # With invalid enumerator
|
|
52
|
+
# car = Car.new # => #<Car id: nil, color_id: nil>
|
|
53
|
+
# car.color = 'invalid'
|
|
54
|
+
# car.color_id # => nil
|
|
55
|
+
# car.color # => nil
|
|
56
|
+
#
|
|
57
|
+
# In the above example, the actual Color association is automatically
|
|
58
|
+
# looked up by finding the Color record identified by the enumerator the
|
|
59
|
+
# given enumerator ("red" in this case).
|
|
60
|
+
module Associations
|
|
61
|
+
def self.extended(base) #:nodoc:
|
|
62
|
+
class << base
|
|
63
|
+
alias_method_chain :belongs_to, :enumerations
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Adds support for belongs_to and enumerations
|
|
68
|
+
def belongs_to_with_enumerations(association_id, options = {})
|
|
69
|
+
belongs_to_without_enumerations(association_id, options)
|
|
70
|
+
|
|
71
|
+
# Override accessor if class is valid enumeration
|
|
72
|
+
reflection = reflections[association_id.to_sym]
|
|
73
|
+
if !reflection.options[:polymorphic] && (reflection.klass < ActiveRecord::Base) && reflection.klass.enumeration?
|
|
74
|
+
name = reflection.name
|
|
75
|
+
primary_key_name = reflection.primary_key_name
|
|
76
|
+
class_name = reflection.class_name
|
|
77
|
+
klass = reflection.klass
|
|
78
|
+
|
|
79
|
+
# Inclusion scopes
|
|
80
|
+
%W(with_#{name} with_#{name.to_s.pluralize}).each do |scope_name|
|
|
81
|
+
named_scope scope_name.to_sym, lambda {|*enumerators| {
|
|
82
|
+
:conditions => {primary_key_name => klass.find_all_by_enumerator!(enumerators).map(&:id)}
|
|
83
|
+
}}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Exclusion scopes
|
|
87
|
+
%W(without_#{name} without_#{name.to_s.pluralize}).each do |scope_name|
|
|
88
|
+
named_scope scope_name.to_sym, lambda {|*enumerators| {
|
|
89
|
+
:conditions => ["#{primary_key_name} NOT IN (?)", klass.find_all_by_enumerator!(enumerators).map(&:id)]
|
|
90
|
+
}}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Hook in shortcut writer
|
|
94
|
+
define_method("#{name}_with_enumerators=") do |new_value|
|
|
95
|
+
send("#{name}_without_enumerators=", new_value.is_a?(klass) ? new_value : klass.find_by_enumerator(new_value))
|
|
96
|
+
end
|
|
97
|
+
alias_method_chain "#{name}=", :enumerators
|
|
98
|
+
|
|
99
|
+
# Track the association
|
|
100
|
+
enumeration_associations[primary_key_name.to_s] = name.to_s
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
ActiveRecord::Base.class_eval do
|
|
108
|
+
extend EnumerateBy::Extensions::Associations
|
|
109
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module EnumerateBy
|
|
2
|
+
module Extensions #:nodoc:
|
|
3
|
+
# Adds support for using enumerators in dynamic finders and conditions.
|
|
4
|
+
# For example suppose the following models are defined:
|
|
5
|
+
#
|
|
6
|
+
# class Color < ActiveRecord::Base
|
|
7
|
+
# enumerate_by :name
|
|
8
|
+
#
|
|
9
|
+
# bootstrap(
|
|
10
|
+
# {:id => 1, :name => 'red'},
|
|
11
|
+
# {:id => 2, :name => 'blue'},
|
|
12
|
+
# {:id => 3, :name => 'green'}
|
|
13
|
+
# )
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# class Car < ActiveRecord::Base
|
|
17
|
+
# belongs_to :color
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# Normally, looking up all cars associated with a particular color
|
|
21
|
+
# requires either a join or knowing the id of the color upfront:
|
|
22
|
+
#
|
|
23
|
+
# Car.find(:all, :joins => :color, :conditions => {:colors => {:name => 'red}})
|
|
24
|
+
# Car.find_by_color_id(1)
|
|
25
|
+
#
|
|
26
|
+
# Instead of doing this manually, the color can be referenced directly
|
|
27
|
+
# via its enumerator like so:
|
|
28
|
+
#
|
|
29
|
+
# # With dynamic finders
|
|
30
|
+
# Car.find_by_color('red')
|
|
31
|
+
#
|
|
32
|
+
# # With conditions
|
|
33
|
+
# Car.all(:conditions => {:color => 'red'})
|
|
34
|
+
#
|
|
35
|
+
# # With updates
|
|
36
|
+
# Car.update_all(:color => 'red')
|
|
37
|
+
#
|
|
38
|
+
# In the above examples, +color+ is essentially treated like a normal
|
|
39
|
+
# attribute on the class, instead triggering the associated Color record
|
|
40
|
+
# to be looked up and replacing the condition with a +color_id+ condition.
|
|
41
|
+
#
|
|
42
|
+
# *Note* that this does not add an additional join on the +colors+ table
|
|
43
|
+
# since the lookup of the color's id should be relatively fast when it's
|
|
44
|
+
# cached in-memory.
|
|
45
|
+
module BaseConditions
|
|
46
|
+
def self.extended(base) #:nodoc:
|
|
47
|
+
class << base
|
|
48
|
+
alias_method_chain :construct_attributes_from_arguments, :enumerations
|
|
49
|
+
alias_method_chain :sanitize_sql_hash_for_conditions, :enumerations
|
|
50
|
+
alias_method_chain :sanitize_sql_hash_for_assignment, :enumerations
|
|
51
|
+
alias_method_chain :all_attributes_exists?, :enumerations
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add support for dynamic finders
|
|
56
|
+
def construct_attributes_from_arguments_with_enumerations(attribute_names, arguments)
|
|
57
|
+
attributes = construct_attributes_from_arguments_without_enumerations(attribute_names, arguments)
|
|
58
|
+
attribute_names.each_with_index do |name, idx|
|
|
59
|
+
if options = enumerator_options_for(name, arguments[idx])
|
|
60
|
+
attributes.delete(name)
|
|
61
|
+
attributes.merge!(options)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
attributes
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
|
69
|
+
def sanitize_sql_hash_for_conditions_with_enumerations(attrs, *args)
|
|
70
|
+
replace_enumerations_in_hash(attrs)
|
|
71
|
+
sanitize_sql_hash_for_conditions_without_enumerations(attrs, *args)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
|
75
|
+
def sanitize_sql_hash_for_assignment_with_enumerations(attrs, *args)
|
|
76
|
+
replace_enumerations_in_hash(attrs, false)
|
|
77
|
+
sanitize_sql_hash_for_assignment_without_enumerations(attrs, *args)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Make sure dynamic finders don't fail since it won't find the association
|
|
81
|
+
# name in its columns
|
|
82
|
+
def all_attributes_exists_with_enumerations?(attribute_names)
|
|
83
|
+
exists = all_attributes_exists_without_enumerations?(attribute_names)
|
|
84
|
+
exists ||= attribute_names.all? do |name|
|
|
85
|
+
column_methods_hash.include?(name.to_sym) || reflect_on_enumeration(name)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
# Finds all of the attributes that are enumerations and replaces them
|
|
91
|
+
# with the correct enumerator id
|
|
92
|
+
def replace_enumerations_in_hash(attrs, allow_multiple = true) #:nodoc:
|
|
93
|
+
attrs.each do |attr, value|
|
|
94
|
+
if options = enumerator_options_for(attr, value, allow_multiple)
|
|
95
|
+
attrs.delete(attr)
|
|
96
|
+
attrs.merge!(options)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Generates the enumerator lookup options for the given association
|
|
102
|
+
# name and enumerator value. If the association is *not* for an
|
|
103
|
+
# enumeration, then this will return nil.
|
|
104
|
+
def enumerator_options_for(name, enumerator, allow_multiple = true)
|
|
105
|
+
if reflection = reflect_on_enumeration(name)
|
|
106
|
+
klass = reflection.klass
|
|
107
|
+
attribute = reflection.primary_key_name
|
|
108
|
+
id = if allow_multiple && enumerator.is_a?(Array)
|
|
109
|
+
enumerator.map {|enumerator| klass.find_by_enumerator!(enumerator).id}
|
|
110
|
+
else
|
|
111
|
+
klass.find_by_enumerator!(enumerator).id
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
{attribute => id}
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Attempts to find an association with the given name *and* represents
|
|
119
|
+
# an enumeration
|
|
120
|
+
def reflect_on_enumeration(name)
|
|
121
|
+
reflection = reflect_on_association(name.to_sym)
|
|
122
|
+
reflection if reflection && reflection.klass.enumeration?
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
ActiveRecord::Base.class_eval do
|
|
129
|
+
extend EnumerateBy::Extensions::BaseConditions
|
|
130
|
+
end
|