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 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 grguest
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
- == Further resources
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
@@ -1,3 +1,4 @@
1
1
 
2
+ * Migration examples in docs
2
3
  * Controller for tagging generator
3
4
  * Tag cloud method
@@ -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
- self.class.const_get(model[1..-1].classify) rescue puts "** Error: model #{model[1..-1].classify} could not be loaded." or exit
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, :recipes, :posts
4
+ fixtures :tags, :taggings, <%= taggable_models[0..1].join(", ") -%>
5
5
 
6
6
  def test_to_s
7
- assert_equal "delicious sexy", Recipe.find(2).tags.to_s
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, :posts, :recipes
4
+ fixtures :taggings, :tags, <%= taggable_models[0..1].join(", ") -%>
5
5
 
6
6
  def setup
7
- @obj1 = Recipe.find(1)
8
- @obj2 = Recipe.find(2)
9
- @obj3 = Post.find(1)
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 = taggable_models[0][1..-1].classify %>
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 = (taggable_models[1][1..-1].classify rescue model_one) %>
12
+ taggable_type: <%= model_two %>
11
13
  taggable_id: "2"
12
14
  taggings_001:
13
15
  <%= parent_association_name -%>_id: "1"
@@ -1,11 +1,11 @@
1
1
 
2
- # Gem::Specification for Has_many_polymorphs-2.9
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.9"
8
- s.date = %q{2007-08-13}
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", "TODO", "README", "LICENSE", "CHANGELOG", "has_many_polymorphs.gemspec"]
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/snax/public/files/doc/"
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.'
@@ -153,7 +153,7 @@ module ActiveRecord #:nodoc:
153
153
  raise PolymorphicMethodNotSupportedError, "You can't associate new records."
154
154
  end
155
155
 
156
- end
157
-
156
+ end
157
+
158
158
  end
159
159
  end
@@ -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
- Overrides Rails::Initializer#after_initialize.
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
- def after_initialize_with_autoload
13
- after_initialize_without_autoload
14
-
15
- _logger_debug "has_many_polymorphs: autoload hook invoked"
16
- Dir["#{RAILS_ROOT}/app/models/**/*.rb"].each do |filename|
17
- next if filename =~ /svn|CVS|bzr/
18
- open filename do |file|
19
- if file.grep(/has_many_polymorphs|acts_as_double_polymorphic_join/).any?
20
- begin
21
- model = File.basename(filename)[0..-4].classify
22
- _logger_warn "has_many_polymorphs: preloading parent model #{model}"
23
- model.constantize
24
- rescue Object => e
25
- _logger_warn "has_many_polymorphs: WARNING; possibly critical error preloading #{model}: #{e.inspect}"
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
- end
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 instantiate_without_callbacks_with_polymorphic_checks(record)
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
- instantiate_without_callbacks_without_polymorphic_checks(record)
52
+ instantiate_without_polymorphic_checks(record)
43
53
  end
44
54
  end
45
55
 
