has_many_polymorphs 2.9 → 2.10
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +57 -0
- data/README +18 -3
- data/TODO +1 -0
- data/generators/tagging/tagging_generator.rb +12 -1
- data/generators/tagging/templates/tag_test.rb +2 -2
- data/generators/tagging/templates/tagging_test.rb +8 -4
- data/generators/tagging/templates/taggings.yml +5 -3
- data/has_many_polymorphs.gemspec +7 -6
- data/lib/has_many_polymorphs/association.rb +2 -2
- data/lib/has_many_polymorphs/autoload.rb +48 -19
- data/lib/has_many_polymorphs/base.rb +13 -3
- data/lib/has_many_polymorphs/class_methods.rb +170 -82
- data/lib/has_many_polymorphs/configuration.rb +17 -0
- data/lib/has_many_polymorphs.rb +3 -0
- data/test/fixtures/people.yml +7 -0
- data/test/models/parentship.rb +4 -0
- data/test/models/person.rb +9 -0
- data/test/models/petfood.rb +2 -1
- data/test/schema.rb +15 -0
- data/test/unit/polymorph_test.rb +83 -11
- data.tar.gz.sig +3 -0
- metadata +32 -5
- metadata.gz.sig +0 -0
data/CHANGELOG
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
|
2
|
+
v2.10. Add :parent_conditions option; bugfix for nullified conditions; bugfix for self-referential tagging generator; allow setting of has_many_polymorphs_options hash in Configuration's after_initialize if you need to adjust the autoload behavior; clear error message on missing or improperly namespaced models; fix .build on double-sided relationships; add :namespace key for easier set up of Camping apps or other unusual class structures.
|
3
|
+
|
2
4
|
v2.9. Gem version renumbering; my apologies if this messes anyone up.
|
3
5
|
|
4
6
|
v2.8. RDoc documentation; repository relocation; Rakefile cleanup; remove deprecated plugin-specific class caching.
|
data/Manifest
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
TODO
|
2
|
+
test/unit/polymorph_test.rb
|
3
|
+
test/test_helper.rb
|
4
|
+
test/schema.rb
|
5
|
+
test/modules/other_extension_module.rb
|
6
|
+
test/modules/extension_module.rb
|
7
|
+
test/models/wild_boar.rb
|
8
|
+
test/models/tabby.rb
|
9
|
+
test/models/petfood.rb
|
10
|
+
test/models/person.rb
|
11
|
+
test/models/parentship.rb
|
12
|
+
test/models/kitten.rb
|
13
|
+
test/models/frog.rb
|
14
|
+
test/models/eaters_foodstuff.rb
|
15
|
+
test/models/dog.rb
|
16
|
+
test/models/cat.rb
|
17
|
+
test/models/canine.rb
|
18
|
+
test/models/beautiful_fight_relationship.rb
|
19
|
+
test/models/aquatic/whale.rb
|
20
|
+
test/models/aquatic/pupils_whale.rb
|
21
|
+
test/models/aquatic/fish.rb
|
22
|
+
test/fixtures/wild_boars.yml
|
23
|
+
test/fixtures/petfoods.yml
|
24
|
+
test/fixtures/people.yml
|
25
|
+
test/fixtures/keep_your_enemies_close.yml
|
26
|
+
test/fixtures/frogs.yml
|
27
|
+
test/fixtures/eaters_foodstuffs.yml
|
28
|
+
test/fixtures/cats.yml
|
29
|
+
test/fixtures/bow_wows.yml
|
30
|
+
test/fixtures/aquatic/whales.yml
|
31
|
+
test/fixtures/aquatic/little_whale_pupils.yml
|
32
|
+
test/fixtures/aquatic/fish.yml
|
33
|
+
README
|
34
|
+
Manifest
|
35
|
+
LICENSE
|
36
|
+
lib/has_many_polymorphs.rb
|
37
|
+
lib/has_many_polymorphs/support_methods.rb
|
38
|
+
lib/has_many_polymorphs/reflection.rb
|
39
|
+
lib/has_many_polymorphs/rake_task_redefine_task.rb
|
40
|
+
lib/has_many_polymorphs/dependencies.rb
|
41
|
+
lib/has_many_polymorphs/debugging_tools.rb
|
42
|
+
lib/has_many_polymorphs/configuration.rb
|
43
|
+
lib/has_many_polymorphs/class_methods.rb
|
44
|
+
lib/has_many_polymorphs/base.rb
|
45
|
+
lib/has_many_polymorphs/autoload.rb
|
46
|
+
lib/has_many_polymorphs/association.rb
|
47
|
+
init.rb
|
48
|
+
generators/tagging/templates/tags.yml
|
49
|
+
generators/tagging/templates/taggings.yml
|
50
|
+
generators/tagging/templates/tagging_test.rb
|
51
|
+
generators/tagging/templates/tagging_extensions.rb
|
52
|
+
generators/tagging/templates/tagging.rb
|
53
|
+
generators/tagging/templates/tag_test.rb
|
54
|
+
generators/tagging/templates/tag.rb
|
55
|
+
generators/tagging/templates/migration.rb
|
56
|
+
generators/tagging/tagging_generator.rb
|
57
|
+
CHANGELOG
|
data/README
CHANGED
@@ -7,6 +7,8 @@ An ActiveRecord plugin for self-referential and double-sided polymorphic associa
|
|
7
7
|
|
8
8
|
Copyright 2007 Cloudburst, LLC. Licensed under the AFL 3. See the included LICENSE file.
|
9
9
|
|
10
|
+
The public certificate for this gem is at http://rubyforge.org/frs/download.php/25331/evan_weaver-original-public_cert.pem.
|
11
|
+
|
10
12
|
== Description
|
11
13
|
|
12
14
|
This plugin lets you define self-referential and double-sided polymorphic associations in your models. It is an extension of <tt>has_many :through</tt>.
|
@@ -26,7 +28,7 @@ The plugin also includes a generator for a tagging system, a common use case (se
|
|
26
28
|
|
27
29
|
== Requirements
|
28
30
|
|
29
|
-
* Rails 1.2.3 or
|
31
|
+
* Rails 1.2.3 or greater
|
30
32
|
|
31
33
|
= Usage
|
32
34
|
|
@@ -153,7 +155,20 @@ If you are having trouble, think very carefully about how your model classes, ke
|
|
153
155
|
|
154
156
|
Note that because of the way Rails reloads model classes, the plugin can sometimes bog down your development server. Set <tt>config.cache_classes = true</tt> in <tt>config/environments/development.rb</tt> to avoid this.
|
155
157
|
|
156
|
-
==
|
158
|
+
== Reporting problems
|
157
159
|
|
158
|
-
* http://blog.evanweaver.com/pages/code#polymorphs
|
159
160
|
* http://rubyforge.org/forum/forum.php?forum_id=16450
|
161
|
+
|
162
|
+
Patches and contributions are very welcome. Please note that contributors are required to assign copyright for their additions to Cloudburst, LLC.
|
163
|
+
|
164
|
+
== Further resources
|
165
|
+
|
166
|
+
* http://blog.evanweaver.com/articles/2007/08/15/polymorphs-tutorial
|
167
|
+
* http://blog.evanweaver.com/articles/2007/02/22/polymorphs-25-total-insanity-branch
|
168
|
+
* http://blog.evanweaver.com/articles/2007/02/09/how-to-find-the-most-popular-tags
|
169
|
+
* http://blog.evanweaver.com/articles/2007/01/13/growing-up-your-acts_as_taggable
|
170
|
+
* http://blog.evanweaver.com/articles/2006/12/02/polymorphs-19
|
171
|
+
* http://blog.evanweaver.com/articles/2006/11/05/directed-double-polymorphic-associations
|
172
|
+
* http://blog.evanweaver.com/articles/2006/11/04/namespaced-model-support-in-has_many_polymorphs
|
173
|
+
* http://blog.evanweaver.com/articles/2006/09/26/sti-support-in-has_many_polymorphs
|
174
|
+
* http://blog.evanweaver.com/articles/2006/09/11/make-polymorphic-children-belong-to-only-one-parent
|
data/TODO
CHANGED
@@ -23,7 +23,9 @@ class TaggingGenerator < Rails::Generator::NamedBase
|
|
23
23
|
def verify models
|
24
24
|
puts "** Warning: only one taggable model specified; tests may not run properly." if models.size < 2
|
25
25
|
models.each do |model|
|
26
|
-
|
26
|
+
model = model[1..-1].classify
|
27
|
+
next if model == "Tag" # don't load ourselves when --self-referential is used
|
28
|
+
self.class.const_get(model) rescue puts "** Error: model #{model[1..-1].classify} could not be loaded." or exit
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -81,4 +83,13 @@ class TaggingGenerator < Rails::Generator::NamedBase
|
|
81
83
|
opt.on("--self-referential",
|
82
84
|
"Allow tags to tag themselves.") { |v| options[:self_referential] = v }
|
83
85
|
end
|
86
|
+
|
87
|
+
# Useful for generating tests/fixtures
|
88
|
+
def model_one
|
89
|
+
taggable_models[0][1..-1].classify
|
90
|
+
end
|
91
|
+
|
92
|
+
def model_two
|
93
|
+
taggable_models[1][1..-1].classify rescue model_one
|
94
|
+
end
|
84
95
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../test_helper'
|
2
2
|
|
3
3
|
class TagTest < Test::Unit::TestCase
|
4
|
-
fixtures :tags, :taggings,
|
4
|
+
fixtures :tags, :taggings, <%= taggable_models[0..1].join(", ") -%>
|
5
5
|
|
6
6
|
def test_to_s
|
7
|
-
assert_equal "delicious sexy",
|
7
|
+
assert_equal "delicious sexy", <%= model_two -%>.find(2).tags.to_s
|
8
8
|
end
|
9
9
|
|
10
10
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../test_helper'
|
2
2
|
|
3
3
|
class TaggingTest < Test::Unit::TestCase
|
4
|
-
fixtures :taggings, :tags,
|
4
|
+
fixtures :taggings, :tags, <%= taggable_models[0..1].join(", ") -%>
|
5
5
|
|
6
6
|
def setup
|
7
|
-
@obj1 =
|
8
|
-
@obj2 =
|
9
|
-
|
7
|
+
@obj1 = <%= model_two %>.find(1)
|
8
|
+
@obj2 = <%= model_two %>.find(2)
|
9
|
+
<% if taggable_models.size > 1 -%>
|
10
|
+
@obj3 = <%= model_one -%>.find(1)
|
11
|
+
<% end -%>
|
10
12
|
@tag1 = Tag.find(1)
|
11
13
|
@tag2 = Tag.find(2)
|
12
14
|
@tagging1 = Tagging.find(1)
|
@@ -49,7 +51,9 @@ class TaggingTest < Test::Unit::TestCase
|
|
49
51
|
@tagging1.send(:taggable?, true)
|
50
52
|
end
|
51
53
|
assert !@tagging1.send(:taggable?)
|
54
|
+
<% if taggable_models.size > 1 -%>
|
52
55
|
assert @obj3.send(:taggable?)
|
56
|
+
<% end -%>
|
53
57
|
<% if options[:self_referential] -%>
|
54
58
|
assert @tag1.send(:taggable?)
|
55
59
|
<% end -%>
|
@@ -1,13 +1,15 @@
|
|
1
1
|
---
|
2
|
+
<% if taggable_models.size > 1 -%>
|
2
3
|
taggings_003:
|
3
4
|
<%= parent_association_name -%>_id: "2"
|
4
5
|
id: "3"
|
5
|
-
taggable_type: <%= model_one
|
6
|
-
taggable_id: "1"
|
6
|
+
taggable_type: <%= model_one %>
|
7
|
+
taggable_id: "1"
|
8
|
+
<% end -%>
|
7
9
|
taggings_004:
|
8
10
|
<%= parent_association_name -%>_id: "2"
|
9
11
|
id: "4"
|
10
|
-
taggable_type: <%= model_two
|
12
|
+
taggable_type: <%= model_two %>
|
11
13
|
taggable_id: "2"
|
12
14
|
taggings_001:
|
13
15
|
<%= parent_association_name -%>_id: "1"
|
data/has_many_polymorphs.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Has_many_polymorphs-2.
|
2
|
+
# Gem::Specification for Has_many_polymorphs-2.10
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = %q{has_many_polymorphs}
|
7
|
-
s.version = "2.
|
8
|
-
s.date = %q{2007-
|
7
|
+
s.version = "2.10"
|
8
|
+
s.date = %q{2007-09-16}
|
9
9
|
s.summary = %q{An ActiveRecord plugin for self-referential and double-sided polymorphic associations.}
|
10
10
|
s.email = %q{}
|
11
11
|
s.homepage = %q{http://blog.evanweaver.com/pages/code#polymorphs}
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{An ActiveRecord plugin for self-referential and double-sided polymorphic associations.}
|
14
14
|
s.has_rdoc = true
|
15
15
|
s.authors = [""]
|
16
|
-
s.files = ["test/unit/polymorph_test.rb", "test/test_helper.rb", "test/schema.rb", "test/modules/other_extension_module.rb", "test/modules/extension_module.rb", "test/models/wild_boar.rb", "test/models/tabby.rb", "test/models/petfood.rb", "test/models/kitten.rb", "test/models/frog.rb", "test/models/eaters_foodstuff.rb", "test/models/dog.rb", "test/models/cat.rb", "test/models/canine.rb", "test/models/beautiful_fight_relationship.rb", "test/models/aquatic/whale.rb", "test/models/aquatic/pupils_whale.rb", "test/models/aquatic/fish.rb", "test/fixtures/wild_boars.yml", "test/fixtures/petfoods.yml", "test/fixtures/keep_your_enemies_close.yml", "test/fixtures/frogs.yml", "test/fixtures/eaters_foodstuffs.yml", "test/fixtures/cats.yml", "test/fixtures/bow_wows.yml", "test/fixtures/aquatic/whales.yml", "test/fixtures/aquatic/little_whale_pupils.yml", "test/fixtures/aquatic/fish.yml", "lib/has_many_polymorphs.rb", "lib/has_many_polymorphs/support_methods.rb", "lib/has_many_polymorphs/reflection.rb", "lib/has_many_polymorphs/rake_task_redefine_task.rb", "lib/has_many_polymorphs/dependencies.rb", "lib/has_many_polymorphs/debugging_tools.rb", "lib/has_many_polymorphs/class_methods.rb", "lib/has_many_polymorphs/base.rb", "lib/has_many_polymorphs/autoload.rb", "lib/has_many_polymorphs/association.rb", "init.rb", "generators/tagging/templates/tags.yml", "generators/tagging/templates/taggings.yml", "generators/tagging/templates/tagging_test.rb", "generators/tagging/templates/tagging_extensions.rb", "generators/tagging/templates/tagging.rb", "generators/tagging/templates/tag_test.rb", "generators/tagging/templates/tag.rb", "generators/tagging/templates/migration.rb", "generators/tagging/tagging_generator.rb", "
|
16
|
+
s.files = ["TODO", "test/unit/polymorph_test.rb", "test/test_helper.rb", "test/schema.rb", "test/modules/other_extension_module.rb", "test/modules/extension_module.rb", "test/models/wild_boar.rb", "test/models/tabby.rb", "test/models/petfood.rb", "test/models/person.rb", "test/models/parentship.rb", "test/models/kitten.rb", "test/models/frog.rb", "test/models/eaters_foodstuff.rb", "test/models/dog.rb", "test/models/cat.rb", "test/models/canine.rb", "test/models/beautiful_fight_relationship.rb", "test/models/aquatic/whale.rb", "test/models/aquatic/pupils_whale.rb", "test/models/aquatic/fish.rb", "test/fixtures/wild_boars.yml", "test/fixtures/petfoods.yml", "test/fixtures/people.yml", "test/fixtures/keep_your_enemies_close.yml", "test/fixtures/frogs.yml", "test/fixtures/eaters_foodstuffs.yml", "test/fixtures/cats.yml", "test/fixtures/bow_wows.yml", "test/fixtures/aquatic/whales.yml", "test/fixtures/aquatic/little_whale_pupils.yml", "test/fixtures/aquatic/fish.yml", "README", "Manifest", "LICENSE", "lib/has_many_polymorphs.rb", "lib/has_many_polymorphs/support_methods.rb", "lib/has_many_polymorphs/reflection.rb", "lib/has_many_polymorphs/rake_task_redefine_task.rb", "lib/has_many_polymorphs/dependencies.rb", "lib/has_many_polymorphs/debugging_tools.rb", "lib/has_many_polymorphs/configuration.rb", "lib/has_many_polymorphs/class_methods.rb", "lib/has_many_polymorphs/base.rb", "lib/has_many_polymorphs/autoload.rb", "lib/has_many_polymorphs/association.rb", "init.rb", "generators/tagging/templates/tags.yml", "generators/tagging/templates/taggings.yml", "generators/tagging/templates/tagging_test.rb", "generators/tagging/templates/tagging_extensions.rb", "generators/tagging/templates/tagging.rb", "generators/tagging/templates/tag_test.rb", "generators/tagging/templates/tag.rb", "generators/tagging/templates/migration.rb", "generators/tagging/tagging_generator.rb", "CHANGELOG", "has_many_polymorphs.gemspec"]
|
17
17
|
s.test_files = ["test/test_helper.rb"]
|
18
18
|
s.add_dependency(%q<activerecord>, ["> 0.0.0"])
|
19
19
|
end
|
@@ -31,9 +31,10 @@ end
|
|
31
31
|
# p.project = "fauna"
|
32
32
|
# p.summary = "An ActiveRecord plugin for self-referential and double-sided polymorphic associations."
|
33
33
|
# p.url = "http://blog.evanweaver.com/pages/code#polymorphs"
|
34
|
-
# p.docs_host = "blog.evanweaver.com:~/www/
|
34
|
+
# p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
|
35
35
|
# p.dependencies = ["activerecord"]
|
36
|
-
# p.rdoc_pattern = /polymorphs\/association|polymorphs\/class_methods|polymorphs\/reflection|README|CHANGELOG|TODO|LICENSE|templates\/migration\.rb|templates\/tag\.rb|templates\/tagging\.rb|templates\/tagging_extensions\.rb/
|
36
|
+
# p.rdoc_pattern = /polymorphs\/association|polymorphs\/class_methods|polymorphs\/reflection|polymorphs\/autoload|polymorphs\/configuration|README|CHANGELOG|TODO|LICENSE|templates\/migration\.rb|templates\/tag\.rb|templates\/tagging\.rb|templates\/tagging_extensions\.rb/
|
37
|
+
# p.require_signed = true
|
37
38
|
# end
|
38
39
|
#
|
39
40
|
# desc 'Run the test suite.'
|
@@ -1,33 +1,62 @@
|
|
1
1
|
|
2
2
|
require 'initializer'
|
3
3
|
|
4
|
-
class Rails::Initializer
|
4
|
+
class Rails::Initializer #:nodoc:
|
5
5
|
|
6
|
-
=begin rdoc
|
6
|
+
=begin rdoc
|
7
7
|
Searches for models that use <tt>has_many_polymorphs</tt> or <tt>acts_as_double_polymorphic_join</tt> and makes sure that they get loaded during app initialization. This ensures that helper methods are injected into the target classes.
|
8
8
|
|
9
|
-
|
9
|
+
Note that you can override DEFAULT_OPTIONS via Rails::Configuration#has_many_polymorphs_options. For example, if you need an application extension to be required before has_many_polymorphs loads your models, add an <tt>after_initialize</tt> block in <tt>config/environment.rb</tt> that appends to the <tt>'requirements'</tt> key:
|
10
|
+
Rails::Initializer.run do |config|
|
11
|
+
# your other configuration here
|
12
|
+
|
13
|
+
config.after_initialize do
|
14
|
+
config.has_many_polymorphs_options['requirements'] << '/lib/my_extension'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
10
18
|
=end
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
module HasManyPolymorphsAutoload
|
21
|
+
|
22
|
+
DEFAULT_OPTIONS = {
|
23
|
+
:file_pattern => "#{RAILS_ROOT}/app/models/**/*.rb",
|
24
|
+
:file_exclusions => ['svn', 'CVS', 'bzr'],
|
25
|
+
:methods => ['has_many_polymorphs', 'acts_as_double_polymorphic_join'],
|
26
|
+
:requirements => []}
|
27
|
+
|
28
|
+
mattr_accessor :options
|
29
|
+
@@options = HashWithIndifferentAccess.new(DEFAULT_OPTIONS)
|
30
|
+
|
31
|
+
# Override for Rails::Initializer#after_initialize.
|
32
|
+
def after_initialize_with_autoload
|
33
|
+
after_initialize_without_autoload
|
34
|
+
|
35
|
+
_logger_debug "has_many_polymorphs: autoload hook invoked"
|
36
|
+
|
37
|
+
HasManyPolymorphsAutoload.options[:requirements].each do |requirement|
|
38
|
+
require requirement
|
39
|
+
end
|
40
|
+
|
41
|
+
Dir[HasManyPolymorphsAutoload.options[:file_pattern]].each do |filename|
|
42
|
+
next if filename =~ /#{HasManyPolymorphsAutoload.options[:file_exclusions].join("|")}/
|
43
|
+
open filename do |file|
|
44
|
+
if file.grep(/#{HasManyPolymorphsAutoload.options[:methods].join("|")}/).any?
|
45
|
+
begin
|
46
|
+
model = File.basename(filename)[0..-4].camelize
|
47
|
+
_logger_warn "has_many_polymorphs: preloading parent model #{model}"
|
48
|
+
model.constantize
|
49
|
+
rescue Object => e
|
50
|
+
_logger_warn "has_many_polymorphs: WARNING; possibly critical error preloading #{model}: #{e.inspect}"
|
51
|
+
end
|
26
52
|
end
|
27
53
|
end
|
28
54
|
end
|
29
|
-
end
|
30
|
-
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
31
58
|
|
59
|
+
include HasManyPolymorphsAutoload
|
60
|
+
|
32
61
|
alias_method_chain :after_initialize, :autoload
|
33
62
|
end
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
class << self
|
6
6
|
|
7
7
|
# Interprets a polymorphic row from a unified SELECT, returning the appropriate ActiveRecord instance. Overrides ActiveRecord::Base.instantiate_without_callbacks.
|
8
|
-
def
|
8
|
+
def instantiate_with_polymorphic_checks(record)
|
9
9
|
if record['polymorphic_parent_class']
|
10
10
|
reflection = record['polymorphic_parent_class'].constantize.reflect_on_association(record['polymorphic_association_id'].to_sym)
|
11
11
|
# _logger_debug "Instantiating a polymorphic row for #{record['polymorphic_parent_class']}.reflect_on_association(:#{record['polymorphic_association_id']})"
|
@@ -37,13 +37,23 @@ module ActiveRecord
|
|
37
37
|
# allocate and assign values
|
38
38
|
returning(klass.allocate) do |obj|
|
39
39
|
obj.instance_variable_set("@attributes", record)
|
40
|
+
obj.instance_variable_set("@attributes_cache", Hash.new)
|
41
|
+
|
42
|
+
if obj.respond_to_without_attributes?(:after_find)
|
43
|
+
obj.send(:callback, :after_find)
|
44
|
+
end
|
45
|
+
|
46
|
+
if obj.respond_to_without_attributes?(:after_initialize)
|
47
|
+
obj.send(:callback, :after_initialize)
|
48
|
+
end
|
49
|
+
|
40
50
|
end
|
41
51
|
else
|
42
|
-
|
52
|
+
instantiate_without_polymorphic_checks(record)
|
43
53
|
end
|
44
54
|
end
|
45
55
|
|
46
|
-
alias_method_chain :
|
56
|
+
alias_method_chain :instantiate, :polymorphic_checks
|
47
57
|
end
|
48
58
|
|
49
59
|
end
|
@@ -12,14 +12,17 @@ STI association targets must enumerated and named. For example, if Dog and Cat b
|
|
12
12
|
|
13
13
|
Namespaced models follow the Rails <tt>underscore</tt> convention. ZooAnimal::Lion becomes <tt>:'zoo_animal/lion'</tt>.
|
14
14
|
|
15
|
-
You do not need to set up any other associations other than for either the regular method or the double. The join associations and all individual and reverse associations are generated for you. However, a join
|
15
|
+
You do not need to set up any other associations other than for either the regular method or the double. The join associations and all individual and reverse associations are generated for you. However, a join model and table are required.
|
16
|
+
|
17
|
+
There is a tentative report that you can make the parent model be its own join model, but this is untested.
|
16
18
|
|
17
19
|
=end
|
18
20
|
|
19
21
|
module PolymorphicClassMethods
|
20
22
|
|
21
|
-
|
22
|
-
:join_extend, :dependent, :rename_individual_collections
|
23
|
+
RESERVED_DOUBLES_KEYS = [:conditions, :order, :limit, :offset, :extend, :skip_duplicates,
|
24
|
+
:join_extend, :dependent, :rename_individual_collections,
|
25
|
+
:namespace] #:nodoc:
|
23
26
|
|
24
27
|
=begin rdoc
|
25
28
|
|
@@ -52,41 +55,22 @@ These options are passed through to targets on both sides of the association. If
|
|
52
55
|
<tt>:order</tt>:: A string for the SQL <tt>ORDER BY</tt> clause.
|
53
56
|
<tt>:limit</tt>:: An integer. Affects the polymorphic and individual associations.
|
54
57
|
<tt>:offset</tt>:: An integer. Only affects the polymorphic association.
|
55
|
-
|
58
|
+
<tt>:namespace</tt>:: A symbol. Prepended to all the models in the <tt>:from</tt> and <tt>:through</tt> keys. This is especially useful for Camping, which namespaces models by default.
|
59
|
+
|
56
60
|
=end
|
57
61
|
|
58
|
-
def acts_as_double_polymorphic_join options={}, &extension
|
62
|
+
def acts_as_double_polymorphic_join options={}, &extension
|
59
63
|
|
60
|
-
collections
|
61
|
-
raise PolymorphicError, "Couldn't understand options in acts_as_double_polymorphic_join. Valid parameters are your two class collections, and then #{RESERVED_KEYS.inspect[1..-2]}, with optionally your collection names prepended and joined with an underscore." unless collections.size == 2
|
64
|
+
collections, options = extract_double_collections(options)
|
62
65
|
|
63
|
-
|
64
|
-
options[:extend] = (
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
|
71
|
-
options.each do |key, value|
|
72
|
-
next if collection_option_keys.values.flatten.include? key
|
73
|
-
# shift the general options to the individual sides
|
74
|
-
collection_value = options[collection_key = "#{collection}_#{key}".to_sym]
|
75
|
-
case key
|
76
|
-
when :conditions
|
77
|
-
collection_value, value = sanitize_sql(collection_value), sanitize_sql(value)
|
78
|
-
options[collection_key] = (collection_value ? "(#{collection_value}) AND (#{value})" : value)
|
79
|
-
when :order
|
80
|
-
options[collection_key] = (collection_value ? "#{collection_value}, #{value}" : value)
|
81
|
-
when :extend, :join_extend
|
82
|
-
options[collection_key] = Array(collection_value) + Array(value)
|
83
|
-
when :limit, :offset, :dependent, :rename_individual_collections
|
84
|
-
options[collection_key] ||= value
|
85
|
-
else
|
86
|
-
raise PolymorphicError, "Unknown option key #{key.inspect}."
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
66
|
+
# handle the block
|
67
|
+
options[:extend] = (if options[:extend]
|
68
|
+
Array(options[:extend]) + [extension]
|
69
|
+
else
|
70
|
+
extension
|
71
|
+
end) if extension
|
72
|
+
|
73
|
+
collection_option_keys = make_general_option_keys_specific!(options, collections)
|
90
74
|
|
91
75
|
join_name = self.name.tableize.to_sym
|
92
76
|
collections.each do |association_id, children|
|
@@ -97,7 +81,7 @@ These options are passed through to targets on both sides of the association. If
|
|
97
81
|
rescue NoMethodError
|
98
82
|
raise PolymorphicError, "Couldn't find 'belongs_to' association for :#{parent_hash_key._singularize} in #{self.name}." unless parent_foreign_key
|
99
83
|
end
|
100
|
-
|
84
|
+
|
101
85
|
parents = collections[parent_hash_key]
|
102
86
|
conflicts = (children & parents) # set intersection
|
103
87
|
parents.each do |plural_parent_name|
|
@@ -105,19 +89,31 @@ These options are passed through to targets on both sides of the association. If
|
|
105
89
|
parent_class = plural_parent_name._as_class
|
106
90
|
singular_reverse_association_id = parent_hash_key._singularize
|
107
91
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
92
|
+
internal_options = {
|
93
|
+
:is_double => true,
|
94
|
+
:from => children,
|
95
|
+
:as => singular_reverse_association_id,
|
96
|
+
:through => join_name.to_sym,
|
97
|
+
:foreign_key => parent_foreign_key,
|
98
|
+
:foreign_type_key => parent_foreign_key.to_s.sub(/_id$/, '_type'),
|
99
|
+
:singular_reverse_association_id => singular_reverse_association_id,
|
100
|
+
:conflicts => conflicts
|
101
|
+
}
|
102
|
+
|
103
|
+
general_options = Hash[*options._select do |key, value|
|
104
|
+
collection_option_keys[association_id].include? key and !value.nil?
|
105
|
+
end.map do |key, value|
|
106
|
+
[key.to_s[association_id.to_s.length+1..-1].to_sym, value]
|
107
|
+
end._flatten_once] # rename side-specific options to general names
|
108
|
+
|
109
|
+
general_options.each do |key, value|
|
110
|
+
# avoid clobbering keys that appear in both option sets
|
111
|
+
if internal_options[key]
|
112
|
+
general_options[key] = Array(value) + Array(internal_options[key])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
parent_class.send(:has_many_polymorphs, association_id, internal_options.merge(general_options))
|
121
117
|
|
122
118
|
if conflicts.include? plural_parent_name
|
123
119
|
# unify the alternate sides of the conflicting children
|
@@ -128,7 +124,7 @@ These options are passed through to targets on both sides of the association. If
|
|
128
124
|
self.send("#{association_id._singularize}_#{method_name}")).freeze
|
129
125
|
end
|
130
126
|
end
|
131
|
-
end
|
127
|
+
end
|
132
128
|
|
133
129
|
# unify the join model... join model is always renamed for doubles, unlike child associations
|
134
130
|
unless parent_class.instance_methods.include?(join_name)
|
@@ -146,6 +142,54 @@ These options are passed through to targets on both sides of the association. If
|
|
146
142
|
end
|
147
143
|
end
|
148
144
|
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def extract_double_collections(options)
|
149
|
+
collections = options._select do |key, value|
|
150
|
+
value.is_a? Array and key.to_s !~ /(#{RESERVED_DOUBLES_KEYS.map(&:to_s).join('|')})$/
|
151
|
+
end
|
152
|
+
|
153
|
+
raise PolymorphicError, "Couldn't understand options in acts_as_double_polymorphic_join. Valid parameters are your two class collections, and then #{RESERVED_DOUBLES_KEYS.inspect[1..-2]}, with optionally your collection names prepended and joined with an underscore." unless collections.size == 2
|
154
|
+
|
155
|
+
options = options._select do |key, value|
|
156
|
+
!collections[key]
|
157
|
+
end
|
158
|
+
|
159
|
+
[collections, options]
|
160
|
+
end
|
161
|
+
|
162
|
+
def make_general_option_keys_specific!(options, collections)
|
163
|
+
collection_option_keys = Hash[*collections.keys.map do |key|
|
164
|
+
[key, RESERVED_DOUBLES_KEYS.map{|option| "#{key}_#{option}".to_sym}]
|
165
|
+
end._flatten_once]
|
166
|
+
|
167
|
+
collections.keys.each do |collection|
|
168
|
+
options.each do |key, value|
|
169
|
+
next if collection_option_keys.values.flatten.include? key
|
170
|
+
# shift the general options to the individual sides
|
171
|
+
collection_key = "#{collection}_#{key}".to_sym
|
172
|
+
collection_value = options[collection_key]
|
173
|
+
case key
|
174
|
+
when :conditions
|
175
|
+
collection_value, value = sanitize_sql(collection_value), sanitize_sql(value)
|
176
|
+
options[collection_key] = (collection_value ? "(#{collection_value}) AND (#{value})" : value)
|
177
|
+
when :order
|
178
|
+
options[collection_key] = (collection_value ? "#{collection_value}, #{value}" : value)
|
179
|
+
when :extend, :join_extend
|
180
|
+
options[collection_key] = Array(collection_value) + Array(value)
|
181
|
+
else
|
182
|
+
options[collection_key] ||= value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
collection_option_keys
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
public
|
149
193
|
|
150
194
|
=begin rdoc
|
151
195
|
|
@@ -157,32 +201,41 @@ This method createds a single-sided polymorphic relationship.
|
|
157
201
|
|
158
202
|
The only required parameter, aside from the association name, is <tt>:from</tt>.
|
159
203
|
|
160
|
-
The method generates a number of associations aside from the polymorphic one. In this example Petfood also gets <tt>dogs</tt>, <tt>cats</tt>, and <tt>birds</tt>, and Dog, Cat, and Bird get <tt>petfoods</tt>. (The reverse association to the
|
204
|
+
The method generates a number of associations aside from the polymorphic one. In this example Petfood also gets <tt>dogs</tt>, <tt>cats</tt>, and <tt>birds</tt>, and Dog, Cat, and Bird get <tt>petfoods</tt>. (The reverse association to the parents is always plural.)
|
161
205
|
|
162
206
|
== Available options
|
163
207
|
|
164
|
-
<tt>:from</tt>:: An array of symbols representing the target
|
208
|
+
<tt>:from</tt>:: An array of symbols representing the target models. Required.
|
165
209
|
<tt>:as</tt>:: A symbol for the parent's interface in the join--what the parent 'acts as'.
|
166
210
|
<tt>:through</tt>:: A symbol representing the class of the join model. Follows Rails defaults if not supplied (the parent and the association names, alphabetized, concatenated with an underscore, and singularized).
|
167
211
|
<tt>:foreign_key</tt>:: The column name for the parent's id in the join.
|
168
|
-
<tt>:foreign_type_key</tt>:: The column name for the parent's class in the join, if the parent itself is polymorphic. Rarely needed.
|
212
|
+
<tt>:foreign_type_key</tt>:: The column name for the parent's class name in the join, if the parent itself is polymorphic. Rarely needed.
|
169
213
|
<tt>:polymorphic_key</tt>:: The column name for the child's id in the join.
|
170
|
-
<tt>:polymorphic_type_key</tt>:: The column name for the child's class in the join.
|
214
|
+
<tt>:polymorphic_type_key</tt>:: The column name for the child's class name in the join.
|
171
215
|
<tt>:dependent</tt>:: Accepts <tt>:destroy</tt>, <tt>:nullify</tt>, <tt>:delete_all</tt>. Controls how the join record gets treated on any associate delete (whether from the polymorph or from an individual collection); defaults to <tt>:destroy</tt>.
|
172
216
|
<tt>:skip_duplicates</tt>:: If <tt>true</tt>, will check to avoid pushing already associated records (but also triggering a database load). Defaults to <tt>true</tt>.
|
173
217
|
<tt>:rename_individual_collections</tt>:: If <tt>true</tt>, all individual collections are prepended with the polymorph name, and the children's parent collection is appended with "_of_#{association_name}"</tt>. For example, <tt>zoos</tt> becomes <tt>zoos_of_animals</tt>. This is to help avoid method name collisions in crowded classes.
|
174
218
|
<tt>:extend</tt>:: One or an array of mixed modules and procs, which are applied to the polymorphic association (usually to define custom methods).
|
175
219
|
<tt>:join_extend</tt>:: One or an array of mixed modules and procs, which are applied to the join association.
|
176
|
-
<tt>:parent_extend</tt>:: One or an array of mixed modules and procs, which are applied to the target
|
220
|
+
<tt>:parent_extend</tt>:: One or an array of mixed modules and procs, which are applied to the target models' association to the parents.
|
177
221
|
<tt>:conditions</tt>:: An array or string of conditions for the SQL <tt>WHERE</tt> clause.
|
222
|
+
<tt>:parent_conditions</tt>:: An array or string of conditions which are applied to the target models' association to the parents.
|
178
223
|
<tt>:order</tt>:: A string for the SQL <tt>ORDER BY</tt> clause.
|
224
|
+
<tt>:parent_order</tt>:: A string for the SQL <tt>ORDER BY</tt> which is applied to the target models' association to the parents.
|
179
225
|
<tt>:group</tt>:: An array or string of conditions for the SQL <tt>GROUP BY</tt> clause. Affects the polymorphic and individual associations.
|
180
226
|
<tt>:limit</tt>:: An integer. Affects the polymorphic and individual associations.
|
181
227
|
<tt>:offset</tt>:: An integer. Only affects the polymorphic association.
|
228
|
+
<tt>:namespace</tt>:: A symbol. Prepended to all the models in the <tt>:from</tt> and <tt>:through</tt> keys. This is especially useful for Camping, which namespaces models by default.
|
182
229
|
<tt>:uniq</tt>:: If <tt>true</tt>, the records returned are passed through a pure-Ruby <tt>uniq</tt> before they are returned. Rarely needed.
|
183
230
|
|
184
231
|
If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
185
232
|
|
233
|
+
== On condition nullification
|
234
|
+
|
235
|
+
When you request an individual association, non-applicable but fully-qualified fields in the polymorphic association's <tt>:conditions</tt>, <tt>:order</tt>, and <tt>:group</tt> options get changed to <tt>NULL</tt>. For example, if you set <tt>:conditions => "dogs.name != 'Spot'"</tt>, when you request <tt>.cats</tt>, the conditions string is changed to <tt>NULL != 'Spot'</tt>.
|
236
|
+
|
237
|
+
Be aware, however, that <tt>NULL != 'Spot'</tt> returns <tt>false</tt> due to SQL's 3-value logic. Instead, you need to use the <tt>:conditions</tt> string <tt>"dogs.name IS NULL OR dogs.name != 'Spot'"</tt> to get the behavior you probably expect for negative matches.
|
238
|
+
|
186
239
|
=end
|
187
240
|
|
188
241
|
def has_many_polymorphs (association_id, options = {}, &extension)
|
@@ -219,6 +272,8 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
219
272
|
:select, # applies to the polymorphic relationship
|
220
273
|
:conditions, # applies to the polymorphic relationship, the children, and the join
|
221
274
|
# :include,
|
275
|
+
:parent_conditions,
|
276
|
+
:parent_order,
|
222
277
|
:order, # applies to the polymorphic relationship, the children, and the join
|
223
278
|
:group, # only applies to the polymorphic relationship and the children
|
224
279
|
:limit, # only applies to the polymorphic relationship and the children
|
@@ -228,6 +283,7 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
228
283
|
:parent_limit,
|
229
284
|
:parent_offset,
|
230
285
|
# :source,
|
286
|
+
:namespace,
|
231
287
|
:uniq, # XXX untested, only applies to the polymorphic relationship
|
232
288
|
# :finder_sql,
|
233
289
|
# :counter_sql,
|
@@ -242,13 +298,13 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
242
298
|
raise PolymorphicError, ":from option must be an array" unless options[:from].is_a? Array
|
243
299
|
options[:from].each{|plural| verify_pluralization_of(plural)}
|
244
300
|
|
245
|
-
options[:as] ||= self.name.demodulize.
|
301
|
+
options[:as] ||= self.name.demodulize.underscore.to_sym
|
246
302
|
options[:conflicts] = Array(options[:conflicts])
|
247
303
|
options[:foreign_key] ||= "#{options[:as]}_id"
|
248
304
|
|
249
305
|
options[:association_foreign_key] =
|
250
306
|
options[:polymorphic_key] ||= "#{association_id._singularize}_id"
|
251
|
-
options[:polymorphic_type_key] ||= "#{association_id._singularize}_type"
|
307
|
+
options[:polymorphic_type_key] ||= "#{association_id._singularize}_type"
|
252
308
|
|
253
309
|
if options.has_key? :ignore_duplicates
|
254
310
|
_logger_warn "DEPRECATION WARNING: please use :skip_duplicates instead of :ignore_duplicates"
|
@@ -258,9 +314,20 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
258
314
|
options[:dependent] = :destroy unless options.has_key? :dependent
|
259
315
|
options[:conditions] = sanitize_sql(options[:conditions])
|
260
316
|
|
261
|
-
|
262
|
-
|
263
|
-
options[:through] ||= build_join_table_symbol((options[:as]._pluralize or self.table_name)
|
317
|
+
# options[:finder_sql] ||= "(options[:polymorphic_key]
|
318
|
+
|
319
|
+
options[:through] ||= build_join_table_symbol(association_id, (options[:as]._pluralize or self.table_name))
|
320
|
+
|
321
|
+
# set up namespaces if we have a namespace key
|
322
|
+
# XXX needs test coverage
|
323
|
+
if options[:namespace]
|
324
|
+
namespace = options[:namespace].to_s.chomp("/") + "/"
|
325
|
+
options[:from].map! do |child|
|
326
|
+
"#{namespace}#{child}".to_sym
|
327
|
+
end
|
328
|
+
options[:through] = "#{namespace}#{options[:through]}".to_sym
|
329
|
+
end
|
330
|
+
|
264
331
|
options[:join_class_name] ||= options[:through]._classify
|
265
332
|
options[:table_aliases] ||= build_table_aliases([options[:through]] + options[:from])
|
266
333
|
options[:select] ||= build_select(association_id, options[:table_aliases])
|
@@ -274,7 +341,7 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
274
341
|
|
275
342
|
# create the reflection object
|
276
343
|
returning(create_reflection(:has_many_polymorphs, association_id, options, self)) do |reflection|
|
277
|
-
if defined? Dependencies and RAILS_ENV == "development"
|
344
|
+
if defined? Dependencies and defined? RAILS_ENV and RAILS_ENV == "development"
|
278
345
|
inject_dependencies(association_id, reflection) if Dependencies.mechanism == :load
|
279
346
|
end
|
280
347
|
|
@@ -294,7 +361,11 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
294
361
|
# for the targets
|
295
362
|
returning({}) do |aliases|
|
296
363
|
from.map(&:to_s).sort.map(&:to_sym).each_with_index do |plural, t_index|
|
297
|
-
|
364
|
+
begin
|
365
|
+
table = plural._as_class.table_name
|
366
|
+
rescue NameError => e
|
367
|
+
raise PolymorphicError, "Could not find a valid class for #{plural.inspect}. If it's namespaced, be sure to specify it as :\"module/#{plural}\" instead."
|
368
|
+
end
|
298
369
|
plural._as_class.columns.map(&:name).each_with_index do |field, f_index|
|
299
370
|
aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}"
|
300
371
|
end
|
@@ -324,29 +395,32 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
324
395
|
|
325
396
|
def create_join_association(association_id, reflection)
|
326
397
|
|
327
|
-
options = {
|
398
|
+
options = {
|
399
|
+
:foreign_key => reflection.options[:foreign_key],
|
328
400
|
:dependent => reflection.options[:dependent],
|
329
401
|
:class_name => reflection.klass.name,
|
330
|
-
:extend => reflection.options[:join_extend]
|
331
|
-
|
332
|
-
|
333
|
-
:order => devolve(association_id, reflection, reflection.options[:order], reflection.klass),
|
334
|
-
:conditions => devolve(association_id, reflection, reflection.options[:conditions], reflection.klass)
|
335
|
-
}
|
402
|
+
:extend => reflection.options[:join_extend]
|
403
|
+
# :limit => reflection.options[:limit],
|
404
|
+
# :offset => reflection.options[:offset],
|
405
|
+
# :order => devolve(association_id, reflection, reflection.options[:order], reflection.klass, true),
|
406
|
+
# :conditions => devolve(association_id, reflection, reflection.options[:conditions], reflection.klass, true)
|
407
|
+
}
|
336
408
|
|
337
409
|
if reflection.options[:foreign_type_key]
|
338
410
|
type_check = "#{reflection.options[:foreign_type_key]} = #{quote_value(self.base_class.name)}"
|
339
411
|
conjunction = options[:conditions] ? " AND " : nil
|
340
412
|
options[:conditions] = "#{options[:conditions]}#{conjunction}#{type_check}"
|
413
|
+
options[:as] = reflection.options[:as]
|
341
414
|
end
|
342
|
-
|
343
|
-
has_many(reflection.options[:through], options)
|
415
|
+
|
416
|
+
has_many(reflection.options[:through], options)
|
417
|
+
|
344
418
|
inject_before_save_into_join_table(association_id, reflection)
|
345
419
|
end
|
346
420
|
|
347
421
|
def inject_before_save_into_join_table(association_id, reflection)
|
348
422
|
sti_hook = "sti_class_rewrite"
|
349
|
-
rewrite_procedure = %[self.send(:#{
|
423
|
+
rewrite_procedure = %[self.send(:#{reflection.options[:polymorphic_type_key]}=, self.#{reflection.options[:polymorphic_type_key]}.constantize.base_class.name)]
|
350
424
|
|
351
425
|
# XXX should be abstracted?
|
352
426
|
reflection.klass.class_eval %[
|
@@ -387,27 +461,34 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
387
461
|
through = "#{reflection.options[:through]}#{'_as_child' if parent == self}".to_sym
|
388
462
|
has_many(through,
|
389
463
|
:as => association_id._singularize,
|
464
|
+
# :source => association_id._singularize,
|
465
|
+
# :source_type => reflection.options[:polymorphic_type_key],
|
390
466
|
:class_name => reflection.klass.name,
|
391
467
|
:dependent => reflection.options[:dependent],
|
392
468
|
:extend => reflection.options[:join_extend],
|
393
469
|
# :limit => reflection.options[:limit],
|
394
470
|
# :offset => reflection.options[:offset],
|
395
|
-
:order => devolve(association_id, reflection, reflection.options[:
|
396
|
-
:conditions => devolve(association_id, reflection, reflection.options[:
|
471
|
+
:order => devolve(association_id, reflection, reflection.options[:parent_order], reflection.klass),
|
472
|
+
:conditions => devolve(association_id, reflection, reflection.options[:parent_conditions], reflection.klass)
|
397
473
|
)
|
398
474
|
|
399
|
-
# the association to the
|
475
|
+
# the association to the target's parents
|
400
476
|
association = "#{reflection.options[:as]._pluralize}#{"_of_#{association_id}" if reflection.options[:rename_individual_collections]}".to_sym
|
401
477
|
has_many(association,
|
402
478
|
:through => through,
|
403
479
|
:class_name => parent.name,
|
404
480
|
:source => reflection.options[:as],
|
405
|
-
:foreign_key => reflection.options[:foreign_key]
|
481
|
+
:foreign_key => reflection.options[:foreign_key],
|
406
482
|
:extend => reflection.options[:parent_extend],
|
483
|
+
:conditions => reflection.options[:parent_conditions],
|
407
484
|
:order => reflection.options[:parent_order],
|
408
485
|
:offset => reflection.options[:parent_offset],
|
409
486
|
:limit => reflection.options[:parent_limit],
|
410
487
|
:group => reflection.options[:parent_group])
|
488
|
+
|
489
|
+
# debugger if association == :parents
|
490
|
+
#
|
491
|
+
# nil
|
411
492
|
|
412
493
|
end
|
413
494
|
end
|
@@ -452,8 +533,8 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
452
533
|
end
|
453
534
|
end
|
454
535
|
|
455
|
-
# some support methods
|
456
|
-
|
536
|
+
# some support methods
|
537
|
+
|
457
538
|
def child_pluralization_map(association_id, reflection)
|
458
539
|
Hash[*reflection.options[:from].map do |plural|
|
459
540
|
[plural, plural._singularize]
|
@@ -470,8 +551,8 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
470
551
|
s.to_s.gsub('/', '_').to_sym
|
471
552
|
end
|
472
553
|
|
473
|
-
def build_join_table_symbol(
|
474
|
-
[
|
554
|
+
def build_join_table_symbol(association_id, name)
|
555
|
+
[name.to_s, association_id.to_s].sort.join("_").to_sym
|
475
556
|
end
|
476
557
|
|
477
558
|
def all_classes_for(association_id, reflection)
|
@@ -480,18 +561,25 @@ If you pass a block, it gets converted to a Proc and added to <tt>:extend</tt>.
|
|
480
561
|
klasses.uniq
|
481
562
|
end
|
482
563
|
|
483
|
-
def devolve(association_id, reflection, string, klass)
|
564
|
+
def devolve(association_id, reflection, string, klass, remove_inappropriate_clauses = false)
|
565
|
+
# XXX remove_inappropriate_clauses is not implemented; we'll wait until someone actually needs it
|
484
566
|
return unless string
|
485
|
-
|
486
|
-
|
567
|
+
string = string.dup
|
568
|
+
# _logger_debug "has_many_polymorphs: devolving #{string} for #{klass}"
|
569
|
+
inappropriate_classes = (all_classes_for(association_id, reflection) - # the join class must always be preserved
|
570
|
+
[klass, klass.base_class, reflection.klass, reflection.klass.base_class])
|
571
|
+
inappropriate_classes.map do |klass|
|
487
572
|
klass.columns.map do |column|
|
488
573
|
[klass.table_name, column.name]
|
489
574
|
end.map do |table, column|
|
490
575
|
["#{table}.#{column}", "`#{table}`.#{column}", "#{table}.`#{column}`", "`#{table}`.`#{column}`"]
|
491
576
|
end
|
492
577
|
end.flatten.sort_by(&:size).reverse.each do |quoted_reference|
|
578
|
+
# _logger_debug "devolved #{quoted_reference} to NULL"
|
579
|
+
# XXX clause removal would go here
|
493
580
|
string.gsub!(quoted_reference, "NULL")
|
494
581
|
end
|
582
|
+
# _logger_debug "has_many_polymorphs: altered to #{string}"
|
495
583
|
string
|
496
584
|
end
|
497
585
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
=begin rdoc
|
3
|
+
Access the <tt>has_many_polymorphs_options</tt> hash in your Rails::Initializer.run#after_initialize block if you need to modify the behavior of Rails::Initializer::HasManyPolymorphsAutoload.
|
4
|
+
=end
|
5
|
+
|
6
|
+
class Rails::Configuration
|
7
|
+
|
8
|
+
def has_many_polymorphs_options
|
9
|
+
Rails::Initializer::HasManyPolymorphsAutoload.options
|
10
|
+
end
|
11
|
+
|
12
|
+
def has_many_polymorphs_options=(hash)
|
13
|
+
Rails::Initializer::HasManyPolymorphsAutoload.options = hash
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
data/lib/has_many_polymorphs.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
|
2
2
|
require 'active_record'
|
3
3
|
|
4
|
+
RAILS_DEFAULT_LOGGER = nil unless defined? RAILS_DEFAULT_LOGGER
|
5
|
+
|
4
6
|
require 'has_many_polymorphs/reflection'
|
5
7
|
require 'has_many_polymorphs/association'
|
6
8
|
require 'has_many_polymorphs/class_methods'
|
@@ -19,6 +21,7 @@ end
|
|
19
21
|
|
20
22
|
if defined? Rails and RAILS_ENV and RAILS_ROOT
|
21
23
|
_logger_warn "has_many_polymorphs: Rails environment detected"
|
24
|
+
require 'has_many_polymorphs/configuration'
|
22
25
|
require 'has_many_polymorphs/dependencies'
|
23
26
|
require 'has_many_polymorphs/autoload'
|
24
27
|
end
|
data/test/models/petfood.rb
CHANGED
@@ -23,7 +23,8 @@ class Petfood < ActiveRecord::Base
|
|
23
23
|
:ignore_duplicates => false,
|
24
24
|
:conditions => "NULL IS NULL",
|
25
25
|
:order => "eaters_foodstuffs.updated_at ASC",
|
26
|
-
:parent_order => "the_petfood_primary_key DESC",
|
26
|
+
:parent_order => "petfoods.the_petfood_primary_key DESC",
|
27
|
+
:parent_conditions => "petfoods.name IS NULL OR petfoods.name != 'Snausages'",
|
27
28
|
:extend => [ExtensionModule, OtherExtensionModule, proc {}],
|
28
29
|
:join_extend => proc {
|
29
30
|
def a_method
|
data/test/schema.rb
CHANGED
@@ -67,6 +67,21 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
67
67
|
t.column :protector_type, :string
|
68
68
|
t.column :created_at, :datetime, :null => false
|
69
69
|
t.column :updated_at, :datetime, :null => false
|
70
|
+
end
|
71
|
+
|
72
|
+
create_table :parentships, :force => true do |t|
|
73
|
+
t.column :parent_id, :integer
|
74
|
+
t.column :child_type, :string
|
75
|
+
t.column :kid_id, :integer
|
76
|
+
t.column :created_at, :datetime, :null => false
|
77
|
+
t.column :updated_at, :datetime, :null => false
|
78
|
+
end
|
79
|
+
|
80
|
+
create_table :people, :force => true do |t|
|
81
|
+
t.column :name, :string
|
82
|
+
t.column :age, :integer
|
83
|
+
t.column :created_at, :datetime, :null => false
|
84
|
+
t.column :updated_at, :datetime, :null => false
|
70
85
|
end
|
71
86
|
|
72
87
|
end
|
data/test/unit/polymorph_test.rb
CHANGED
@@ -4,7 +4,7 @@ class PolymorphTest < Test::Unit::TestCase
|
|
4
4
|
|
5
5
|
fixtures :cats, :bow_wows, :frogs, :wild_boars, :eaters_foodstuffs, :petfoods,
|
6
6
|
:"aquatic/fish", :"aquatic/whales", :"aquatic/little_whale_pupils",
|
7
|
-
:keep_your_enemies_close
|
7
|
+
:keep_your_enemies_close, :people
|
8
8
|
require 'beautiful_fight_relationship'
|
9
9
|
|
10
10
|
def setup
|
@@ -24,6 +24,9 @@ class PolymorphTest < Test::Unit::TestCase
|
|
24
24
|
@join_count = EatersFoodstuff.count
|
25
25
|
@l = @kibbles.eaters.size
|
26
26
|
@m = @bits.eaters.size
|
27
|
+
|
28
|
+
@double_join_count = BeautifulFightRelationship.count
|
29
|
+
@n = @alice.enemies.size
|
27
30
|
end
|
28
31
|
|
29
32
|
def d
|
@@ -40,14 +43,24 @@ class PolymorphTest < Test::Unit::TestCase
|
|
40
43
|
Tabby.reflect_on_all_associations.map &:check_validity!
|
41
44
|
Kitten.reflect_on_all_associations.map &:check_validity!
|
42
45
|
Dog.reflect_on_all_associations.map &:check_validity!
|
46
|
+
Canine.reflect_on_all_associations.map &:check_validity!
|
43
47
|
Aquatic::Fish.reflect_on_all_associations.map &:check_validity!
|
44
48
|
EatersFoodstuff.reflect_on_all_associations.map &:check_validity!
|
45
49
|
WildBoar.reflect_on_all_associations.map &:check_validity!
|
46
50
|
Frog.reflect_on_all_associations.map &:check_validity!
|
47
|
-
Aquatic::Whale.reflect_on_all_associations.map &:check_validity!
|
48
51
|
Cat.reflect_on_all_associations.map &:check_validity!
|
49
|
-
|
52
|
+
Right.reflect_on_all_associations.map &:check_validity!
|
53
|
+
Left.reflect_on_all_associations.map &:check_validity!
|
54
|
+
DoubleJoin.reflect_on_all_associations.map &:check_validity!
|
50
55
|
BeautifulFightRelationship.reflect_on_all_associations.map &:check_validity!
|
56
|
+
Person.reflect_on_all_associations.map &:check_validity!
|
57
|
+
Parentship.reflect_on_all_associations.map &:check_validity!
|
58
|
+
Aquatic::Whale.reflect_on_all_associations.map &:check_validity!
|
59
|
+
Aquatic::PupilsWhale.reflect_on_all_associations.map &:check_validity!
|
60
|
+
Tagging.reflect_on_all_associations.map &:check_validity!
|
61
|
+
Recipe.reflect_on_all_associations.map &:check_validity!
|
62
|
+
Category.reflect_on_all_associations.map &:check_validity!
|
63
|
+
Tag.reflect_on_all_associations.map &:check_validity!
|
51
64
|
end
|
52
65
|
|
53
66
|
def test_assignment
|
@@ -90,12 +103,11 @@ class PolymorphTest < Test::Unit::TestCase
|
|
90
103
|
|
91
104
|
def test_add_join_record
|
92
105
|
assert_equal Kitten, @chloe.class
|
93
|
-
assert
|
94
|
-
assert
|
95
|
-
assert
|
106
|
+
assert join = EatersFoodstuff.new(:foodstuff_id => @bits.id, :eater_id => @chloe.id, :eater_type => @chloe.class.name )
|
107
|
+
assert join.save!
|
108
|
+
assert join.id
|
96
109
|
assert_equal @join_count + 1, EatersFoodstuff.count
|
97
110
|
|
98
|
-
# not reloaded
|
99
111
|
#assert_equal @m, @bits.eaters.size # Doesn't behave this way on latest edge anymore
|
100
112
|
assert_equal @m + 1, @bits.eaters.count # SQL
|
101
113
|
|
@@ -104,6 +116,18 @@ class PolymorphTest < Test::Unit::TestCase
|
|
104
116
|
assert @bits.eaters.include?(@chloe)
|
105
117
|
end
|
106
118
|
|
119
|
+
def test_build_join_record_on_association
|
120
|
+
assert_equal Kitten, @chloe.class
|
121
|
+
assert join = @chloe.eaters_foodstuffs.build(:foodstuff => @bits)
|
122
|
+
# assert_equal join.eater_type, @chloe.class.name # will be STI parent type
|
123
|
+
assert join.save!
|
124
|
+
assert join.id
|
125
|
+
assert_equal @join_count + 1, EatersFoodstuff.count
|
126
|
+
|
127
|
+
assert @bits.eaters.reload
|
128
|
+
assert @bits.eaters.include?(@chloe)
|
129
|
+
end
|
130
|
+
|
107
131
|
# not supporting this, since has_many :through doesn't support it either
|
108
132
|
# def test_add_unsaved
|
109
133
|
# # add an unsaved item
|
@@ -258,8 +282,9 @@ class PolymorphTest < Test::Unit::TestCase
|
|
258
282
|
end
|
259
283
|
|
260
284
|
def test_attributes_come_through_when_child_has_underscore_in_table_name
|
261
|
-
|
262
|
-
|
285
|
+
join = EatersFoodstuff.new(:foodstuff_id => @bits.id, :eater_id => @puma.id, :eater_type => @puma.class.name)
|
286
|
+
join.save!
|
287
|
+
|
263
288
|
@bits.eaters.reload
|
264
289
|
|
265
290
|
assert_equal "Puma", @puma.name
|
@@ -349,6 +374,20 @@ class PolymorphTest < Test::Unit::TestCase
|
|
349
374
|
assert_equal 3, @alice.beautiful_fight_relationships.size
|
350
375
|
end
|
351
376
|
|
377
|
+
def test_double_collection_build_join_record_on_association
|
378
|
+
|
379
|
+
join = @alice.beautiful_fight_relationships_as_protector.build(:enemy => @spot)
|
380
|
+
|
381
|
+
assert_equal @alice.class.base_class.name, join.protector_type
|
382
|
+
assert_nothing_raised { join.save! }
|
383
|
+
|
384
|
+
assert join.id
|
385
|
+
assert_equal @double_join_count + 1, BeautifulFightRelationship.count
|
386
|
+
|
387
|
+
assert @alice.enemies.reload
|
388
|
+
assert @alice.enemies.include?(@spot)
|
389
|
+
end
|
390
|
+
|
352
391
|
def test_double_dependency_injection
|
353
392
|
# breakpoint
|
354
393
|
end
|
@@ -521,9 +560,16 @@ class PolymorphTest < Test::Unit::TestCase
|
|
521
560
|
end" }
|
522
561
|
assert_raises(@association_error) {
|
523
562
|
eval "class SomeModel < ActiveRecord::Base
|
524
|
-
|
563
|
+
acts_as_double_polymorphic_join :polymorph => [:dogs, :cats], :unimorphs => [:dogs, :cats]
|
525
564
|
end" }
|
526
565
|
end
|
566
|
+
|
567
|
+
def test_error_message_on_namespaced_targets
|
568
|
+
assert_raises(@association_error) {
|
569
|
+
eval "class SomeModel < ActiveRecord::Base
|
570
|
+
has_many_polymorphs :polymorphs, :from => [:fish]
|
571
|
+
end" }
|
572
|
+
end
|
527
573
|
|
528
574
|
def test_single_custom_finders
|
529
575
|
[@kibbles, @alice, @puma, @spot, @bits].each {|record| @kibbles.eaters << record; sleep 1} # XXX yeah i know
|
@@ -588,12 +634,38 @@ class PolymorphTest < Test::Unit::TestCase
|
|
588
634
|
assert_equal EatersFoodstuff.name, Petfood.reflect_on_association(:eaters).class_name
|
589
635
|
end
|
590
636
|
|
591
|
-
def
|
637
|
+
def test_parent_order
|
592
638
|
@alice.foodstuffs_of_eaters << Petfood.find(:all, :order => "the_petfood_primary_key ASC")
|
593
639
|
@alice.reload #not necessary
|
594
640
|
assert_equal [2,1], @alice.foodstuffs_of_eaters.map(&:id)
|
595
641
|
end
|
596
642
|
|
643
|
+
def test_parent_conditions
|
644
|
+
@kibbles.eaters << @alice
|
645
|
+
assert_equal [@alice], @kibbles.eaters
|
646
|
+
|
647
|
+
@snausages = Petfood.create(:name => 'Snausages')
|
648
|
+
@snausages.eaters << @alice
|
649
|
+
assert_equal [@alice], @snausages.eaters
|
650
|
+
|
651
|
+
assert_equal [@kibbles], @alice.foodstuffs_of_eaters
|
652
|
+
end
|
653
|
+
|
654
|
+
def test_self_referential_hmp_with_conditions
|
655
|
+
p = Person.find(:first)
|
656
|
+
kid = Person.create(:name => "Tim", :age => 3)
|
657
|
+
p.kids << kid
|
658
|
+
|
659
|
+
kid.reload; p.reload
|
660
|
+
|
661
|
+
# assert_equal [p], kid.parents
|
662
|
+
# assert Rails.has_one? Bug
|
663
|
+
# non-standard foreign_type key is not set properly when you are the polymorphic interface of a has_many going to a :through
|
664
|
+
|
665
|
+
assert_equal [kid], p.kids
|
666
|
+
assert_equal [kid], p.people
|
667
|
+
end
|
668
|
+
|
597
669
|
# def test_polymorphic_include
|
598
670
|
# @kibbles.eaters << [@kibbles, @alice, @puma, @spot, @bits]
|
599
671
|
# assert @kibbles.eaters.include?(@kibbles.eaters_foodstuffs.find(:all, :include => :eater).first.eater)
|
data.tar.gz.sig
ADDED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: has_many_polymorphs
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: "2.
|
7
|
-
date: 2007-
|
6
|
+
version: "2.10"
|
7
|
+
date: 2007-09-16 00:00:00 -04:00
|
8
8
|
summary: An ActiveRecord plugin for self-referential and double-sided polymorphic associations.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -25,10 +25,33 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
|
|
25
25
|
platform: ruby
|
26
26
|
signing_key:
|
27
27
|
cert_chain:
|
28
|
+
- |
|
29
|
+
-----BEGIN CERTIFICATE-----
|
30
|
+
MIIDLjCCAhagAwIBAgIBADANBgkqhkiG9w0BAQUFADA9MQ0wCwYDVQQDDARldmFu
|
31
|
+
MRgwFgYKCZImiZPyLGQBGRYIY2xvdWRidXIxEjAQBgoJkiaJk/IsZAEZFgJzdDAe
|
32
|
+
Fw0wNzA5MTYxMDMzMDBaFw0wODA5MTUxMDMzMDBaMD0xDTALBgNVBAMMBGV2YW4x
|
33
|
+
GDAWBgoJkiaJk/IsZAEZFghjbG91ZGJ1cjESMBAGCgmSJomT8ixkARkWAnN0MIIB
|
34
|
+
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5C0Io89nyApnr+PvbNFge9Vs
|
35
|
+
yRWAlGBUEMahpXp28VrrfXZT0rAW7JBo4PlCE3jl4nE4dzE6gAdItSycjTosrw7A
|
36
|
+
Ir5+xoyl4Vb35adv56TIQQXvNz+BzlqnkAY5JN0CSBRTQb6mxS3hFyD/h4qgDosj
|
37
|
+
R2RFVzHqSxCS8xq4Ny8uzOwOi+Xyu4w67fI5JvnPvMxqrlR1eaIQHmxnf76RzC46
|
38
|
+
QO5QhufjAYGGXd960XzbQsQyTDUYJzrvT7AdOfiyZzKQykKt8dEpDn+QPjFTnGnT
|
39
|
+
QmgJBX5WJN0lHF2l1sbv3gh4Kn1tZu+kTUqeXY6ShAoDTyvZRiFqQdwh8w2lTQID
|
40
|
+
AQABozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU+WqJz3xQ
|
41
|
+
XSea1hRvvHWcIMgeeC4wDQYJKoZIhvcNAQEFBQADggEBAGLZ75jfOEW8Nsl26CTt
|
42
|
+
JFrWxQTcQT/UljeefVE3xYr7lc9oQjbqO3FOyued3qW7TaNEtZfSHoYeUSMYbpw1
|
43
|
+
XAwocIPuSRFDGM4B+hgQGVDx8PMGiJKom4qLXjO40UZsR7QyN/u869Vj45LURm6h
|
44
|
+
MBcPeqCASI+WNprj9+uZa2kmHiitrFqqfMBNlm5IFbn9XeYSta9AHVvs5QQqV2m5
|
45
|
+
hIPfLqCyxsn/YgOGvo6iwyQTWyTswamaAC3HRWZxIS1sfn/Ssqa7E7oQMkv5FAXr
|
46
|
+
x5rKePfXINf8XTJczkl9OBEYdE9aNdJsJpXD0asLgGVwBICS5Bjohp6mizJcDC1+
|
47
|
+
yZ0=
|
48
|
+
-----END CERTIFICATE-----
|
49
|
+
|
28
50
|
post_install_message:
|
29
51
|
authors:
|
30
52
|
- ""
|
31
53
|
files:
|
54
|
+
- TODO
|
32
55
|
- test/unit/polymorph_test.rb
|
33
56
|
- test/test_helper.rb
|
34
57
|
- test/schema.rb
|
@@ -37,6 +60,8 @@ files:
|
|
37
60
|
- test/models/wild_boar.rb
|
38
61
|
- test/models/tabby.rb
|
39
62
|
- test/models/petfood.rb
|
63
|
+
- test/models/person.rb
|
64
|
+
- test/models/parentship.rb
|
40
65
|
- test/models/kitten.rb
|
41
66
|
- test/models/frog.rb
|
42
67
|
- test/models/eaters_foodstuff.rb
|
@@ -49,6 +74,7 @@ files:
|
|
49
74
|
- test/models/aquatic/fish.rb
|
50
75
|
- test/fixtures/wild_boars.yml
|
51
76
|
- test/fixtures/petfoods.yml
|
77
|
+
- test/fixtures/people.yml
|
52
78
|
- test/fixtures/keep_your_enemies_close.yml
|
53
79
|
- test/fixtures/frogs.yml
|
54
80
|
- test/fixtures/eaters_foodstuffs.yml
|
@@ -57,12 +83,16 @@ files:
|
|
57
83
|
- test/fixtures/aquatic/whales.yml
|
58
84
|
- test/fixtures/aquatic/little_whale_pupils.yml
|
59
85
|
- test/fixtures/aquatic/fish.yml
|
86
|
+
- README
|
87
|
+
- Manifest
|
88
|
+
- LICENSE
|
60
89
|
- lib/has_many_polymorphs.rb
|
61
90
|
- lib/has_many_polymorphs/support_methods.rb
|
62
91
|
- lib/has_many_polymorphs/reflection.rb
|
63
92
|
- lib/has_many_polymorphs/rake_task_redefine_task.rb
|
64
93
|
- lib/has_many_polymorphs/dependencies.rb
|
65
94
|
- lib/has_many_polymorphs/debugging_tools.rb
|
95
|
+
- lib/has_many_polymorphs/configuration.rb
|
66
96
|
- lib/has_many_polymorphs/class_methods.rb
|
67
97
|
- lib/has_many_polymorphs/base.rb
|
68
98
|
- lib/has_many_polymorphs/autoload.rb
|
@@ -77,9 +107,6 @@ files:
|
|
77
107
|
- generators/tagging/templates/tag.rb
|
78
108
|
- generators/tagging/templates/migration.rb
|
79
109
|
- generators/tagging/tagging_generator.rb
|
80
|
-
- TODO
|
81
|
-
- README
|
82
|
-
- LICENSE
|
83
110
|
- CHANGELOG
|
84
111
|
- has_many_polymorphs.gemspec
|
85
112
|
test_files:
|
metadata.gz.sig
ADDED
Binary file
|