Commit ca3fcbeb14ab838a8f58faab3972d36b87c0637d

Authored by Rafael Martins
1 parent df397df0

Revert nested_has_many_through but fix has_one :through

test/unit/product_test.rb
... ... @@ -8,6 +8,14 @@ class ProductTest < ActiveSupport::TestCase
8 8 @profile = fast_create(Enterprise)
9 9 end
10 10  
  11 + should 'return associated enterprise region' do
  12 + @profile.region = fast_create Region, :name => 'Salvador'
  13 + @profile.save!
  14 + p = fast_create(Product, :name => 'test product1', :product_category_id => @product_category.id, :enterprise_id => @profile.id)
  15 +
  16 + assert_equal @profile.region, p.region
  17 + end
  18 +
11 19 should 'create product' do
12 20 assert_difference Product, :count do
13 21 p = Product.new(:name => 'test product1', :product_category => @product_category, :enterprise_id => @profile.id)
... ...
vendor/plugins/nested_has_many_through/.gitignore 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +.garlic
  2 +doc/*
... ...
vendor/plugins/nested_has_many_through/CHANGELOG 0 → 100644
... ... @@ -0,0 +1,4 @@
  1 +* spec'd and fixed some problems with using named_scope in edge
  2 +
  3 +* Initial commit
  4 +
... ...
vendor/plugins/nested_has_many_through/MIT-LICENSE 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2008 Ian White - ian.w.white@gmail.com
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 21 \ No newline at end of file
... ...
vendor/plugins/nested_has_many_through/README.rdoc 0 → 100755
... ... @@ -0,0 +1,92 @@
  1 +http://plugins.ardes.com > nested_has_many_through
  2 +
  3 += nested_has_many_through
  4 +
  5 +A fantastic patch/plugin has been floating around for a while:
  6 +
  7 +* http://dev.rubyonrails.org/ticket/6461
  8 +* http://code.torchbox.com/svn/rails/plugins/nested_has_many_through
  9 +
  10 +obrie made the original ticket and Matt Westcott released the first version of
  11 +the plugin, under the MIT license. Many others have contributed, see the trac
  12 +ticket for details.
  13 +
  14 +Here is a refactored version (I didn't write the original), suitable for edge/2.0-stable
  15 +with a bunch of acceptance specs. I'm concentrating on plugin usage, once
  16 +it becomes stable, and well enough speced/understood, then it's time to pester
  17 +rails-core.
  18 +
  19 +== Why republish this on github?
  20 +
  21 +* The previous implementations are very poorly speced/tested, so it's pretty
  22 + hard to refactor and understand this complicated bit of sql-fu, especially
  23 + when you're aiming at a moving target (edge)
  24 +* the lastest patches don't apply on edge
  25 +* github - let's collab to make this better and get a patch accepted, fork away!
  26 +
  27 +== Help out
  28 +
  29 +I'm releasing 'early and often' in the hope that people will use it and find bugs/problems.
  30 +Report them at http://ianwhite.lighthouseapp.com, or fork and pull request, yada yada.
  31 +
  32 +== History
  33 +
  34 +Here's the original description:
  35 +
  36 + This plugin makes it possible to define has_many :through relationships that
  37 + go through other has_many :through relationships, possibly through an
  38 + arbitrarily deep hierarchy. This allows associations across any number of
  39 + tables to be constructed, without having to resort to find_by_sql (which isn't
  40 + a suitable solution if you need to do eager loading through :include as well).
  41 +
  42 +== Contributors
  43 +
  44 +* Matt Westcott
  45 +* terceiro
  46 +* shoe
  47 +* mhoroschun
  48 +* Ian White (http://github.com/ianwhite)
  49 +* Claudio (http://github.com/masterkain)
  50 +
  51 +Get in touch if you should be on this list
  52 +
  53 +== Show me the money!
  54 +
  55 +Here's some models from the specs:
  56 +
  57 + class Author < User
  58 + has_many :posts
  59 + has_many :categories, :through => :posts, :uniq => true
  60 + has_many :similar_posts, :through => :categories, :source => :posts
  61 + has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
  62 + has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
  63 + has_many :commenters, :through => :posts, :uniq => true
  64 + end
  65 +
  66 + class Post < ActiveRecord::Base
  67 + belongs_to :author
  68 + belongs_to :category
  69 + has_many :comments
  70 + has_many :commenters, :through => :comments, :source => :user, :uniq => true
  71 + end
  72 +
  73 +The first two has_manys of Author are plain vanilla, the last four are what this plugin enables
  74 +
  75 + # has_many through a has_many :through
  76 + has_many :similar_posts, :through => :categories, :source => :posts
  77 +
  78 + # doubly nested has_many :through
  79 + has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
  80 +
  81 + # whoah!
  82 + has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
  83 +
  84 + # has_many through a has_many :through in another model
  85 + has_many :commenters, :through => :posts, :uniq => true
  86 +
  87 +== What does it run on?
  88 +
  89 +Currently it's running on 2.0, 2.1, and 2.2 stable branches
  90 +
  91 +If you want to run the CI suite, then check out garlic_example.rb (The CI suite
  92 +is being cooked with garlic - git://github.com/ianwhite/garlic)
... ...
vendor/plugins/nested_has_many_through/Rakefile 0 → 100644
... ... @@ -0,0 +1,77 @@
  1 +# use pluginized rpsec if it exists
  2 +rspec_base = File.expand_path(File.dirname(__FILE__) + '/../rspec/lib')
  3 +$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base) and !$LOAD_PATH.include?(rspec_base)
  4 +
  5 +require 'spec/rake/spectask'
  6 +require 'spec/rake/verify_rcov'
  7 +require 'rake/rdoctask'
  8 +
  9 +plugin_name = 'nested_has_many_through'
  10 +
  11 +task :default => :spec
  12 +
  13 +desc "Run the specs for #{plugin_name}"
  14 +Spec::Rake::SpecTask.new(:spec) do |t|
  15 + t.spec_files = FileList['spec/**/*_spec.rb']
  16 + t.spec_opts = ["--colour"]
  17 +end
  18 +
  19 +namespace :spec do
  20 + desc "Generate RCov report for #{plugin_name}"
  21 + Spec::Rake::SpecTask.new(:rcov) do |t|
  22 + t.spec_files = FileList['spec/**/*_spec.rb']
  23 + t.rcov = true
  24 + t.rcov_dir = 'doc/coverage'
  25 + t.rcov_opts = ['--text-report', '--exclude', "spec/,rcov.rb,#{File.expand_path(File.join(File.dirname(__FILE__),'../../..'))}"]
  26 + end
  27 +
  28 + namespace :rcov do
  29 + desc "Verify RCov threshold for #{plugin_name}"
  30 + RCov::VerifyTask.new(:verify => "spec:rcov") do |t|
  31 + t.threshold = 97.1
  32 + t.index_html = File.join(File.dirname(__FILE__), 'doc/coverage/index.html')
  33 + end
  34 + end
  35 +
  36 + desc "Generate specdoc for #{plugin_name}"
  37 + Spec::Rake::SpecTask.new(:doc) do |t|
  38 + t.spec_files = FileList['spec/**/*_spec.rb']
  39 + t.spec_opts = ["--format", "specdoc:SPECDOC"]
  40 + end
  41 +
  42 + namespace :doc do
  43 + desc "Generate html specdoc for #{plugin_name}"
  44 + Spec::Rake::SpecTask.new(:html => :rdoc) do |t|
  45 + t.spec_files = FileList['spec/**/*_spec.rb']
  46 + t.spec_opts = ["--format", "html:doc/rspec_report.html", "--diff"]
  47 + end
  48 + end
  49 +end
  50 +
  51 +task :rdoc => :doc
  52 +task "SPECDOC" => "spec:doc"
  53 +
  54 +desc "Generate rdoc for #{plugin_name}"
  55 +Rake::RDocTask.new(:doc) do |t|
  56 + t.rdoc_dir = 'doc'
  57 + t.main = 'README.rdoc'
  58 + t.title = "#{plugin_name}"
  59 + t.template = ENV['RDOC_TEMPLATE']
  60 + t.options = ['--line-numbers', '--inline-source']
  61 + t.rdoc_files.include('README.rdoc', 'SPECDOC', 'MIT-LICENSE')
  62 + t.rdoc_files.include('lib/**/*.rb')
  63 +end
  64 +
  65 +namespace :doc do
  66 + desc "Generate all documentation (rdoc, specdoc, specdoc html and rcov) for #{plugin_name}"
  67 + task :all => ["spec:doc:html", "spec:doc", "spec:rcov", "doc"]
  68 +end
  69 +
  70 +task :cruise do
  71 + # run the garlic task, capture the output, if succesful make the docs and copy them to ardes
  72 + sh "garlic all"
  73 + `garlic run > .garlic/report.txt`
  74 + `scp -i ~/.ssh/ardes .garlic/report.txt ardes@ardes.com:~/subdomains/plugins/httpdocs/doc/#{plugin_name}_garlic_report.txt`
  75 + `cd .garlic/*/vendor/plugins/#{plugin_name}; rake doc:all; scp -i ~/.ssh/ardes -r doc ardes@ardes.com:~/subdomains/plugins/httpdocs/doc/#{plugin_name}`
  76 + puts "The build is GOOD"
  77 +end