46
- alias_method_chain :instantiate_without_callbacks, :polymorphic_checks
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 class and table is required. There are tentative reports that it works without it if you make the parent class join to the targets <tt>:through</tt> itself, but this is untested.
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
- RESERVED_KEYS = [:conditions, :order, :limit, :offset, :extend, :skip_duplicates,
22
- :join_extend, :dependent, :rename_individual_collections] #:nodoc:
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 = options._select {|k,v| v.is_a? Array and k.to_s !~ /(#{RESERVED_KEYS.map(&:to_s).join('|')})$/}
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
- options = options._select {|k,v| !collections[k]}
64
- options[:extend] = (options[:extend] ? Array(options[:extend]) + [extension] : extension) if extension # inline the block
65
-
66
- collection_option_keys = Hash[*collections.keys.map do |key|
67
- [key, RESERVED_KEYS.map{|option| "#{key}_#{option}".to_sym}]
68
- end._flatten_once]
69
-
70
- collections.keys.each do |collection|
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
- parent_class.send(:has_many_polymorphs,
109
- association_id, {:is_double => true,
110
- :from => children,
111
- :as => singular_reverse_association_id,
112
- :through => join_name.to_sym,
113
- :foreign_key => parent_foreign_key,
114
- :foreign_type_key => parent_foreign_key.to_s.sub(/_id$/, '_type'),
115
- :singular_reverse_association_id => singular_reverse_association_id,
116
- :conflicts => conflicts}.merge(Hash[*options._select do |key, value|
117
- collection_option_keys[association_id].include? key and !value.nil?
118
- end.map do |key, value|
119
- [key.to_s[association_id.to_s.length+1..-1].to_sym, value]
120
- end._flatten_once])) # rename side-specific options to general names
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 parent is always plural.)
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 classes. Required.
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 classes' association to the parent.
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.downcase.to_sym
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
- # options[:finder_sql] ||= "(options[:polymorphic_key]
262
-
263
- options[:through] ||= build_join_table_symbol((options[:as]._pluralize or self.table_name), association_id)
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
- table = plural._as_class.table_name
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 = {:foreign_key => reflection.options[:foreign_key],
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
- # :limit => reflection.options[:limit],
332
- # :offset => reflection.options[:offset],
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(:#{association_id._singularize}_type=, self.#{association_id._singularize}_type.constantize.base_class.name)]
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[:order], reflection.klass),
396
- :conditions => devolve(association_id, reflection, reflection.options[:conditions], reflection.klass)
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 collection parents
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(a, b)
474
- [a.to_s, b.to_s].sort.join("_").to_sym
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
- (all_classes_for(association_id, reflection) - # the join class must always be preserved
486
- [klass, klass.base_class, reflection.klass, reflection.klass.base_class]).map do |klass|
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
+
@@ -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
@@ -0,0 +1,7 @@
1
+ bob:
2
+ id: 1
3
+ name: Bob
4
+ age: 45
5
+ created_at: "2007-04-01 12:00:00"
6
+ updated_at: "2007-04-04 10:00:00"
7
+
@@ -0,0 +1,4 @@
1
+ class Parentship < ActiveRecord::Base
2
+ belongs_to :parent, :class_name => "Person", :foreign_key => "parent_id"
3
+ belongs_to :kid, :polymorphic => true, :foreign_type => "child_type"
4
+ end
@@ -0,0 +1,9 @@
1
+ require 'parentship'
2
+ class Person < ActiveRecord::Base
3
+ has_many_polymorphs :kids,
4
+ :through => :parentships,
5
+ :from => [:people],
6
+ :as => :parent,
7
+ :polymorphic_type_key => "child_type",
8
+ :conditions => "people.age < 10"
9
+ end
@@ -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
@@ -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
- Aquatic::PupilsWhale.reflect_on_all_associations.map &:check_validity!
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 @join_record = EatersFoodstuff.new(:foodstuff_id => @bits.id, :eater_id => @chloe.id, :eater_type => @chloe.class.name )
94
- assert @join_record.save!
95
- assert @join_record.id
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
- @join_record = EatersFoodstuff.new(:foodstuff_id => @bits.id, :eater_id => @puma.id, :eater_type => @puma.class.name)
262
- @join_record.save!
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
- acts_as_double_polymorphic_join :polymorph => [:dogs, :cats], :unimorphs => [:dogs, :cats]
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 test_parent_order_orders_parents
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
@@ -0,0 +1,3 @@
1
+ ک�E;ˇ^;��G��N�nh�Qy�<)��Dhk
2
+ �P�"�%��qI5��i�[�8�c�VNӬ�kz�F��($<hC�-v�;�����s4ɲ�m �\J:�ս���ʖ�� �'��jb
3
+ �J����ޠ+��u$=��-KQ�5��/?_������0_x~�3mDo�D���?���h����m���o Ϥ����x�1��U�����i�9�\&9��Wd�r���Sؖ��> �Au�/j{���P�R��qf��
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.9"
7
- date: 2007-08-13 00:00:00 -04:00
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