... ...
vendor/plugins/nested_has_many_through/SPECDOC 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +
  2 +Author (newly created)
  3 +- #posts should == []
  4 +- #categories should == []
  5 +- #similar_posts should == []
  6 +- #similar_authors should == []
  7 +- #commenters should == []
  8 +
  9 +Author (newly created) who creates post with category
  10 +- #posts should == [post]
  11 +- #categories should == [category]
  12 +
  13 +Author (newly created) who creates post with category and @other_author creates post2 in category
  14 +- #posts should == [post2]
  15 +- #categories should == [category]
  16 +- #similar_posts.should == [post, post2]
  17 +- #similar_authors.should == [@author, @other_author]
  18 +
  19 +Author (newly created) who creates post with category and @other_author creates post2 in category and creates @other_post in @other_category
  20 +- #similar_posts.should == [@post, @post2]
  21 +- #posts_by_similar_authors.should == [@post, @post2, @other_post]
  22 +
  23 +Commenter use case (a1: p1>c1, a2: p2>c1, p3>c2, a3: p4>c3)
  24 +- a1.posts should == [p1]
  25 +- a1.categories should == [c1]
  26 +- a2.posts should == [p2, p3]
  27 +- a2.categories should == [c1, c2]
  28 +
  29 +Commenter use case (a1: p1>c1, a2: p2>c1, p3>c2, a3: p4>c3) u1 comments on p2
  30 +- u1.comments should == [comment]
  31 +- a1.commenters should be empty
  32 +- a2.commenters should == [u1]
  33 +- u1.commented_posts should == [p2]
  34 +- u1.commented_posts.find_inflamatory(:all) should be empty
  35 +- u1.commented_posts.inflamatory should be empty
  36 +- u1.commented_authors should == [a2]
  37 +- u1.posts_of_interest should == [p1, p2, p3]
  38 +- u1.categories_of_interest should == [c1, c2]
  39 +
  40 +Commenter use case (a1: p1>c1, a2: p2>c1, p3>c2, a3: p4>c3) u1 comments on p2 when p2 is inflamatory
  41 +- p2 should be inflamatory
  42 +- u1.commented_posts.find_inflamatory(:all) should == [p2]
  43 +- u1.posts_of_interest.find_inflamatory(:all) should == [p2]
  44 +- u1.commented_posts.inflamatory should == [p2]
  45 +- u1.posts_of_interest.inflamatory should == [p2]
  46 +
  47 +Finished in 0.538693 seconds
  48 +
  49 +31 examples, 0 failures
... ...
vendor/plugins/nested_has_many_through/TODO 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +* get C2 up to 100%
  2 + - spec a polymorphic relationship
  3 +
  4 +* quote table names
  5 +
  6 +* make more use of rails in construct_has_many_or_belongs_to_attributes to reduce brittleness
  7 +
  8 +* Add more coverage
  9 + - scopes
  10 + - raise an error when nhmt is being used in a perverse way
0 11 \ No newline at end of file
... ...
vendor/plugins/nested_has_many_through/garlic.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +garlic do
  2 + repo 'nested_has_many_through', :path => '.'
  3 +
  4 + repo 'rails', :url => 'git://github.com/rails/rails'
  5 + repo 'rspec', :url => 'git://github.com/dchelimsky/rspec'
  6 + repo 'rspec-rails', :url => 'git://github.com/dchelimsky/rspec-rails'
  7 +
  8 + # target rails versions
  9 + ['origin/2-2-stable', 'origin/2-1-stable', 'origin/2-0-stable'].each do |rails|
  10 + # specify how to prepare app and run CI task
  11 + target "Rails: #{rails}", :tree_ish => rails do
  12 + prepare do
  13 + plugin 'rspec'
  14 + plugin 'rspec-rails' do
  15 + `script/generate rspec -f`
  16 + end
  17 + plugin 'nested_has_many_through', :clone => true
  18 + end
  19 +
  20 + run do
  21 + cd "vendor/plugins/nested_has_many_through" do
  22 + sh "rake spec:rcov:verify"
  23 + end
  24 + end
  25 + end
  26 + end
  27 +end
... ...
vendor/plugins/nested_has_many_through/init.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +require 'nested_has_many_through'
  2 +
  3 +ActiveRecord::Associations::HasManyThroughAssociation.send :include, NestedHasManyThrough::Association
  4 +
  5 +# BC
  6 +if defined?(ActiveRecord::Reflection::ThroughReflection)
  7 + ActiveRecord::Reflection::ThroughReflection.send :include, NestedHasManyThrough::Reflection
  8 +else
  9 + ActiveRecord::Reflection::AssociationReflection.send :include, NestedHasManyThrough::Reflection
  10 +end
0 11 \ No newline at end of file
... ...
vendor/plugins/nested_has_many_through/lib/nested_has_many_through.rb 0 → 100644
... ... @@ -0,0 +1,148 @@
  1 +module NestedHasManyThrough
  2 + module Reflection # :nodoc:
  3 + def self.included(base)
  4 + base.send :alias_method_chain, :check_validity!, :nested_has_many_through
  5 + end
  6 +
  7 + def check_validity_with_nested_has_many_through!
  8 + check_validity_without_nested_has_many_through!
  9 + rescue ActiveRecord::HasManyThroughSourceAssociationMacroError => e
  10 + # now we permit has many through to a :though source
  11 + raise e unless source_reflection.options[:through]
  12 + end
  13 + end
  14 +
  15 + module Association
  16 + def self.included(base)
  17 + base.class_eval do
  18 + alias_method :original_construct_conditions, :construct_conditions
  19 + alias_method :original_construct_joins, :construct_joins
  20 +
  21 + def construct_conditions
  22 + if @reflection.macro == :has_one
  23 + original_construct_conditions
  24 + else
  25 + @nested_join_attributes ||= construct_nested_join_attributes
  26 + "#{@nested_join_attributes[:remote_key]} = #{@owner.quoted_id} #{@nested_join_attributes[:conditions]}"
  27 + end
  28 + end
  29 +
  30 + def construct_joins(custom_joins = nil)
  31 + if @reflection.macro == :has_one
  32 + original_construct_joins(custom_joins)
  33 + else
  34 + @nested_join_attributes ||= construct_nested_join_attributes
  35 + "#{@nested_join_attributes[:joins]} #{custom_joins}"
  36 + end
  37 + end
  38 + end
  39 + end
  40 +
  41 + protected
  42 + # Given any belongs_to or has_many (including has_many :through) association,
  43 + # return the essential components of a join corresponding to that association, namely:
  44 + #
  45 + # * <tt>:joins</tt>: any additional joins required to get from the association's table
  46 + # (reflection.table_name) to the table that's actually joining to the active record's table
  47 + # * <tt>:remote_key</tt>: the name of the key in the join table (qualified by table name) which will join
  48 + # to a field of the active record's table
  49 + # * <tt>:local_key</tt>: the name of the key in the local table (not qualified by table name) which will
  50 + # take part in the join
  51 + # * <tt>:conditions</tt>: any additional conditions (e.g. filtering by type for a polymorphic association,
  52 + # or a :conditions clause explicitly given in the association), including a leading AND
  53 + def construct_nested_join_attributes( reflection = @reflection,
  54 + association_class = reflection.klass,
  55 + table_ids = {association_class.table_name => 1})
  56 + if reflection.macro == :has_many && reflection.through_reflection
  57 + construct_has_many_through_attributes(reflection, table_ids)
  58 + else
  59 + construct_has_many_or_belongs_to_attributes(reflection, association_class, table_ids)
  60 + end
  61 + end
  62 +
  63 + def construct_has_many_through_attributes(reflection, table_ids)
  64 + # Construct the join components of the source association, so that we have a path from
  65 + # the eventual target table of the association up to the table named in :through, and
  66 + # all tables involved are allocated table IDs.
  67 + source_attrs = construct_nested_join_attributes(reflection.source_reflection, reflection.klass, table_ids)
  68 +
  69 + # Determine the alias of the :through table; this will be the last table assigned
  70 + # when constructing the source join components above.
  71 + through_table_alias = through_table_name = reflection.through_reflection.table_name
  72 + through_table_alias += "_#{table_ids[through_table_name]}" unless table_ids[through_table_name] == 1
  73 +
  74 + # Construct the join components of the through association, so that we have a path to
  75 + # the active record's table.
  76 + through_attrs = construct_nested_join_attributes(reflection.through_reflection, reflection.through_reflection.klass, table_ids)
  77 +
  78 + # Any subsequent joins / filters on owner attributes will act on the through association,
  79 + # so that's what we return for the conditions/keys of the overall association.
  80 + conditions = through_attrs[:conditions]
  81 + conditions += " AND #{interpolate_sql(reflection.klass.send(:sanitize_sql, reflection.options[:conditions]))}" if reflection.options[:conditions]
  82 +
  83 + {
  84 + :joins => "%s INNER JOIN %s ON ( %s = %s.%s %s) %s %s" % [
  85 + source_attrs[:joins],
  86 + through_table_name == through_table_alias ? through_table_name : "#{through_table_name} #{through_table_alias}",
  87 + source_attrs[:remote_key],
  88 + through_table_alias, source_attrs[:local_key],
  89 + source_attrs[:conditions],
  90 + through_attrs[:joins],
  91 + reflection.options[:joins]
  92 + ],
  93 + :remote_key => through_attrs[:remote_key],
  94 + :local_key => through_attrs[:local_key],
  95 + :conditions => conditions
  96 + }
  97 + end
  98 +
  99 +
  100 + # reflection is not has_many :through; it's a standard has_many / belongs_to instead
  101 + # TODO: see if we can defer to rails code here a bit more
  102 + def construct_has_many_or_belongs_to_attributes(reflection, association_class, table_ids)
  103 + # Determine the alias used for remote_table_name, if any. In all cases this will already
  104 + # have been assigned an ID in table_ids (either through being involved in a previous join,
  105 + # or - if it's the first table in the query - as the default value of table_ids)
  106 + remote_table_alias = remote_table_name = association_class.table_name
  107 + remote_table_alias += "_#{table_ids[remote_table_name]}" unless table_ids[remote_table_name] == 1
  108 +
  109 + # Assign a new alias for the local table.
  110 + local_table_alias = local_table_name = reflection.active_record.table_name
  111 + if table_ids[local_table_name]
  112 + table_id = table_ids[local_table_name] += 1
  113 + local_table_alias += "_#{table_id}"
  114 + else
  115 + table_ids[local_table_name] = 1
  116 + end
  117 +
  118 + conditions = ''
  119 + # Add filter for single-table inheritance, if applicable.
  120 + conditions += " AND #{remote_table_alias}.#{association_class.inheritance_column} = #{association_class.quote_value(association_class.name.demodulize)}" unless association_class.descends_from_active_record?
  121 + # Add custom conditions
  122 + conditions += " AND (#{interpolate_sql(association_class.send(:sanitize_sql, reflection.options[:conditions]))})" if reflection.options[:conditions]
  123 +
  124 + if reflection.macro == :belongs_to
  125 + if reflection.options[:polymorphic]
  126 + conditions += " AND #{local_table_alias}.#{reflection.options[:foreign_type]} = #{reflection.active_record.quote_value(association_class.base_class.name.to_s)}"
  127 + end
  128 + {
  129 + :joins => reflection.options[:joins],
  130 + :remote_key => "#{remote_table_alias}.#{association_class.primary_key}",
  131 + :local_key => reflection.primary_key_name,
  132 + :conditions => conditions
  133 + }
  134 + else
  135 + # Association is has_many (without :through)
  136 + if reflection.options[:as]
  137 + conditions += " AND #{remote_table_alias}.#{reflection.options[:as]}_type = #{reflection.active_record.quote_value(reflection.active_record.base_class.name.to_s)}"
  138 + end
  139 + {
  140 + :joins => "#{reflection.options[:joins]}",
  141 + :remote_key => "#{remote_table_alias}.#{reflection.primary_key_name}",
  142 + :local_key => reflection.klass.primary_key,
  143 + :conditions => conditions
  144 + }
  145 + end
  146 + end
  147 + end
  148 +end
... ...
vendor/plugins/nested_has_many_through/spec/app.rb 0 → 100644
... ... @@ -0,0 +1,84 @@
  1 +# Testing app setup
  2 +
  3 +##################
  4 +# Database schema
  5 +##################
  6 +
  7 +ActiveRecord::Migration.suppress_messages do
  8 + ActiveRecord::Schema.define(:version => 0) do
  9 + create_table :users, :force => true do |t|
  10 + t.column "type", :string
  11 + end
  12 +
  13 + create_table :posts, :force => true do |t|
  14 + t.column "author_id", :integer
  15 + t.column "category_id", :integer
  16 + t.column "inflamatory", :boolean
  17 + end
  18 +
  19 + create_table :categories, :force => true do |t|
  20 + end
  21 +
  22 + create_table :comments, :force => true do |t|
  23 + t.column "user_id", :integer
  24 + t.column "post_id", :integer
  25 + end
  26 + end
  27 +end
  28 +
  29 +#########
  30 +# Models
  31 +#
  32 +# Domain model is this:
  33 +#
  34 +# - authors (type of user) can create posts in categories
  35 +# - users can comment on posts
  36 +# - authors have similar_posts: posts in the same categories as ther posts
  37 +# - authors have similar_authors: authors of the recommended_posts
  38 +# - authors have posts_of_similar_authors: all posts by similar authors (not just the similar posts,
  39 +# similar_posts is be a subset of this collection)
  40 +# - authors have commenters: users who have commented on their posts
  41 +#
  42 +class User < ActiveRecord::Base
  43 + has_many :comments
  44 + has_many :commented_posts, :through => :comments, :source => :post, :uniq => true
  45 + has_many :commented_authors, :through => :commented_posts, :source => :author, :uniq => true
  46 + has_many :posts_of_interest, :through => :commented_authors, :source => :posts_of_similar_authors, :uniq => true
  47 + has_many :categories_of_interest, :through => :posts_of_interest, :source => :category, :uniq => true
  48 +end
  49 +
  50 +class Author < User
  51 + has_many :posts
  52 + has_many :categories, :through => :posts
  53 + has_many :similar_posts, :through => :categories, :source => :posts
  54 + has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
  55 + has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
  56 + has_many :commenters, :through => :posts, :uniq => true
  57 +end
  58 +
  59 +class Post < ActiveRecord::Base
  60 +
  61 + # testing with_scope
  62 + def self.find_inflamatory(*args)
  63 + with_scope :find => {:conditions => {:inflamatory => true}} do
  64 + find(*args)
  65 + end
  66 + end
  67 +
  68 + # only test named_scope in edge
  69 + named_scope(:inflamatory, :conditions => {:inflamatory => true}) if respond_to?(:named_scope)
  70 +
  71 + belongs_to :author
  72 + belongs_to :category
  73 + has_many :comments
  74 + has_many :commenters, :through => :comments, :source => :user, :uniq => true
  75 +end
  76 +
  77 +class Category < ActiveRecord::Base
  78 + has_many :posts
  79 +end
  80 +
  81 +class Comment < ActiveRecord::Base
  82 + belongs_to :user
  83 + belongs_to :post
  84 +end
0 85 \ No newline at end of file
... ...
vendor/plugins/nested_has_many_through/spec/models/author_spec.rb 0 → 100644
... ... @@ -0,0 +1,85 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
  2 +require File.expand_path(File.join(File.dirname(__FILE__), '../app'))
  3 +
  4 +describe Author do
  5 + describe "(newly created)" do
  6 + before do
  7 + @category = Category.create!
  8 + @other_category = Category.create!
  9 + @author = Author.create!
  10 + end
  11 +
  12 + it "#posts should == []" do
  13 + @author.posts.should == []
  14 + end
  15 +
  16 + it "#categories should == []" do
  17 + @author.categories.should == []
  18 + end
  19 +
  20 + it "#similar_posts should == []" do
  21 + @author.similar_posts.should == []
  22 + end
  23 +
  24 + it "#similar_authors should == []" do
  25 + @author.similar_authors.should == []
  26 + end
  27 +
  28 + it "#commenters should == []" do
  29 + @author.commenters.should == []
  30 + end
  31 +
  32 + describe "who creates post with category" do
  33 + before do
  34 + @post = Post.create! :author => @author, :category => @category
  35 + end
  36 +
  37 + it "#posts should == [post]" do
  38 + @author.posts.should == [@post]
  39 + end
  40 +
  41 + it "#categories should == [category]" do
  42 + @author.categories.should == [@category]
  43 + end
  44 +
  45 + describe "and @other_author creates post2 in category" do
  46 +
  47 + before do
  48 + @other_author = Author.create!
  49 + @post2 = Post.create! :author => @other_author, :category => @category
  50 + end
  51 +
  52 + it "#posts should == [post2]" do
  53 + @author.posts.should == [@post]
  54 + end
  55 +
  56 + it "#categories should == [category]" do
  57 + @author.categories.should == [@category]
  58 + end
  59 +
  60 + it "#similar_posts.should == [post, post2]" do
  61 + @author.similar_posts.should == [@post, @post2]
  62 + end
  63 +
  64 + it "#similar_authors.should == [@author, @other_author]" do
  65 + @author.similar_authors.should == [@author, @other_author]
  66 + end
  67 +
  68 + describe "and creates @other_post in @other_category" do
  69 + before do
  70 + @other_category = Category.create!
  71 + @other_post = Post.create! :author => @other_author, :category => @other_category
  72 + end
  73 +
  74 + it "#similar_posts.should == [@post, @post2]" do
  75 + @author.similar_posts.should == [@post, @post2]
  76 + end
  77 +
  78 + it "#posts_by_similar_authors.should == [@post, @post2, @other_post]" do
  79 + @author.posts_of_similar_authors.should == [@post, @post2, @other_post]
  80 + end
  81 + end
  82 + end
  83 + end
  84 + end
  85 +end
0 86 \ No newline at end of file
... ...
vendor/plugins/nested_has_many_through/spec/models/commenter_spec.rb 0 → 100644
... ... @@ -0,0 +1,109 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
  2 +require File.expand_path(File.join(File.dirname(__FILE__), '../app'))
  3 +
  4 +describe 'Commenter use case (a1: p1>c1, a2: p2>c1, p3>c2, a3: p4>c3)' do
  5 + before do
  6 + @c1 = Category.create!
  7 + @c2 = Category.create!
  8 + @c3 = Category.create!
  9 + @a1 = Author.create!
  10 + @a2 = Author.create!
  11 + @a3 = Author.create!
  12 + @p1 = @a1.posts.create! :category => @c1
  13 + @p2 = @a2.posts.create! :category => @c1
  14 + @p3 = @a2.posts.create! :category => @c2
  15 + @p4 = @a3.posts.create! :category => @c3
  16 + @a1.reload
  17 + @a2.reload
  18 + end
  19 +
  20 + it "a1.posts should == [p1]" do
  21 + @a1.posts.should == [@p1]
  22 + end
  23 +
  24 + it "a1.categories should == [c1]" do
  25 + @a1.categories.should == [@c1]
  26 + end
  27 +
  28 + it "a2.posts should == [p2, p3]" do
  29 + @a2.posts.should == [@p2, @p3]
  30 + end
  31 +
  32 + it "a2.categories should == [c1, c2]" do
  33 + @a2.categories.should == [@c1, @c2]
  34 + end
  35 +
  36 + describe "u1 comments on p2" do
  37 + before do
  38 + @u1 = User.create!
  39 + @comment = @p2.comments.create! :user => @u1
  40 + end
  41 +
  42 + it "u1.comments should == [comment]" do
  43 + @u1.comments.should == [@comment]
  44 + end
  45 +
  46 + it "a1.commenters should be empty" do
  47 + @a1.commenters.should be_empty
  48 + end
  49 +
  50 + it "a2.commenters should == [u1]" do
  51 + @a2.commenters.should == [@u1]
  52 + end
  53 +
  54 + it "u1.commented_posts should == [p2]" do
  55 + @u1.commented_posts.should == [@p2]
  56 + end
  57 +
  58 + it "u1.commented_posts.find_inflamatory(:all) should be empty" do
  59 + @u1.commented_posts.find_inflamatory(:all).should be_empty
  60 + end
  61 +
  62 + if ActiveRecord::Base.respond_to?(:named_scope)
  63 + it "u1.commented_posts.inflamatory should be empty" do
  64 + @u1.commented_posts.inflamatory.should be_empty
  65 + end
  66 + end
  67 +
  68 + it "u1.commented_authors should == [a2]" do
  69 + @u1.commented_authors.should == [@a2]
  70 + end
  71 +
  72 + it "u1.posts_of_interest should == [p1, p2, p3]" do
  73 + @u1.posts_of_interest.should == [@p1, @p2, @p3]
  74 + end
  75 +
  76 + it "u1.categories_of_interest should == [c1, c2]" do
  77 + @u1.categories_of_interest.should == [@c1, @c2]
  78 + end
  79 +
  80 + describe "when p2 is inflamatory" do
  81 + before do
  82 + @p2.toggle!(:inflamatory)
  83 + end
  84 +
  85 + it "p2 should be inflamatory" do
  86 + @p2.should be_inflamatory
  87 + end
  88 +
  89 + it "u1.commented_posts.find_inflamatory(:all) should == [p2]" do
  90 + # uniq ids is here (and next spec) because eager loading changed behaviour 2.0.2 => edge
  91 + @u1.commented_posts.find_inflamatory(:all).collect(&:id).uniq.should == [@p2.id]
  92 + end
  93 +
  94 + it "u1.posts_of_interest.find_inflamatory(:all).uniq should == [p2]" do
  95 + @u1.posts_of_interest.find_inflamatory(:all).collect(&:id).uniq.should == [@p2.id]
  96 + end
  97 +
  98 + if ActiveRecord::Base.respond_to?(:named_scope)
  99 + it "u1.commented_posts.inflamatory should == [p2]" do
  100 + @u1.commented_posts.inflamatory.should == [@p2]
  101 + end
  102 +
  103 + it "u1.posts_of_interest.inflamatory should == [p2]" do
  104 + @u1.posts_of_interest.inflamatory.should == [@p2]
  105 + end
  106 + end
  107 + end
  108 + end
  109 +end
... ...
vendor/plugins/nested_has_many_through/spec/spec_helper.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +# This file is copied to ~/spec when you run 'ruby script/generate rspec'
  2 +# from the project root directory.
  3 +ENV["RAILS_ENV"] ||= "test"
  4 +require File.expand_path(File.join(File.dirname(__FILE__), "../../../../config/environment"))
  5 +require 'spec/rails'
  6 +
  7 +Spec::Runner.configure do |config|
  8 + config.use_transactional_fixtures = true
  9 + config.use_instantiated_fixtures = false
  10 + config.fixture_path = RAILS_ROOT + '/spec/fixtures'
  11 +
  12 + # You can declare fixtures for each behaviour like this:
  13 + # describe "...." do
  14 + # fixtures :table_a, :table_b
  15 + #
  16 + # Alternatively, if you prefer to declare them only once, you can
  17 + # do so here, like so ...
  18 + #
  19 + # config.global_fixtures = :table_a, :table_b
  20 + #
  21 + # If you declare global fixtures, be aware that they will be declared
  22 + # for all of your examples, even those that don't use them.
  23 +end
0 24 \ No newline at end of file
... ...