Preparing to upgrade has_many_polymorphs plugin...

git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@682 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
lukemelia 2007-12-11 04:33:31 +00:00
parent 04f1bd87e7
commit a6501c3d83
57 changed files with 0 additions and 2590 deletions

View file

@ -1,184 +0,0 @@
Academic Free License (AFL) v. 3.0
This Academic Free License (the "License") applies to any original work
of authorship (the "Original Work") whose owner (the "Licensor") has
placed the following licensing notice adjacent to the copyright notice
for the Original Work:
Licensed under the Academic Free License version 3.0
1) Grant of Copyright License. Licensor grants You a worldwide,
royalty-free, non-exclusive, sublicensable license, for the duration of
the copyright, to do the following:
a) to reproduce the Original Work in copies, either alone or as part of
a collective work;
b) to translate, adapt, alter, transform, modify, or arrange the
Original Work, thereby creating derivative works ("Derivative Works")
based upon the Original Work;
c) to distribute or communicate copies of the Original Work and
Derivative Works to the public, under any license of your choice that
does not contradict the terms and conditions, including Licensor's
reserved rights and remedies, in this Academic Free License;
d) to perform the Original Work publicly; and
e) to display the Original Work publicly.
2) Grant of Patent License. Licensor grants You a worldwide,
royalty-free, non-exclusive, sublicensable license, under patent claims
owned or controlled by the Licensor that are embodied in the Original
Work as furnished by the Licensor, for the duration of the patents, to
make, use, sell, offer for sale, have made, and import the Original Work
and Derivative Works.
3) Grant of Source Code License. The term "Source Code" means the
preferred form of the Original Work for making modifications to it and
all available documentation describing how to modify the Original Work.
Licensor agrees to provide a machine-readable copy of the Source Code of
the Original Work along with each copy of the Original Work that
Licensor distributes. Licensor reserves the right to satisfy this
obligation by placing a machine-readable copy of the Source Code in an
information repository reasonably calculated to permit inexpensive and
convenient access by You for as long as Licensor continues to distribute
the Original Work.
4) Exclusions From License Grant. Neither the names of Licensor, nor the
names of any contributors to the Original Work, nor any of their
trademarks or service marks, may be used to endorse or promote products
derived from this Original Work without express prior permission of the
Licensor. Except as expressly stated herein, nothing in this License
grants any license to Licensor's trademarks, copyrights, patents, trade
secrets or any other intellectual property. No patent license is granted
to make, use, sell, offer for sale, have made, or import embodiments of
any patent claims other than the licensed claims defined in Section 2.
No license is granted to the trademarks of Licensor even if such marks
are included in the Original Work. Nothing in this License shall be
interpreted to prohibit Licensor from licensing under terms different
from this License any Original Work that Licensor otherwise would have a
right to license.
5) External Deployment. The term "External Deployment" means the use,
distribution, or communication of the Original Work or Derivative Works
in any way such that the Original Work or Derivative Works may be used
by anyone other than You, whether those works are distributed or
communicated to those persons or made available as an application
intended for use over a network. As an express condition for the grants
of license hereunder, You must treat any External Deployment by You of
the Original Work or a Derivative Work as a distribution under section
1(c).
6) Attribution Rights. You must retain, in the Source Code of any
Derivative Works that You create, all copyright, patent, or trademark
notices from the Source Code of the Original Work, as well as any
notices of licensing and any descriptive text identified therein as an
"Attribution Notice." You must cause the Source Code for any Derivative
Works that You create to carry a prominent Attribution Notice reasonably
calculated to inform recipients that You have modified the Original
Work.
7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants
that the copyright in and to the Original Work and the patent rights
granted herein by Licensor are owned by the Licensor or are sublicensed
to You under the terms of this License with the permission of the
contributor(s) of those copyrights and patent rights. Except as
expressly stated in the immediately preceding sentence, the Original
Work is provided under this License on an "AS IS" BASIS and WITHOUT
WARRANTY, either express or implied, including, without limitation, the
warranties of non-infringement, merchantability or fitness for a
particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL
WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential
part of this License. No license to the Original Work is granted by this
License except under this disclaimer.
8) Limitation of Liability. Under no circumstances and under no legal
theory, whether in tort (including negligence), contract, or otherwise,
shall the Licensor be liable to anyone for any indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or the use of the Original Work including,
without limitation, damages for loss of goodwill, work stoppage,
computer failure or malfunction, or any and all other commercial damages
or losses. This limitation of liability shall not apply to the extent
applicable law prohibits such limitation.
9) Acceptance and Termination. If, at any time, You expressly assented
to this License, that assent indicates your clear and irrevocable
acceptance of this License and all of its terms and conditions. If You
distribute or communicate copies of the Original Work or a Derivative
Work, You must make a reasonable effort under the circumstances to
obtain the express assent of recipients to the terms of this License.
This License conditions your rights to undertake the activities listed
in Section 1, including your right to create Derivative Works based upon
the Original Work, and doing so without honoring these terms and
conditions is prohibited by copyright law and international treaty.
Nothing in this License is intended to affect copyright exceptions and
limitations (including "fair use" or "fair dealing"). This License shall
terminate immediately and You may no longer exercise any of the rights
granted to You by this License upon your failure to honor the conditions
in Section 1(c).
10) Termination for Patent Action. This License shall terminate
automatically and You may no longer exercise any of the rights granted
to You by this License as of the date You commence an action, including
a cross-claim or counterclaim, against Licensor or any licensee alleging
that the Original Work infringes a patent. This termination provision
shall not apply for an action alleging patent infringement by
combinations of the Original Work with other software or hardware.
11) Jurisdiction, Venue and Governing Law. Any action or suit relating
to this License may be brought only in the courts of a jurisdiction
wherein the Licensor resides or in which Licensor conducts its primary
business, and under the laws of that jurisdiction excluding its
conflict-of-law provisions. The application of the United Nations
Convention on Contracts for the International Sale of Goods is expressly
excluded. Any use of the Original Work outside the scope of this License
or after its termination shall be subject to the requirements and
penalties of copyright or patent law in the appropriate jurisdiction.
This section shall survive the termination of this License.
12) Attorneys' Fees. In any action to enforce the terms of this License
or seeking damages relating thereto, the prevailing party shall be
entitled to recover its costs and expenses, including, without
limitation, reasonable attorneys' fees and costs incurred in connection
with such action, including any appeal of such action. This section
shall survive the termination of this License.
13) Miscellaneous. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable.
14) Definition of "You" in This License. "You" throughout this License,
whether in upper or lower case, means an individual or a legal entity
exercising rights under, and complying with all of the terms of, this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with you. For purposes of this
definition, "control" means (i) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
15) Right to Use. You may use the Original Work in all ways not
otherwise restricted or conditioned by this License or by law, and
Licensor promises not to interfere with or be responsible for such uses
by You.
16) Modification of This License. This License is Copyright (c) 2005
Lawrence Rosen. Permission is granted to copy, distribute, or
communicate this License without modification. Nothing in this License
permits You to modify this License as applied to the Original Work or to
Derivative Works. However, You may modify the text of this License and
copy, distribute or communicate your modified version (the "Modified
License") and apply it to other original works of authorship subject to
the following conditions: (i) You may not indicate in any way that your
Modified License is the "Academic Free License" or "AFL" and you may not
use those names in the name of your Modified License; (ii) You must
replace the notice specified in the first paragraph above with the
notice "Licensed under <insert your license name here>" or with a notice
of your own that is not confusingly similar to the notice in this
License; and (iii) You may not claim that your original works are open
source software unless your Modified License has been approved by Open
Source Initiative (OSI) and You comply with its license review and
certification process.

View file

@ -1,43 +0,0 @@
LICENSE
Manifest
README
Rakefile
init.rb
lib/has_many_polymorphs/association.rb
lib/has_many_polymorphs/autoload.rb
lib/has_many_polymorphs/base.rb
lib/has_many_polymorphs/class_methods.rb
lib/has_many_polymorphs/configuration.rb
lib/has_many_polymorphs/debugging_tools.rb
lib/has_many_polymorphs/dependencies.rb
lib/has_many_polymorphs/rake_task_redefine_task.rb
lib/has_many_polymorphs/reflection.rb
lib/has_many_polymorphs/support_methods.rb
lib/has_many_polymorphs.rb
test/fixtures/aquatic/fish.yml
test/fixtures/aquatic/little_whale_pupils.yml
test/fixtures/aquatic/whales.yml
test/fixtures/bow_wows.yml
test/fixtures/cats.yml
test/fixtures/eaters_foodstuffs.yml
test/fixtures/frogs.yml
test/fixtures/keep_your_enemies_close.yml
test/fixtures/petfoods.yml
test/fixtures/wild_boars.yml
test/models/aquatic/fish.rb
test/models/aquatic/pupils_whale.rb
test/models/aquatic/whale.rb
test/models/beautiful_fight_relationship.rb
test/models/cat.rb
test/models/dog.rb
test/models/eaters_foodstuff.rb
test/models/frog.rb
test/models/kitten.rb
test/models/petfood.rb
test/models/tabby.rb
test/models/wild_boar.rb
test/modules/extension_module.rb
test/modules/other_extension_module.rb
test/schema.rb
test/test_helper.rb
test/unit/polymorph_test.rb

View file

@ -1,49 +0,0 @@
Self-referential, polymorphic has_many :through helper for ActiveRecord.
Copyright 2007 Cloudburst, LLC (see the LICENSE file)
Documentation:
http://blog.evanweaver.com/pages/has_many_polymorphs
Changelog:
27.3. use new :source and :source_type options in 1.2.3 (David Lemstra); fix pluralization bug; add some tests; experimental tagging generator
27.2. deprecate has_many_polymorphs_cache_classes= option, because it doesn't really work. use config.cache_classes= instead to cache all reloadable items
27.1. dispatcher.to_prepare didn't fire in the console; now using a config.after_initialize wrapper instead
27. dependency injection framework elimates having to care about load order
26. make the logger act sane for the gem version
25.2. allow :skip_duplicates on double relationships
25.1. renamed :ignore_duplicates to :skip_duplicates to better express its non-passive behavior, made sure not to load target set on push unless necessary
25. activerecord compatibility branch becomes trunk. extra options now supported for double polymorphism; conditions nulled-out and propogated to child relationships; more tests; new :ignore_duplicates option on macro can be set to false if you want << to try to push duplicate associations
24.1. code split into multiple files. tests added for pluralization check. rails 1.1.6 no longer officially supported.
24. unlimited mixed class association extensions for both single and double targets and joins
23. gem version
22. api change; prefix on methods is now singular when using :rename_individual_collections
21. add configuration option to cache polymorphic classes in development mode
20. collection methods (push, delete, clear) now on individual collections
19.2. disjoint collection sides bugfix, don't raise on new records
19.1. double classify bugfix
19. large changes to properly support double polymorphism
18.2. bugfix to make sure the type gets checked on doubly polymorphic parents
18.1. bugfix for sqlite3 child attribute retrieval
18. bugfix for instantiating attributes of namespaced models
17.1. bugfix for double polymorphic relationships
17. double polymorphic relationships (includes new API method)
16. namespaced model support
15. bugfix for postgres and mysql under 1.1.6; refactored tests (thanks hildofur); properly handles legacy table names set with set_table_name()
14. sti support added (use the child class names, not the base class)
13. bug regarding table names with underscores in SQL query fixed
12.1. license change
12. file_column bug fixed
11. tests written; after_find and after_initialize now correctly called
10. bugfix
9. rollback
8. SQL performance enhancements added
7. rewrote singletons as full-fledged proxy class so that marshalling works (e.g. in the session)
6. caching added
5. fixed dependency reloading problem in development mode
4. license change
3. added :dependent support on the join table
1-2. no changelog

View file

@ -1,59 +0,0 @@
require 'rubygems'
require 'rake'
require 'lib/has_many_polymorphs/rake_task_redefine_task'
NAME = "has_many_polymorphs"
begin
require 'rake/clean'
gem 'echoe', '>= 1.1'
require 'echoe'
require 'fileutils'
AUTHOR = "Evan Weaver"
EMAIL = "evan at cloudbur dot st"
DESCRIPTION = "Self-referential, polymorphic has_many :through helper for ActiveRecord."
CHANGES = `cat README`[/^([\d\.]+\. .*)/, 1]
RUBYFORGE_NAME = "polymorphs"
GEM_NAME = "has_many_polymorphs"
HOMEPATH = "http://blog.evanweaver.com"
RELEASE_TYPES = ["gem"]
REV = nil
VERS = `cat README`[/^([\d\.]+)\. /, 1]
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
RDOC_OPTS = ['--quiet', '--title', "has_many_polymorphs documentation",
"--opname", "index.html",
"--line-numbers",
"--main", "README",
"--inline-source"]
include FileUtils
echoe = Echoe.new(GEM_NAME, VERS) do |p|
p.author = AUTHOR
p.rubyforge_name = RUBYFORGE_NAME
p.name = NAME
p.description = DESCRIPTION
p.changes = CHANGES
p.email = EMAIL
p.summary = DESCRIPTION
p.url = HOMEPATH
p.need_tar = false
p.need_tar_gz = true
p.test_globs = ["*_test.rb"]
p.clean_globs = CLEAN
end
rescue LoadError => boom
puts "You are missing a dependency required for meta-operations on this gem."
puts "#{boom.to_s.capitalize}."
desc 'Run the default tasks'
task :default => :test
end
desc 'Run the test suite.'
Rake::Task.redefine_task("test") do
puts "Notice; tests must be run from within a functioning Rails environment."
system "ruby -Ibin:lib:test test/unit/polymorph_test.rb #{ENV['METHOD'] ? "--name=#{ENV['METHOD']}" : ""}"
end

View file

@ -1,12 +0,0 @@
Thank you for installing has_many_polymorphs!
There is an experimental tagging system generator in place.
./script/generator tagging TaggableModel1 TaggableModel2 [..]
You can use the flags --skip-migration and/or --self-referential.
Tests will be generated, but will not work unless you have at
least 2 fixture entries for each taggable model. Their ids must
be 1 and 2.

View file

@ -1,78 +0,0 @@
require 'ruby-debug'
Debugger.start
class TaggingGenerator < Rails::Generator::NamedBase
default_options :skip_migration => false
default_options :self_referential => false
attr_reader :parent_association_name
attr_reader :taggable_models
def initialize(runtime_args, runtime_options = {})
@parent_association_name = (runtime_args.include?("--self-referential") ? "tagger" : "tag")
@taggable_models = runtime_args.reject{|opt| opt =~ /^--/}.map do |taggable|
":" + taggable.underscore.pluralize
end
@taggable_models += [":tags"] if runtime_args.include?("--self-referential")
@taggable_models.uniq!
hacks
runtime_args.unshift("placeholder")
super
end
def hacks
# add the extension require in environment.rb
phrase = "require 'tagging_extensions'"
filename = "#{RAILS_ROOT}/config/environment.rb"
unless (open(filename) do |file|
file.grep(/#{Regexp.escape phrase}/).any?
end)
open(filename, 'a+') do |file|
file.puts "\n" + phrase + "\n"
end
end
end
def manifest
record do |m|
m.class_collisions class_path, class_name, "#{class_name}Test"
m.directory File.join('app/models', class_path)
m.directory File.join('test/unit', class_path)
m.directory File.join('test/fixtures', class_path)
m.directory File.join('test/fixtures', class_path)
m.directory File.join('lib')
m.template 'tag.rb', File.join('app/models', class_path, "tag.rb")
m.template 'tag_test.rb', File.join('test/unit', class_path, "tag_test.rb")
m.template 'tags.yml', File.join('test/fixtures', class_path, "tags.yml")
m.template 'tagging.rb', File.join('app/models', class_path, "tagging.rb")
m.template 'tagging_test.rb', File.join('test/unit', class_path, "tagging_test.rb")
m.template 'taggings.yml', File.join('test/fixtures', class_path, "taggings.yml")
m.template 'tagging_extensions.rb', File.join('lib', 'tagging_extensions.rb')
unless options[:skip_migration]
m.migration_template 'migration.rb', 'db/migrate',
:migration_file_name => "create_tags_and_taggings"
end
end
end
protected
def banner
"Usage: #{$0} generate tagging [TaggableModelA TaggableModelB ...]"
end
def add_options!(opt)
opt.separator ''
opt.separator 'Options:'
opt.on("--skip-migration",
"Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
opt.on("--self-referential",
"Allow tags to tag themselves.") { |v| options[:self_referential] = v }
end
end

View file

@ -1,23 +0,0 @@
class CreateTagsAndTaggings < ActiveRecord::Migration
def self.up
create_table :tags do |t|
t.column :name, :string, :null => false
end
add_index :tags, :name, :unique => true
create_table :taggings do |t|
t.column :<%= parent_association_name -%>_id, :integer, :null => false
t.column :taggable_id, :integer, :null => false
t.column :taggable_type, :string, :null => false
# t.column :position, :integer
end
add_index :taggings, [:<%= parent_association_name -%>_id, :taggable_id, :taggable_type], :unique => true
end
def self.down
drop_table :tags
drop_table :taggings
end
end

View file

@ -1,35 +0,0 @@
class Tag < ActiveRecord::Base
DELIMITER = " " # how to separate tags in strings (you may
# also need to change the validates_format_of parameters
# if you update this)
# if speed becomes an issue, you could remove these validations
# and rescue the AR index errors instead
validates_presence_of :name
validates_uniqueness_of :name, :case_sensitive => false
validates_format_of :name, :with => /^[a-zA-Z0-9\_\-]+$/,
:message => "can not contain special characters"
has_many_polymorphs :taggables,
:from => [<%= taggable_models.join(", ") %>],
:through => :taggings,
:dependent => :destroy,
<% if options[:self_referential] -%> :as => :<%= parent_association_name -%>,
<% end -%>
:skip_duplicates => false,
:parent_extend => proc { # XXX this isn't right
def to_s
self.map(&:name).sort.join(Tag::DELIMITER)
end
}
def before_create
# if you allow editable tag names, you might want before_save instead
self.name = name.downcase.strip.squeeze(" ")
end
class Error < StandardError
end
end

View file

@ -1,10 +0,0 @@
require File.dirname(__FILE__) + '/../test_helper'
class TagTest < Test::Unit::TestCase
fixtures :tags, :taggings, :recipes, :posts
def test_to_s
assert_equal "delicious sexy", Recipe.find(2).tags.to_s
end
end

View file

@ -1,15 +0,0 @@
class Tagging < ActiveRecord::Base
belongs_to :<%= parent_association_name -%><%= ", :foreign_key => \"#{parent_association_name}_id\", :class_name => \"Tag\"" if options[:self_referential] %>
belongs_to :taggable, :polymorphic => true
# if you want acts_as_list, you will have to manage the tagging positions
# manually, by created decorated join records
# acts_as_list :scope => :taggable
def before_destroy
# if all the taggings for a particular <%= parent_association_name -%> are deleted, we want to
# delete the <%= parent_association_name -%> too
<%= parent_association_name -%>.destroy_without_callbacks if <%= parent_association_name -%>.taggings.count == 1
end
end

View file

@ -1,88 +0,0 @@
class ActiveRecord::Base
# the alternative to these taggable?() checks is to explicitly include a
# TaggingMethods module (which you would create) in each taggable model
def tag_with list
# completely replace the existing tag set
taggable?(true)
list = tag_cast_to_string(list)
Tag.transaction do # transactions may not be ideal for you here
current = <%= parent_association_name -%>s.map(&:name)
_add_tags(list - current)
_remove_tags(current - list)
end
self
end
alias :<%= parent_association_name -%>s= :tag_with
# need to avoid name conflicts with the built-in ActiveRecord association
# methods, thus the underscores
def _add_tags incoming
taggable?(true)
tag_cast_to_string(incoming).each do |tag_name|
begin
tag = Tag.find_or_create_by_name(tag_name)
raise Tag::Error, "tag could not be saved: #{tag_name}" if tag.new_record?
tag.taggables << self
rescue ActiveRecord::StatementInvalid => e
raise unless e.to_s =~ /duplicate/i
end
end
end
def _remove_tags outgoing
taggable?(true)
outgoing = tag_cast_to_string(outgoing)
<% if options[:self_referential] %>
# because of http://dev.rubyonrails.org/ticket/6466
taggings.destroy(taggings.find(:all, :include => :<%= parent_association_name -%>).select do |tagging|
outgoing.include? tagging.<%= parent_association_name -%>.name
end)
<% else -%>
<%= parent_association_name -%>s.delete(<%= parent_association_name -%>s.select do |tag|
outgoing.include? tag.name
end)
<% end -%>
end
def tag_list
taggable?(true)
<%= parent_association_name -%>s.reload
<%= parent_association_name -%>s.to_s
end
private
def tag_cast_to_string obj
case obj
when Array
obj.map! do |item|
case item
when /^\d+$/, Fixnum then Tag.find(item).name # this will be slow if you use ids a lot
when Tag then item.name
when String then item
else
raise "Invalid type"
end
end
when String
obj = obj.split(Tag::DELIMITER).map do |tag_name|
tag_name.strip.squeeze(" ")
end
else
raise "Invalid object of class #{obj.class} as tagging method parameter"
end.flatten.compact.map(&:downcase).uniq
end
def taggable?(should_raise = false)
unless flag = respond_to?(:<%= parent_association_name -%>s)
raise "#{self.class} is not a taggable model" if should_raise
end
flag
end
end

View file

@ -1,58 +0,0 @@
require File.dirname(__FILE__) + '/../test_helper'
class TaggingTest < Test::Unit::TestCase
fixtures :taggings, :tags, :posts, :recipes
def setup
@obj1 = Recipe.find(1)
@obj2 = Recipe.find(2)
@obj3 = Post.find(1)
@tag1 = Tag.find(1)
@tag2 = Tag.find(2)
@tagging1 = Tagging.find(1)
end
def test_tag_with
@obj2.tag_with "dark columbian"
assert_equal "columbian dark", @obj2.tag_list
end
<% if options[:self_referential] -%>
def test_self_referential_tag_with
@tag1.tag_with [1, 2]
assert @tag1.tags.include?(@tag1)
assert !@tag2.tags.include?(@tag1)
end
<% end -%>
def test__add_tags
@obj1._add_tags "porter longneck"
assert Tag.find_by_name("porter").taggables.include?(@obj1)
assert Tag.find_by_name("longneck").taggables.include?(@obj1)
assert_equal "delicious longneck porter", @obj1.tag_list
@obj1._add_tags [2]
assert_equal "delicious longneck porter sexy", @obj1.tag_list
end
def test__remove_tags
@obj2._remove_tags ["2", @tag1]
assert @obj2.tags.empty?
end
def test_tag_list
assert_equal "delicious sexy", @obj2.tag_list
end
def test_taggable
assert_raises(RuntimeError) do
@tagging1.send(:taggable?, true)
end
assert !@tagging1.send(:taggable?)
assert @obj3.send(:taggable?)
<% if options[:self_referential] -%>
assert @tag1.send(:taggable?)
<% end -%>
end
end

View file

@ -1,21 +0,0 @@
---
taggings_003:
<%= parent_association_name -%>_id: "2"
id: "3"
taggable_type: <%= taggable_models[0][1..-1].classify %>
taggable_id: "1"
taggings_004:
<%= parent_association_name -%>_id: "2"
id: "4"
taggable_type: <%= taggable_models[1][1..-1].classify %>
taggable_id: "2"
taggings_001:
<%= parent_association_name -%>_id: "1"
id: "1"
taggable_type: <%= taggable_models[1][1..-1].classify %>
taggable_id: "1"
taggings_002:
<%= parent_association_name -%>_id: "1"
id: "2"
taggable_type: <%= taggable_models[1][1..-1].classify %>
taggable_id: "2"

View file

@ -1,7 +0,0 @@
---
tags_001:
name: delicious
id: "1"
tags_002:
name: sexy
id: "2"

View file

@ -1,2 +0,0 @@
require 'has_many_polymorphs'

View file

@ -1 +0,0 @@
puts open("#{File.dirname(__FILE__)}/TAGGING_INSTALL").read

View file

@ -1,31 +0,0 @@
# self-referential, polymorphic has_many :through plugin
# http://blog.evanweaver.com/pages/has_many_polymorphs
# operates via magic dust, and courage
require 'active_record'
require 'has_many_polymorphs/reflection'
require 'has_many_polymorphs/association'
require 'has_many_polymorphs/class_methods'
require 'has_many_polymorphs/support_methods'
require 'has_many_polymorphs/configuration'
require 'has_many_polymorphs/base'
class ActiveRecord::Base
extend ActiveRecord::Associations::PolymorphicClassMethods
end
if ENV['RAILS_ENV'] =~ /development|test/ and ENV['USER'] == 'eweaver'
_logger_warn "has_many_polymorphs: debug mode enabled"
require 'has_many_polymorphs/debugging_tools'
end
if defined? Rails and RAILS_ENV and RAILS_ROOT
_logger_warn "has_many_polymorphs: Rails environment detected"
require 'has_many_polymorphs/dependencies'
require 'has_many_polymorphs/autoload'
end
_logger_debug "has_many_polymorphs: loaded ok"

View file

@ -1,153 +0,0 @@
module ActiveRecord
module Associations
class PolymorphicError < ActiveRecordError; end
class PolymorphicMethodNotSupportedError < ActiveRecordError; end
class PolymorphicAssociation < HasManyThroughAssociation
def <<(*records)
return if records.empty?
if @reflection.options[:skip_duplicates]
_logger_debug "Loading instances for polymorphic duplicate push check; use :skip_duplicates => false and perhaps a database constraint to avoid this possible performance issue"
load_target
end
@reflection.klass.transaction do
flatten_deeper(records).each do |record|
if @owner.new_record? or not record.respond_to?(:new_record?) or record.new_record?
raise PolymorphicError, "You can't associate unsaved records."
end
next if @reflection.options[:skip_duplicates] and @target.include? record
@owner.send(@reflection.through_reflection.name).proxy_target << @reflection.klass.create!(construct_join_attributes(record))
@target << record if loaded?
end
end
self
end
alias :push :<<
alias :concat :<<
def find(*args)
# super(*(args << returning(Base.send(:extract_options_from_args!, args)) {|opts| opts.delete :include})) # returning is slow
opts = Base.send(:extract_options_from_args!, args)
opts.delete :include
super(*(args + [opts]))
end
def construct_scope
_logger_warn "Warning; not all usage scenarios for polymorphic scopes are supported yet."
super
end
def delete(*records)
records = flatten_deeper(records)
records.reject! {|record| @target.delete(record) if record.new_record?}
return if records.empty?
@reflection.klass.transaction do
records.each do |record|
joins = @reflection.through_reflection.name
@owner.send(joins).delete(@owner.send(joins).select do |join|
join.send(@reflection.options[:polymorphic_key]) == record.id and
join.send(@reflection.options[:polymorphic_type_key]) == "#{record.class.base_class}"
end)
@target.delete(record)
end
end
# records
end
def clear(klass = nil)
load_target
return if @target.empty?
if klass
delete(@target.select {|r| r.is_a? klass })
else
@owner.send(@reflection.through_reflection.name).clear
@target.clear
end
[]
end
# undef :sum
# undef :create!
protected
def construct_quoted_owner_attributes(*args)
# no access to returning() here? why not?
type_key = @reflection.options[:foreign_type_key]
{@reflection.primary_key_name => @owner.id,
type_key=> (@owner.class.base_class.name if type_key)}
end
def construct_from
# build the FROM part of the query, in this case, the polymorphic join table
@reflection.klass.table_name
end
def construct_owner
# the table name for the owner object's class
@owner.class.table_name
end
def construct_owner_key
# the primary key field for the owner object
@owner.class.primary_key
end
def construct_select(custom_select = nil)
# build the select query
selected = custom_select || @reflection.options[:select]
end
def construct_joins(custom_joins = nil)
# build the string of default joins
"JOIN #{construct_owner} AS polymorphic_parent ON #{construct_from}.#{@reflection.options[:foreign_key]} = polymorphic_parent.#{construct_owner_key} " +
@reflection.options[:from].map do |plural|
klass = plural._as_class
"LEFT JOIN #{klass.table_name} ON #{construct_from}.#{@reflection.options[:polymorphic_key]} = #{klass.table_name}.#{klass.primary_key} AND #{construct_from}.#{@reflection.options[:polymorphic_type_key]} = #{@reflection.klass.quote_value(klass.base_class.name)}"
end.uniq.join(" ") + " #{custom_joins}"
end
def construct_conditions
# build the fully realized condition string
conditions = construct_quoted_owner_attributes.map do |field, value|
"#{construct_from}.#{field} = #{@reflection.klass.quote_value(value)}" if value
end
conditions << custom_conditions if custom_conditions
"(" + conditions.compact.join(') AND (') + ")"
end
def custom_conditions
# custom conditions... not as messy as has_many :through because our joins are a little smarter
if @reflection.options[:conditions]
"(" + interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) + ")"
end
end
alias :construct_owner_attributes :construct_quoted_owner_attributes
alias :conditions :custom_conditions # XXX possibly not necessary
alias :sql_conditions :custom_conditions # XXX ditto
# construct attributes for join for a particular record
def construct_join_attributes(record)
{@reflection.options[:polymorphic_key] => record.id,
@reflection.options[:polymorphic_type_key] => "#{record.class.base_class}",
@reflection.options[:foreign_key] => @owner.id}.merge(@reflection.options[:foreign_type_key] ?
{@reflection.options[:foreign_type_key] => "#{@owner.class.base_class}"} : {}) # for double-sided relationships
end
def build(attrs = nil)
raise PolymorphicMethodNotSupportedError, "You can't associate new records."
end
end
end
end

View file

@ -1,26 +0,0 @@
require 'initializer'
class Rails::Initializer
def after_initialize_with_autoload
after_initialize_without_autoload
_logger_debug "has_many_polymorphs: autoload hook invoked"
Dir["#{RAILS_ROOT}/app/models/**/*.rb"].each do |filename|
next if filename =~ /svn|CVS|bzr/
open filename do |file|
if file.grep(/has_many_polymorphs|acts_as_double_polymorphic_join/).any?
begin
model = File.basename(filename)[0..-4].classify
model.constantize
_logger_warn "has_many_polymorphs: preloaded parent model #{model}"
rescue Object => e
_logger_warn "error preloading #{model}: #{e.inspect}"
end
end
end
end
end
alias_method_chain :after_initialize, :autoload
end

View file

@ -1,47 +0,0 @@
module ActiveRecord
class Base
class << self
def instantiate_without_callbacks_with_polymorphic_checks(record)
if record['polymorphic_parent_class']
reflection = record['polymorphic_parent_class'].constantize.reflect_on_association(record['polymorphic_association_id'].to_sym)
# _logger_debug "Instantiating a polymorphic row for #{record['polymorphic_parent_class']}.reflect_on_association(:#{record['polymorphic_association_id']})"
# rewrite 'record' with the right column names
table_aliases = reflection.options[:table_aliases].dup
record = Hash[*table_aliases.keys.map {|key| [key, record[table_aliases[key]]] }.flatten]
# find the real child class
klass = record["#{self.table_name}.#{reflection.options[:polymorphic_type_key]}"].constantize
if sti_klass = record["#{klass.table_name}.#{klass.inheritance_column}"]
klass = klass.class_eval do compute_type(sti_klass) end # in case of namespaced STI models
end
# check that the join actually joined to something
unless (child_id = record["#{self.table_name}.#{reflection.options[:polymorphic_key]}"]) == record["#{klass.table_name}.#{klass.primary_key}"]
raise ActiveRecord::Associations::PolymorphicError,
"Referential integrity violation; child <#{klass.name}:#{child_id}> was not found for #{reflection.name.inspect}"
end
# eject the join keys
record = Hash[*record._select do |column, value|
column[/^#{klass.table_name}/]
end.map do |column, value|
[column[/\.(.*)/, 1], value]
end.flatten]
# allocate and assign values
returning(klass.allocate) do |obj|
obj.instance_variable_set("@attributes", record)
end
else
instantiate_without_callbacks_without_polymorphic_checks(record)
end
end
alias_method_chain :instantiate_without_callbacks, :polymorphic_checks # oh yeah
end
end
end

View file

@ -1,452 +0,0 @@
module ActiveRecord::Associations
module PolymorphicClassMethods
#################
# AR::Base association macros
RESERVED_KEYS = [:conditions, :order, :limit, :offset, :extend, :skip_duplicates,
:join_extend, :dependent, :rename_individual_collections]
def acts_as_double_polymorphic_join options={}, &extension
collections = options._select {|k,v| v.is_a? Array and k.to_s !~ /(#{RESERVED_KEYS.map(&:to_s).join('|')})$/}
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
options = options._select {|k,v| !collections[k]}
options[:extend] = (options[:extend] ? Array(options[:extend]) + [extension] : extension) if extension # inline the block
collection_option_keys = Hash[*collections.keys.map do |key|
[key, RESERVED_KEYS.map{|option| "#{key}_#{option}".to_sym}]
end._flatten_once]
collections.keys.each do |collection|
options.each do |key, value|
next if collection_option_keys.values.flatten.include? key
# shift the general options to the individual sides
collection_value = options[collection_key = "#{collection}_#{key}".to_sym]
case key
when :conditions
collection_value, value = sanitize_sql(collection_value), sanitize_sql(value)
options[collection_key] = (collection_value ? "(#{collection_value}) AND (#{value})" : value)
when :order
options[collection_key] = (collection_value ? "#{collection_value}, #{value}" : value)
when :extend, :join_extend
options[collection_key] = Array(collection_value) + Array(value)
when :limit, :offset, :dependent, :rename_individual_collections
options[collection_key] ||= value
else
raise PolymorphicError, "Unknown option key #{key.inspect}."
end
end
end
join_name = self.name.tableize.to_sym
collections.each do |association_id, children|
parent_hash_key = (collections.keys - [association_id]).first # parents are the entries in the _other_ children array
begin
parent_foreign_key = self.reflect_on_association(parent_hash_key._singularize).primary_key_name
rescue NoMethodError
raise PolymorphicError, "Couldn't find 'belongs_to' association for :#{parent_hash_key._singularize} in #{self.name}." unless parent_foreign_key
end
parents = collections[parent_hash_key]
conflicts = (children & parents) # set intersection
parents.each do |plural_parent_name|
parent_class = plural_parent_name._as_class
singular_reverse_association_id = parent_hash_key._singularize
parent_class.send(:has_many_polymorphs,
association_id, {:is_double => true,
:from => children,
:as => singular_reverse_association_id,
:through => join_name.to_sym,
:foreign_key => parent_foreign_key,
:foreign_type_key => parent_foreign_key.to_s.sub(/_id$/, '_type'),
:singular_reverse_association_id => singular_reverse_association_id,
:conflicts => conflicts}.merge(Hash[*options._select do |key, value|
collection_option_keys[association_id].include? key and !value.nil?
end.map do |key, value|
[key.to_s[association_id.to_s.length+1..-1].to_sym, value]
end._flatten_once])) # rename side-specific options to general names
if conflicts.include? plural_parent_name
# unify the alternate sides of the conflicting children
(conflicts).each do |method_name|
unless parent_class.instance_methods.include?(method_name)
parent_class.send(:define_method, method_name) do
(self.send("#{singular_reverse_association_id}_#{method_name}") +
self.send("#{association_id._singularize}_#{method_name}")).freeze
end
end
end
# unify the join model... join model is always renamed for doubles, unlike child associations
unless parent_class.instance_methods.include?(join_name)
parent_class.send(:define_method, join_name) do
(self.send("#{join_name}_as_#{singular_reverse_association_id}") +
self.send("#{join_name}_as_#{association_id._singularize}")).freeze
end
end
else
unless parent_class.instance_methods.include?(join_name)
parent_class.send(:alias_method, join_name, "#{join_name}_as_#{singular_reverse_association_id}")
end
end
end
end
end
def has_many_polymorphs (association_id, options = {}, &extension)
_logger_debug "has_many_polymorphs: INIT"
reflection = create_has_many_polymorphs_reflection(association_id, options, &extension)
# puts "Created reflection #{reflection.inspect}"
# configure_dependency_for_has_many(reflection)
collection_reader_method(reflection, PolymorphicAssociation)
end
def create_has_many_polymorphs_reflection(association_id, options, &extension)
options.assert_valid_keys(
:from,
:as,
:through,
:foreign_key,
:foreign_type_key,
:polymorphic_key, # same as :association_foreign_key
:polymorphic_type_key,
:dependent, # default :destroy, only affects the join table
:skip_duplicates, # default true, only affects the polymorphic collection
:ignore_duplicates, # deprecated
:is_double,
:rename_individual_collections,
:reverse_association_id, # not used
:singular_reverse_association_id,
:conflicts,
:extend,
:join_class_name,
:join_extend,
:parent_extend,
:table_aliases,
:select, # applies to the polymorphic relationship
:conditions, # applies to the polymorphic relationship, the children, and the join
# :include,
:order, # applies to the polymorphic relationship, the children, and the join
:group, # only applies to the polymorphic relationship and the children
:limit, # only applies to the polymorphic relationship and the children
:offset, # only applies to the polymorphic relationship
:parent_order,
:parent_group,
:parent_limit,
:parent_offset,
# :source,
:uniq, # XXX untested, only applies to the polymorphic relationship
# :finder_sql,
# :counter_sql,
# :before_add,
# :after_add,
# :before_remove,
# :after_remove
:dummy)
# validate against the most frequent configuration mistakes
verify_pluralization_of(association_id)
raise PolymorphicError, ":from option must be an array" unless options[:from].is_a? Array
options[:from].each{|plural| verify_pluralization_of(plural)}
options[:as] ||= self.table_name.singularize.to_sym
options[:conflicts] = Array(options[:conflicts])
options[:foreign_key] ||= "#{options[:as]}_id"
options[:association_foreign_key] =
options[:polymorphic_key] ||= "#{association_id._singularize}_id"
options[:polymorphic_type_key] ||= "#{association_id._singularize}_type"
if options.has_key? :ignore_duplicates
_logger_warn "DEPRECATION WARNING: please use :skip_duplicates instead of :ignore_duplicates"
options[:skip_duplicates] = options[:ignore_duplicates]
end
options[:skip_duplicates] = true unless options.has_key? :skip_duplicates
options[:dependent] = :destroy unless options.has_key? :dependent
options[:conditions] = sanitize_sql(options[:conditions])
# options[:finder_sql] ||= "(options[:polymorphic_key]
options[:through] ||= build_join_table_symbol((options[:as]._pluralize or self.table_name), association_id)
options[:join_class_name] ||= options[:through]._classify
options[:table_aliases] ||= build_table_aliases([options[:through]] + options[:from])
options[:select] ||= build_select(association_id, options[:table_aliases])
options[:through] = "#{options[:through]}_as_#{options[:singular_reverse_association_id]}" if options[:singular_reverse_association_id]
options[:through] = demodulate(options[:through]).to_sym
options[:extend] = spiked_create_extension_module(association_id, Array(options[:extend]) + Array(extension))
options[:join_extend] = spiked_create_extension_module(association_id, Array(options[:join_extend]), "Join")
options[:parent_extend] = spiked_create_extension_module(association_id, Array(options[:parent_extend]), "Parent")
# create the reflection object
returning(create_reflection(:has_many_polymorphs, association_id, options, self)) do |reflection|
if defined? Dependencies and RAILS_ENV == "development"
_logger_warn "DEPRECATION WARNING: \"has_many_polymorphs_cache_classes =\" no longer has any effect. Please use \"config.cache_classes = true\" in the regular environment config (not in the \"after_initialize\" block)." if ActiveRecord::Associations::ClassMethods.has_many_polymorphs_cache_classes
inject_dependencies(association_id, reflection) if Dependencies.mechanism == :load
end
# set up the other related associations
create_join_association(association_id, reflection)
create_has_many_through_associations_for_parent_to_children(association_id, reflection)
create_has_many_through_associations_for_children_to_parent(association_id, reflection)
end
end
private
##############
# table mapping for use at instantiation point
def build_table_aliases(from)
# for the targets
returning({}) do |aliases|
from.map(&:to_s).sort.map(&:to_sym).each_with_index do |plural, t_index|
table = plural._as_class.table_name
plural._as_class.columns.map(&:name).each_with_index do |field, f_index|
aliases["#{table}.#{field}"] = "t#{t_index}_r#{f_index}"
end
end
end
end
def build_select(association_id, aliases)
# cause instantiate has to know which reflection the results are coming from
(["\'#{self.name}\' AS polymorphic_parent_class",
"\'#{association_id}\' AS polymorphic_association_id"] +
aliases.map do |table, _alias|
"#{table} AS #{_alias}"
end.sort).join(", ")
end
##############
# model caching
def inject_dependencies(association_id, reflection)
_logger_debug "has_many_polymorphs: injecting dependencies"
requirements = [self, reflection.klass].map{|klass| [klass, klass.base_class]}.flatten.uniq
# below, a contributed fix for a bug in doubles that I can't reproduce
# parents = all_classes_for(association_id, reflection)
# if (parents - requirements).empty?
# requirements = (requirements - [parents[0]])
# parents = [parents[0]]
# else
# parents = (parents - requirements)
# end
#
# parents.each do |target_klass|
# Dependencies.inject_dependency(target_klass, *requirements)
# end
(all_classes_for(association_id, reflection) - requirements).each do |target_klass|
Dependencies.inject_dependency(target_klass, *requirements)
end
end
#################
# macro sub-builders
def create_join_association(association_id, reflection)
options = {:foreign_key => reflection.options[:foreign_key],
:dependent => reflection.options[:dependent],
:class_name => reflection.klass.name,
:extend => reflection.options[:join_extend],
# :limit => reflection.options[:limit],
# :offset => reflection.options[:offset],
:order => devolve(association_id, reflection, reflection.options[:order], reflection.klass),
:conditions => devolve(association_id, reflection, reflection.options[:conditions], reflection.klass)
}
if reflection.options[:foreign_type_key]
type_check = "#{reflection.options[:foreign_type_key]} = #{quote_value(self.base_class.name)}"
conjunction = options[:conditions] ? " AND " : nil
options[:conditions] = "#{options[:conditions]}#{conjunction}#{type_check}"
end
has_many(reflection.options[:through], options)
inject_before_save_into_join_table(association_id, reflection)
end
def inject_before_save_into_join_table(association_id, reflection)
sti_hook = "sti_class_rewrite"
rewrite_procedure = %[self.send(:#{association_id._singularize}_type=, self.#{association_id._singularize}_type.constantize.base_class.name)]
# XXX should be abstracted?
reflection.klass.class_eval %[
unless instance_methods.include? "before_save_with_#{sti_hook}"
if instance_methods.include? "before_save"
alias_method :before_save_without_#{sti_hook}, :before_save
def before_save_with_#{sti_hook}
before_save_without_#{sti_hook}
#{rewrite_procedure}
end
else
def before_save_with_#{sti_hook}
#{rewrite_procedure}
end
end
alias_method :before_save, :before_save_with_#{sti_hook}
end
]
end
def create_has_many_through_associations_for_children_to_parent(association_id, reflection)
child_pluralization_map(association_id, reflection).each do |plural, singular|
if singular == reflection.options[:as]
raise PolymorphicError, if reflection.options[:is_double]
"You can't give either of the sides in a double-polymorphic join the same name as any of the individual target classes."
else
"You can't have a self-referential polymorphic has_many :through without renaming the non-polymorphic foreign key in the join model."
end
end
parent = self
plural._as_class.instance_eval do
# this shouldn't be called at all during doubles; there is no way to traverse to a double polymorphic parent (XXX is that right?)
unless reflection.options[:is_double] or reflection.options[:conflicts].include? self.name.tableize.to_sym
# the join table
through = "#{reflection.options[:through]}#{'_as_child' if parent == self}".to_sym
has_many(through,
:as => association_id._singularize,
:class_name => reflection.klass.name,
:dependent => reflection.options[:dependent],
:extend => reflection.options[:join_extend],
# :limit => reflection.options[:limit],
# :offset => reflection.options[:offset],
:order => devolve(association_id, reflection, reflection.options[:order], reflection.klass),
:conditions => devolve(association_id, reflection, reflection.options[:conditions], reflection.klass)
)
# the association to the collection parents
association = "#{reflection.options[:as]._pluralize}#{"_of_#{association_id}" if reflection.options[:rename_individual_collections]}".to_sym
has_many(association,
:through => through,
:class_name => parent.name,
:source => reflection.options[:as],
:foreign_key => reflection.options[:foreign_key] ,
:extend => reflection.options[:parent_extend],
:order => reflection.options[:parent_order],
:offset => reflection.options[:parent_offset],
:limit => reflection.options[:parent_limit],
:group => reflection.options[:parent_group])
end
end
end
end
def create_has_many_through_associations_for_parent_to_children(association_id, reflection)
child_pluralization_map(association_id, reflection).each do |plural, singular|
#puts ":source => #{child}"
current_association = demodulate(child_association_map(association_id, reflection)[plural])
source = demodulate(singular)
if reflection.options[:conflicts].include? plural
# XXX check this
current_association = "#{association_id._singularize}_#{current_association}" if reflection.options[:conflicts].include? self.name.tableize.to_sym
source = "#{source}_as_#{association_id._singularize}".to_sym
end
# make push/delete accessible from the individual collections but still operate via the general collection
extension_module = self.class_eval %[
module #{self.name + current_association._classify + "PolymorphicChildAssociationExtension"}
def push *args; proxy_owner.send(:#{association_id}).send(:push, *args).select{|x| x.is_a? #{singular._classify}}; end
alias :<< :push
def delete *args; proxy_owner.send(:#{association_id}).send(:delete, *args); end
def clear; proxy_owner.send(:#{association_id}).send(:clear, #{singular._classify}); end
self
end]
has_many(current_association.to_sym,
:through => reflection.options[:through],
:source => association_id._singularize,
:source_type => plural._as_class.base_class.name,
:extend => (Array(extension_module) + reflection.options[:extend]),
:limit => reflection.options[:limit],
# :offset => reflection.options[:offset],
:order => devolve(association_id, reflection, reflection.options[:order], plural._as_class),
:conditions => devolve(association_id, reflection, reflection.options[:conditions], plural._as_class),
:group => devolve(association_id, reflection, reflection.options[:group], plural._as_class)
)
end
end
##############
# some support methods
def child_pluralization_map(association_id, reflection)
Hash[*reflection.options[:from].map do |plural|
[plural, plural._singularize]
end.flatten]
end
def child_association_map(association_id, reflection)
Hash[*reflection.options[:from].map do |plural|
[plural, "#{association_id._singularize.to_s + "_" if reflection.options[:rename_individual_collections]}#{plural}".to_sym]
end.flatten]
end
def demodulate(s)
s.to_s.gsub('/', '_').to_sym
end
def build_join_table_symbol(a, b)
[a.to_s, b.to_s].sort.join("_").to_sym
end
def all_classes_for(association_id, reflection)
klasses = [self, reflection.klass, *child_pluralization_map(association_id, reflection).keys.map(&:_as_class)]
klasses += klasses.map(&:base_class)
klasses.uniq
end
def devolve(association_id, reflection, string, klass)
return unless string
(all_classes_for(association_id, reflection) - # the join class must always be preserved
[klass, klass.base_class, reflection.klass, reflection.klass.base_class]).map do |klass|
klass.columns.map do |column|
[klass.table_name, column.name]
end.map do |table, column|
["#{table}.#{column}", "`#{table}`.#{column}", "#{table}.`#{column}`", "`#{table}`.`#{column}`"]
end
end.flatten.sort_by(&:size).reverse.each do |quoted_reference|
string.gsub!(quoted_reference, "NULL")
end
string
end
def verify_pluralization_of(sym)
sym = sym.to_s
singular = sym.singularize
plural = singular.pluralize
raise PolymorphicError, "Pluralization rules not set up correctly. You passed :#{sym}, which singularizes to :#{singular}, but that pluralizes to :#{plural}, which is different. Maybe you meant :#{plural} to begin with?" unless sym == plural
end
def spiked_create_extension_module(association_id, extensions, identifier = nil)
module_extensions = extensions.select{|e| e.is_a? Module}
proc_extensions = extensions.select{|e| e.is_a? Proc }
# support namespaced anonymous blocks as well as multiple procs
proc_extensions.each_with_index do |proc_extension, index|
module_name = "#{self.to_s}#{association_id._classify}Polymorphic#{identifier}AssociationExtension#{index}"
the_module = self.class_eval "module #{module_name}; self; end" # haha
the_module.class_eval &proc_extension
module_extensions << the_module
end
module_extensions
end
end
end

View file

@ -1,18 +0,0 @@
### deprecated
if defined? Rails::Configuration
class Rails::Configuration
def has_many_polymorphs_cache_classes= *args
::ActiveRecord::Associations::ClassMethods.has_many_polymorphs_cache_classes = *args
end
end
end
module ActiveRecord
module Associations
module ClassMethods
mattr_accessor :has_many_polymorphs_cache_classes
end
end
end

View file

@ -1,72 +0,0 @@
class << ActiveRecord::Base
COLLECTION_METHODS = [:belongs_to, :has_many, :has_and_belongs_to_many, :has_one,
:has_many_polymorphs, :acts_as_double_polymorphic_join].each do |method_name|
alias_method "original_#{method_name}".to_sym, method_name
undef_method method_name
end
unless defined? GENERATED_CODE_DIR
# automatic code generation for debugging
# you will get a folder "generated_models" in RAILS_ROOT containing valid Ruby files
# explaining all ActiveRecord relationships set up by the plugin, as well as listing the
# line in the plugin that made each particular macro call
GENERATED_CODE_DIR = "#{RAILS_ROOT}/generated_models"
begin
system "rm -rf #{GENERATED_CODE_DIR}"
Dir.mkdir GENERATED_CODE_DIR
rescue Errno::EACCES
_logger_warn "no permissions for generated code dir: #{GENERATED_CODE_DIR}"
end
if File.exist? GENERATED_CODE_DIR
alias :original_method_missing :method_missing
def method_missing(method_name, *args, &block)
if COLLECTION_METHODS.include? method_name.to_sym
Dir.chdir GENERATED_CODE_DIR do
filename = "#{demodulate(self.name.underscore)}.rb"
contents = File.open(filename).read rescue "\nclass #{self.name}\n\nend\n"
line = caller[1][/\:(\d+)\:/, 1]
contents[-5..-5] = "\n #{method_name} #{args[0..-2].inspect[1..-2]},\n #{args[-1].inspect[1..-2].gsub(" :", "\n :").gsub("=>", " => ")}\n#{ block ? " #{block.inspect.sub(/\@.*\//, '@')}\n" : ""} # called from line #{line}\n\n"
File.open(filename, "w") do |file|
file.puts contents
end
end
# doesn't handle blocks cause we can't introspect on code like that in Ruby without hackery and dependencies
self.send("original_#{method_name}", *args, &block)
else
self.send(:original_method_missing, method_name, *args, &block)
end
end
end
end
end
# and have a debugger enabled
case ENV['DEBUG']
when "ruby-debug"
require 'rubygems'
require 'ruby-debug'
Debugger.start
puts "Notice; ruby-debug enabled."
when "trace"
puts "Notice; method tracing enabled"
$debug_trace_indent = 0
set_trace_func (proc do |event, file, line, id, binding, classname|
if id.to_s =~ /instantiate/ #/IRB|Wirble|RubyLex|RubyToken|Logger|ConnectionAdapters|SQLite3|MonitorMixin|Benchmark|Inflector|Inflections/
if event == 'call'
puts (" " * $debug_trace_indent) + "#{event}ed #{classname}\##{id} from #{file.split('/').last}::#{line}"
$debug_trace_indent += 1
elsif event == 'return'
$debug_trace_indent -= 1 unless $debug_trace_indent == 0
puts (" " * $debug_trace_indent) + "#{event}ed #{classname}\##{id}"
end
end
end)
when "dependencies"
puts "Notice; dependency activity being logged"
(::Dependencies.log_activity = true) rescue nil
end

View file

@ -1,30 +0,0 @@
module Dependencies
#### dependency injection
mattr_accessor :injection_graph
self.injection_graph = Hash.new([])
def inject_dependency(target, *requirements)
target, requirements = target.to_s, requirements.map(&:to_s)
injection_graph[target] = ((injection_graph[target] + requirements).uniq - [target])
requirements.each {|requirement| mark_for_unload requirement }
# _logger_debug "has_many_polymorphs: injection graph: #{injection_graph.inspect}"
end
def new_constants_in_with_injection(*descs, &block) # chain
# _logger_debug "has_many_polymorphs: NEW: autoloaded constants: #{autoloaded_constants.inspect}; #{explicitly_unloadable_constants.inspect}" if (autoloaded_constants + explicitly_unloadable_constants).any?
returning(new_constants_in_without_injection(*descs, &block)) do |found|
# _logger_debug "has_many_polymorphs: new constants: #{found.inspect}" if found.any?
found.each do |constant|
injection_graph[constant].each do |requirement|
requirement.constantize
# _logger_debug "has_many_polymorphs: constantized #{requirement}"
end
end
end
end
alias_method_chain :new_constants_in, :injection
end

View file

@ -1,25 +0,0 @@
# http://www.bigbold.com/snippets/posts/show/2032
module Rake
module TaskManager
def redefine_task(task_class, args, &block)
task_name, deps = resolve_args(args)
task_name = task_class.scope_name(@scope, task_name)
deps = [deps] unless deps.respond_to?(:to_ary)
deps = deps.collect {|d| d.to_s }
task = @tasks[task_name.to_s] = task_class.new(task_name, self)
task.application = self
task.add_comment(@last_comment)
@last_comment = nil
task.enhance(deps, &block)
task
end
end
class Task
class << self
def redefine_task(args, &block)
Rake.application.redefine_task(self, args, &block)
end
end
end
end

View file

@ -1,41 +0,0 @@
module ActiveRecord
module Reflection
module ClassMethods
def create_reflection(macro, name, options, active_record)
case macro
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
reflection = AssociationReflection.new(macro, name, options, active_record)
when :composed_of
reflection = AggregateReflection.new(macro, name, options, active_record)
######
when :has_many_polymorphs
reflection = PolymorphicReflection.new(macro, name, options, active_record)
######
end
write_inheritable_hash :reflections, name => reflection
reflection
end
end
class PolymorphicError < ActiveRecordError
end
class PolymorphicReflection < AssociationReflection
def check_validity!
# nothing
end
# these are kind of shady but it lets us inherit more directly
def source_reflection
self
end
def class_name
@class_name ||= options[:join_class_name]
end
end
end
end

View file

@ -1,57 +0,0 @@
# hope these don't mess anyone up
class String
def _as_class
# classify expects self to be plural
self.classify.constantize
end
# def _as_base_class; _as_class.base_class; end
alias :_singularize :singularize
alias :_pluralize :pluralize
alias :_classify :classify
end
class Symbol
def _as_class; self.to_s._as_class; end
# def _as_base_class; self.to_s._as_base_class; end
def _singularize; self.to_s.singularize.to_sym; end
def _pluralize; self.to_s.pluralize.to_sym; end
def _classify; self.to_s.classify; end
end
class Array
def _flatten_once
self.inject([]){|r, el| r + Array(el)}
end
end
class Hash
def _select
Hash[*self.select do |key, value|
yield key, value
end._flatten_once]
end
end
class Object
def _metaclass; (class << self; self; end); end
def _logger_debug s
::ActiveRecord::Base.logger.debug(s) if ::ActiveRecord::Base.logger
end
def _logger_warn s
if ::ActiveRecord::Base.logger
::ActiveRecord::Base.logger.warn(s)
else
$stderr.puts("has_many_polymorphs: #{s}")
end
end
end
class ActiveRecord::Base
def _base_class_name
self.class.base_class.name.to_s
end
end

View file

@ -1,12 +0,0 @@
swimmy:
id: 1
name: Swimmy
speed: 10
created_at: "2007-02-01 12:00:00"
updated_at: "2007-02-04 10:00:00"
jaws:
id: 2
name: Jaws
speed: 20
created_at: "2007-02-02 12:00:00"
updated_at: "2007-02-03 10:00:00"

View file

@ -1,5 +0,0 @@
shamu:
id: 1
name: Shamu
created_at: "2007-03-01 12:00:00"
updated_at: "2007-03-04 10:00:00"

View file

@ -1,10 +0,0 @@
rover:
id: 1
name: Rover
created_at: "2007-01-01 12:00:00"
updated_at: "2007-01-04 10:00:00"
spot:
id: 2
name: Spot
created_at: "2007-01-02 12:00:00"
updated_at: "2007-01-03 10:00:00"

View file

@ -1,12 +0,0 @@
chloe:
id: 1
cat_type: Kitten
name: Chloe
created_at: "2007-04-01 12:00:00"
updated_at: "2007-04-04 10:00:00"
alice:
id: 2
cat_type: Kitten
name: Alice
created_at: "2007-04-02 12:00:00"
updated_at: "2007-04-03 10:00:00"

View file

@ -1,5 +0,0 @@
froggy:
id: 1
name: Froggy
created_at: "2007-05-01 12:00:00"
updated_at: "2007-05-04 10:00:00"

View file

@ -1,11 +0,0 @@
kibbles:
the_petfood_primary_key: 1
name: Kibbles
created_at: "2007-06-01 12:00:00"
updated_at: "2007-06-04 10:00:00"
bits:
the_petfood_primary_key: 2
name: Bits
created_at: "2007-06-02 12:00:00"
updated_at: "2007-06-03 10:00:00"

View file

@ -1,10 +0,0 @@
puma:
id: 1
name: Puma
created_at: "2007-07-01 12:00:00"
updated_at: "2007-07-04 10:00:00"
jacrazy:
id: 2
name: Jacrazy
created_at: "2007-07-02 12:00:00"
updated_at: "2007-07-03 10:00:00"

View file

@ -1,4 +0,0 @@
class Aquatic::Fish < ActiveRecord::Base
# attr_accessor :after_find_test, :after_initialize_test
end

View file

@ -1,7 +0,0 @@
class Aquatic::PupilsWhale < ActiveRecord::Base
set_table_name "little_whale_pupils"
belongs_to :whale, :class_name => "Aquatic::Whale", :foreign_key => "whale_id"
belongs_to :aquatic_pupil, :polymorphic => true
end

View file

@ -1,13 +0,0 @@
# see http://dev.rubyonrails.org/ticket/5935
module Aquatic; end
require 'aquatic/fish'
require 'aquatic/pupils_whale'
class Aquatic::Whale < ActiveRecord::Base
has_many_polymorphs(:aquatic_pupils, :from => [:dogs, :"aquatic/fish"],
:through => "aquatic/pupils_whales") do
def a_method
:correct_block_result
end
end
end

View file

@ -1,26 +0,0 @@
require 'extension_module'
class BeautifulFightRelationship < ActiveRecord::Base
set_table_name 'keep_your_enemies_close'
belongs_to :enemy, :polymorphic => true
belongs_to :protector, :polymorphic => true
# polymorphic relationships with column names different from the relationship name
# are not supported by Rails
acts_as_double_polymorphic_join :enemies => [:dogs, :kittens, :frogs],
:protectors => [:wild_boars, :kittens, :"aquatic/fish", :dogs],
:enemies_extend => [ExtensionModule, proc {}],
:protectors_extend => proc {
def a_method
:correct_proc_result
end
},
:join_extend => proc {
def a_method
:correct_join_result
end
}
end

View file

@ -1,9 +0,0 @@
class Canine < ActiveRecord::Base
self.abstract_class = true
def an_abstract_method
:correct_abstract_method_response
end
end

View file

@ -1,5 +0,0 @@
class Cat < ActiveRecord::Base
# STI base class
self.inheritance_column = 'cat_type'
end

View file

@ -1,18 +0,0 @@
require 'canine'
class Dog < Canine
attr_accessor :after_find_test, :after_initialize_test
set_table_name "bow_wows"
def after_find
@after_find_test = true
# puts "After find called on #{name}."
end
def after_initialize
@after_initialize_test = true
end
end

View file

@ -1,10 +0,0 @@
class EatersFoodstuff < ActiveRecord::Base
belongs_to :foodstuff, :class_name => "Petfood", :foreign_key => "foodstuff_id"
belongs_to :eater, :polymorphic => true
def before_save
self.some_attribute = 3
end
end

View file

@ -1,4 +0,0 @@
class Frog < ActiveRecord::Base
end

View file

@ -1,3 +0,0 @@
class Kitten < Cat
# has_many :eaters_parents, :dependent => true, :as => 'eater'
end

View file

@ -1,38 +0,0 @@
# see http://dev.rubyonrails.org/ticket/5935
require 'eaters_foodstuff'
require 'petfood'
require 'cat'
module Aquatic; end
require 'aquatic/fish'
require 'dog'
require 'wild_boar'
require 'kitten'
require 'tabby'
require 'extension_module'
require 'other_extension_module'
class Petfood < ActiveRecord::Base
set_primary_key 'the_petfood_primary_key'
has_many_polymorphs :eaters,
:from => [:dogs, :petfoods, :wild_boars, :kittens,
:tabbies, :"aquatic/fish"],
# :dependent => :destroy, :destroy is now the default
:rename_individual_collections => true,
:as => :foodstuff,
:foreign_key => "foodstuff_id",
:ignore_duplicates => false,
:conditions => "NULL IS NULL",
:order => "eaters_foodstuffs.updated_at ASC",
:parent_order => "the_petfood_primary_key DESC",
:extend => [ExtensionModule, OtherExtensionModule, proc {}],
:join_extend => proc {
def a_method
:correct_join_result
end
},
:parent_extend => proc {
def a_method
:correct_parent_proc_result
end
}
end

View file

@ -1,2 +0,0 @@
class Tabby < Cat
end

View file

@ -1,3 +0,0 @@
class WildBoar < ActiveRecord::Base
end

View file

@ -1,9 +0,0 @@
module ExtensionModule
def a_method
:correct_module_result
end
def self.a_method
:incorrect_module_result
end
end

View file

@ -1,9 +0,0 @@
module OtherExtensionModule
def another_method
:correct_other_module_result
end
def self.another_method
:incorrect_other_module_result
end
end

View file

@ -1,72 +0,0 @@
ActiveRecord::Schema.define(:version => 0) do
create_table :petfoods, :force => true, :primary_key => :the_petfood_primary_key do |t|
t.column :name, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :bow_wows, :force => true do |t|
t.column :name, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :cats, :force => true do |t|
t.column :name, :string
t.column :cat_type, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :frogs, :force => true do |t|
t.column :name, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :wild_boars, :force => true do |t|
t.column :name, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :eaters_foodstuffs, :force => true do |t|
t.column :foodstuff_id, :integer
t.column :eater_id, :integer
t.column :some_attribute, :integer, :default => 0
t.column :eater_type, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :fish, :force => true do |t|
t.column :name, :string
t.column :speed, :integer
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :whales, :force => true do |t|
t.column :name, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :little_whale_pupils, :force => true do |t|
t.column :whale_id, :integer
t.column :aquatic_pupil_id, :integer
t.column :aquatic_pupil_type, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
create_table :keep_your_enemies_close, :force => true do |t|
t.column :enemy_id, :integer
t.column :enemy_type, :string
t.column :protector_id, :integer
t.column :protector_type, :string
t.column :created_at, :datetime, :null => false
t.column :updated_at, :datetime, :null => false
end
end

View file

@ -1,34 +0,0 @@
begin
require 'rubygems'
require 'ruby-debug'
Debugger.start
rescue Object
end
# load the applicaiton's test helper
begin
require File.dirname(__FILE__) + '/../../../../test/test_helper'
rescue LoadError
require '~/projects/miscellaneous/cookbook/test/test_helper'
end
WORKING_DIR = File.dirname(__FILE__)
Inflector.inflections {|i| i.irregular 'fish', 'fish' }
# fixtures
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path = WORKING_DIR + "/fixtures")
# models
$LOAD_PATH.unshift(WORKING_DIR + "/models")
# extension modules
$LOAD_PATH.unshift(WORKING_DIR + "/modules")
class Test::Unit::TestCase
self.use_transactional_fixtures = (not ActiveRecord::Base.connection.is_a? ActiveRecord::ConnectionAdapters::MysqlAdapter)
self.use_instantiated_fixtures = false
end
# test schema
load(File.dirname(__FILE__) + "/schema.rb")

View file

@ -1,622 +0,0 @@
require File.dirname(__FILE__) + '/../test_helper'
class PolymorphTest < Test::Unit::TestCase
fixtures :cats, :bow_wows, :frogs, :wild_boars, :eaters_foodstuffs, :petfoods,
:"aquatic/fish", :"aquatic/whales", :"aquatic/little_whale_pupils",
:keep_your_enemies_close
require 'beautiful_fight_relationship'
def setup
@association_error = ActiveRecord::Associations::PolymorphicError
@kibbles = Petfood.find(1)
@bits = Petfood.find(2)
@shamu = Aquatic::Whale.find(1)
@swimmy = Aquatic::Fish.find(1)
@rover = Dog.find(1)
@spot = Dog.find(2)
@puma = WildBoar.find(1)
@chloe = Kitten.find(1)
@alice = Kitten.find(2)
@froggy = Frog.find(1)
@join_count = EatersFoodstuff.count
@l = @kibbles.eaters.size
@m = @bits.eaters.size
end
def test_all_relationship_validities
# q = []
# ObjectSpace.each_object(Class){|c| q << c if c.ancestors.include? ActiveRecord::Base }
# q.each{|c| puts "#{c.name}.reflect_on_all_associations.map &:check_validity! "}
Petfood.reflect_on_all_associations.map &:check_validity!
Tabby.reflect_on_all_associations.map &:check_validity!
Kitten.reflect_on_all_associations.map &:check_validity!
Dog.reflect_on_all_associations.map &:check_validity!
Aquatic::Fish.reflect_on_all_associations.map &:check_validity!
EatersFoodstuff.reflect_on_all_associations.map &:check_validity!
WildBoar.reflect_on_all_associations.map &:check_validity!
Frog.reflect_on_all_associations.map &:check_validity!
Aquatic::Whale.reflect_on_all_associations.map &:check_validity!
Cat.reflect_on_all_associations.map &:check_validity!
Aquatic::PupilsWhale.reflect_on_all_associations.map &:check_validity!
BeautifulFightRelationship.reflect_on_all_associations.map &:check_validity!
end
def test_assignment
assert @kibbles.eaters.blank?
assert @kibbles.eaters.push(Cat.find_by_name('Chloe'))
assert_equal @l += 1, @kibbles.eaters.count
@kibbles.reload
assert_equal @l, @kibbles.eaters.count
end
def test_duplicate_assignment
# try to add a duplicate item when :ignore_duplicates is false
@kibbles.eaters.push(@alice)
assert @kibbles.eaters.include?(@alice)
@kibbles.eaters.push(@alice)
assert_equal @l + 2, @kibbles.eaters.count
assert_equal @join_count + 2, EatersFoodstuff.count
end
def test_create_and_push
assert @kibbles.eaters.push(@spot)
assert_equal @l += 1, @kibbles.eaters.count
assert @kibbles.eaters << @rover
assert @kibbles.eaters << Kitten.create(:name => "Miranda")
assert_equal @l += 2, @kibbles.eaters.length
@kibbles.reload
assert_equal @l, @kibbles.eaters.length
# test that ids and new flags were set appropriately
assert_not_nil @kibbles.eaters[0].id
assert !@kibbles.eaters[1].new_record?
end
def test_reload
assert @kibbles.reload
assert @kibbles.eaters.reload
end
def test_add_join_record
assert_equal Kitten, @chloe.class
assert @join_record = EatersFoodstuff.new(:foodstuff_id => @bits.id, :eater_id => @chloe.id, :eater_type => @chloe.class.name )
assert @join_record.save!
assert @join_record.id
assert_equal @join_count + 1, EatersFoodstuff.count
# not reloaded
assert_equal @m, @bits.eaters.size
assert_equal @m + 1, @bits.eaters.count # SQL :)
# is the new association there?
assert @bits.eaters.reload
assert @bits.eaters.include?(@chloe)
end
# not supporting this, since has_many :through doesn't support it either
# def test_add_unsaved
# # add an unsaved item
# assert @bits.eaters << Kitten.new(:name => "Bridget")
# assert_nil Kitten.find_by_name("Bridget")
# assert_equal @m + 1, @bits.eaters.count
#
# assert @bits.save
# @bits.reload
# assert_equal @m + 1, @bits.eaters.count
#
# end
def test_self_reference
assert @kibbles.eaters << @bits
assert_equal @l += 1, @kibbles.eaters.count
assert @kibbles.eaters.include?(@bits)
@kibbles.reload
assert @kibbles.foodstuffs_of_eaters.blank?
@bits.reload
assert @bits.foodstuffs_of_eaters.include?(@kibbles)
assert_equal [@kibbles], @bits.foodstuffs_of_eaters
end
def test_remove
assert @kibbles.eaters << @chloe
@kibbles.reload
assert @kibbles.eaters.delete(@kibbles.eaters[0])
assert_equal @l, @kibbles.eaters.count
end
def test_destroy
assert @kibbles.eaters.push(@chloe)
@kibbles.reload
assert @kibbles.eaters.length > 0
assert @kibbles.eaters[0].destroy
@kibbles.reload
assert_equal @l, @kibbles.eaters.count
end
def test_clear
@kibbles.eaters << [@chloe, @spot, @rover]
@kibbles.reload
assert @kibbles.eaters.clear.blank?
assert @kibbles.eaters.blank?
@kibbles.reload
assert @kibbles.eaters.blank?
end
def test_individual_collections
assert @kibbles.eaters.push(@chloe)
# check if individual collections work
assert_equal @kibbles.eater_kittens.length, 1
assert @kibbles.eater_dogs
assert 1, @rover.eaters_foodstuffs.count
end
def test_individual_collections_push
assert_equal [@chloe], (@kibbles.eater_kittens << @chloe)
@kibbles.reload
assert @kibbles.eaters.include?(@chloe)
assert @kibbles.eater_kittens.include?(@chloe)
assert !@kibbles.eater_dogs.include?(@chloe)
end
def test_individual_collections_delete
@kibbles.eaters << [@chloe, @spot, @rover]
@kibbles.reload
assert_equal [@chloe], @kibbles.eater_kittens.delete(@chloe)
assert @kibbles.eater_kittens.empty?
@kibbles.eater_kittens.delete(@chloe) # what should this return?
@kibbles.reload
assert @kibbles.eater_kittens.empty?
assert @kibbles.eater_dogs.include?(@spot)
end
def test_individual_collections_clear
@kibbles.eaters << [@chloe, @spot, @rover]
@kibbles.reload
assert_equal [], @kibbles.eater_kittens.clear
assert @kibbles.eater_kittens.empty?
assert_equal 2, @kibbles.eaters.size
assert @kibbles.eater_kittens.empty?
assert_equal 2, @kibbles.eaters.size
assert !@kibbles.eater_kittens.include?(@chloe)
assert !@kibbles.eaters.include?(@chloe)
@kibbles.reload
assert @kibbles.eater_kittens.empty?
assert_equal 2, @kibbles.eaters.size
assert !@kibbles.eater_kittens.include?(@chloe)
assert !@kibbles.eaters.include?(@chloe)
end
def test_childrens_individual_collections
assert Cat.find_by_name('Chloe').eaters_foodstuffs
assert @kibbles.eaters_foodstuffs
end
def test_self_referential_join_tables
# check that the self-reference join tables go the right ways
assert_equal @l, @kibbles.eaters_foodstuffs.count
assert_equal @kibbles.eaters_foodstuffs.count, @kibbles.eaters_foodstuffs_as_child.count
end
def test_dependent
assert @kibbles.eaters << @chloe
@kibbles.reload
# delete ourself and see if :dependent was obeyed
dependent_rows = @kibbles.eaters_foodstuffs
assert_equal dependent_rows.length, @kibbles.eaters.count
@join_count = EatersFoodstuff.count
@kibbles.destroy
assert_equal @join_count - dependent_rows.length, EatersFoodstuff.count
assert_equal 0, EatersFoodstuff.find(:all, :conditions => ['foodstuff_id = ?', 1] ).length
end
def test_normal_callbacks
assert @rover.respond_to?(:after_initialize)
assert @rover.respond_to?(:after_find)
assert @rover.after_initialize_test
assert @rover.after_find_test
end
def test_model_callbacks_not_overridden_by_plugin_callbacks
assert 0, @bits.eaters.count
assert @bits.eaters.push(@rover)
@bits.save
@bits2 = Petfood.find_by_name("Bits")
@bits.reload
assert rover = @bits2.eaters.select { |x| x.name == "Rover" }[0]
assert rover.after_initialize_test
assert rover.after_find_test
end
def test_number_of_join_records
assert EatersFoodstuff.create(:foodstuff_id => 1, :eater_id => 1, :eater_type => "Cat")
@join_count = EatersFoodstuff.count
assert @join_count > 0
end
def test_number_of_regular_records
dogs = Dog.count
assert Dog.new(:name => "Auggie").save!
assert dogs + 1, Dog.count
end
def test_attributes_come_through_when_child_has_underscore_in_table_name
@join_record = EatersFoodstuff.new(:foodstuff_id => @bits.id, :eater_id => @puma.id, :eater_type => @puma.class.name)
@join_record.save!
@bits.eaters.reload
assert_equal "Puma", @puma.name
assert_equal "Puma", @bits.eaters.first.name
end
def test_before_save_on_join_table_is_not_clobbered_by_sti_base_class_fix
assert @kibbles.eaters << @chloe
assert_equal 3, @kibbles.eaters_foodstuffs.first.some_attribute
end
def test_creating_namespaced_relationship
assert @shamu.aquatic_pupils.empty?
@shamu.aquatic_pupils << @swimmy
assert_equal 1, @shamu.aquatic_pupils.length
@shamu.reload
assert_equal 1, @shamu.aquatic_pupils.length
end
def test_namespaced_polymorphic_collection
@shamu.aquatic_pupils << @swimmy
assert @shamu.aquatic_pupils.include?(@swimmy)
@shamu.reload
assert @shamu.aquatic_pupils.include?(@swimmy)
@shamu.aquatic_pupils << @spot
assert @shamu.dogs.include?(@spot)
assert @shamu.aquatic_pupils.include?(@swimmy)
assert_equal @swimmy, @shamu.aquatic_fish.first
assert_equal 10, @shamu.aquatic_fish.first.speed
end
def test_deleting_namespaced_relationship
@shamu.aquatic_pupils << @swimmy
@shamu.aquatic_pupils << @spot
@shamu.reload
@shamu.aquatic_pupils.delete @spot
assert !@shamu.dogs.include?(@spot)
assert !@shamu.aquatic_pupils.include?(@spot)
assert_equal 1, @shamu.aquatic_pupils.length
end
def test_unrenamed_parent_of_namespaced_child
@shamu.aquatic_pupils << @swimmy
assert_equal [@shamu], @swimmy.whales
end
def test_empty_double_collections
assert @puma.enemies.empty?
assert @froggy.protectors.empty?
assert @alice.enemies.empty?
assert @spot.protectors.empty?
assert @alice.beautiful_fight_relationships_as_enemy.empty?
assert @alice.beautiful_fight_relationships_as_protector.empty?
assert @alice.beautiful_fight_relationships.empty?
end
def test_double_collection_assignment
@alice.enemies << @spot
@alice.reload
@spot.reload
assert @spot.protectors.include?(@alice)
assert @alice.enemies.include?(@spot)
assert !@alice.protectors.include?(@alice)
assert_equal 1, @alice.beautiful_fight_relationships_as_protector.size
assert_equal 0, @alice.beautiful_fight_relationships_as_enemy.size
assert_equal 1, @alice.beautiful_fight_relationships.size
# self reference
assert_equal 1, @alice.enemies.length
@alice.enemies.push @alice
assert @alice.enemies.include?(@alice)
assert_equal 2, @alice.enemies.length
@alice.reload
assert_equal 2, @alice.beautiful_fight_relationships_as_protector.size
assert_equal 1, @alice.beautiful_fight_relationships_as_enemy.size
assert_equal 3, @alice.beautiful_fight_relationships.size
end
def test_double_dependency_injection
# breakpoint
end
def test_double_collection_deletion
@alice.enemies << @spot
@alice.reload
assert @alice.enemies.include?(@spot)
@alice.enemies.delete(@spot)
assert !@alice.enemies.include?(@spot)
assert @alice.enemies.empty?
@alice.reload
assert !@alice.enemies.include?(@spot)
assert @alice.enemies.empty?
assert_equal 0, @alice.beautiful_fight_relationships.size
end
def test_double_collection_deletion_from_opposite_side
@alice.protectors << @puma
@alice.reload
assert @alice.protectors.include?(@puma)
@alice.protectors.delete(@puma)
assert !@alice.protectors.include?(@puma)
assert @alice.protectors.empty?
@alice.reload
assert !@alice.protectors.include?(@puma)
assert @alice.protectors.empty?
assert_equal 0, @alice.beautiful_fight_relationships.size
end
def test_individual_collections_created_for_double_relationship
assert @alice.dogs.empty?
@alice.enemies << @spot
assert @alice.enemies.include?(@spot)
assert !@alice.kittens.include?(@alice)
assert !@alice.dogs.include?(@spot)
@alice.reload
assert @alice.dogs.include?(@spot)
assert !WildBoar.find(@alice.id).dogs.include?(@spot) # make sure the parent type is checked
end
def test_individual_collections_created_for_double_relationship_from_opposite_side
assert @alice.wild_boars.empty?
@alice.protectors << @puma
assert @alice.protectors.include?(@puma)
assert !@alice.wild_boars.include?(@puma)
@alice.reload
assert @alice.wild_boars.include?(@puma)
assert !Dog.find(@alice.id).wild_boars.include?(@puma) # make sure the parent type is checked
end
def test_self_referential_individual_collections_created_for_double_relationship
@alice.enemies << @alice
@alice.reload
assert @alice.enemy_kittens.include?(@alice)
assert @alice.protector_kittens.include?(@alice)
assert @alice.kittens.include?(@alice)
assert_equal 2, @alice.kittens.size
@alice.enemies << (@chloe = Kitten.find_by_name('Chloe'))
@alice.reload
assert @alice.enemy_kittens.include?(@chloe)
assert !@alice.protector_kittens.include?(@chloe)
assert @alice.kittens.include?(@chloe)
assert_equal 3, @alice.kittens.size
end
def test_child_of_polymorphic_join_can_reach_parent
@alice.enemies << @spot
@alice.reload
assert @spot.protectors.include?(@alice)
end
def test_double_collection_deletion_from_child_polymorphic_join
@alice.enemies << @spot
@spot.protectors.delete(@alice)
assert !@spot.protectors.include?(@alice)
@alice.reload
assert !@alice.enemies.include?(@spot)
BeautifulFightRelationship.create(:protector_id => 2, :protector_type => "Dog", :enemy_id => @spot.id, :enemy_type => @spot.class.name)
@alice.enemies << @spot
@spot.protectors.delete(@alice)
assert !@spot.protectors.include?(@alice)
end
def test_collection_query_on_unsaved_record
assert Dog.new.enemies.empty?
assert Dog.new.foodstuffs_of_eaters.empty?
end
def test_double_individual_collections_push
assert_equal [@chloe], (@spot.protector_kittens << @chloe)
@spot.reload
assert @spot.protectors.include?(@chloe)
assert @spot.protector_kittens.include?(@chloe)
assert !@spot.protector_dogs.include?(@chloe)
assert_equal [@froggy], (@spot.frogs << @froggy)
@spot.reload
assert @spot.enemies.include?(@froggy)
assert @spot.frogs.include?(@froggy)
assert !@spot.enemy_dogs.include?(@froggy)
end
def test_double_individual_collections_delete
@spot.protectors << [@chloe, @puma]
@spot.reload
assert_equal [@chloe], @spot.protector_kittens.delete(@chloe)
assert @spot.protector_kittens.empty?
@spot.protector_kittens.delete(@chloe) # again, unclear what .delete should return
@spot.reload
assert @spot.protector_kittens.empty?
assert @spot.wild_boars.include?(@puma)
end
def test_double_individual_collections_clear
@spot.protectors << [@chloe, @puma, @alice]
@spot.reload
assert_equal [], @spot.protector_kittens.clear
assert @spot.protector_kittens.empty?
assert_equal 1, @spot.protectors.size
@spot.reload
assert @spot.protector_kittens.empty?
assert_equal 1, @spot.protectors.size
assert !@spot.protector_kittens.include?(@chloe)
assert !@spot.protectors.include?(@chloe)
assert !@spot.protector_kittens.include?(@alice)
assert !@spot.protectors.include?(@alice)
assert @spot.protectors.include?(@puma)
assert @spot.wild_boars.include?(@puma)
end
def test_single_extensions
assert_equal :correct_block_result, @shamu.aquatic_pupils.a_method
@kibbles.eaters.push(@alice)
@kibbles.eaters.push(@spot)
assert_equal :correct_join_result, @kibbles.eaters_foodstuffs.a_method
assert_equal :correct_module_result, @kibbles.eaters.a_method
assert_equal :correct_other_module_result, @kibbles.eaters.another_method
@kibbles.eaters.each do |eater|
assert_equal :correct_join_result, eater.eaters_foodstuffs.a_method
end
assert_equal :correct_parent_proc_result, @kibbles.foodstuffs_of_eaters.a_method
assert_equal :correct_parent_proc_result, @kibbles.eaters.first.foodstuffs_of_eaters.a_method
end
def test_double_extensions
assert_equal :correct_proc_result, @spot.protectors.a_method
assert_equal :correct_module_result, @spot.enemies.a_method
assert_equal :correct_join_result, @spot.beautiful_fight_relationships_as_enemy.a_method
assert_equal :correct_join_result, @spot.beautiful_fight_relationships_as_protector.a_method
assert_equal :correct_join_result, @froggy.beautiful_fight_relationships.a_method
assert_equal :correct_join_result, @froggy.beautiful_fight_relationships_as_enemy.a_method
assert_raises(NoMethodError) {@froggy.beautiful_fight_relationships_as_protector.a_method}
end
def test_pluralization_checks
assert_raises(@association_error) {
eval "class SomeModel < ActiveRecord::Base
has_many_polymorphs :polymorphs, :from => [:dog, :cats]
end" }
assert_raises(@association_error) {
eval "class SomeModel < ActiveRecord::Base
has_many_polymorphs :polymorph, :from => [:dogs, :cats]
end" }
assert_raises(@association_error) {
eval "class SomeModel < ActiveRecord::Base
acts_as_double_polymorphic_join :polymorph => [:dogs, :cats], :unimorphs => [:dogs, :cats]
end" }
end
def test_single_custom_finders
[@kibbles, @alice, @puma, @spot, @bits].each {|record| @kibbles.eaters << record; sleep 1} # XXX yeah i know
assert_equal @kibbles.eaters, @kibbles.eaters.find(:all, :order => "eaters_foodstuffs.created_at ASC")
assert_equal @kibbles.eaters.reverse, @kibbles.eaters.find(:all, :order => "eaters_foodstuffs.created_at DESC")
if ActiveRecord::Base.connection.is_a? ActiveRecord::ConnectionAdapters::MysqlAdapter
assert_equal @kibbles.eaters.sort_by(&:created_at), @kibbles.eaters.find(:all, :order => "IFNULL(bow_wows.created_at,(IFNULL(petfoods.created_at,(IFNULL(wild_boars.created_at,(IFNULL(cats.created_at,fish.created_at))))))) ASC")
end
assert_equal @kibbles.eaters.select{|x| x.is_a? Petfood}, @kibbles.eater_petfoods.find(:all, :order => "eaters_foodstuffs.created_at ASC")
end
def test_double_custom_finders
@spot.protectors << [@chloe, @puma, @alice]
assert_equal [@chloe], @spot.protectors.find(:all, :conditions => ["cats.name = ?", @chloe.name], :limit => 1)
assert_equal [], @spot.protectors.find(:all, :conditions => ["cats.name = ?", @chloe.name], :limit => 1, :offset => 1)
assert_equal 2, @spot.protectors.find(:all, :limit => 100, :offset => 1).size
end
def test_single_custom_finder_parameters_carry_to_individual_relationships
# XXX test nullout here
end
def test_double_custom_finder_parameters_carry_to_individual_relationships
# XXX test nullout here
end
def test_include_doesnt_fail
assert_nothing_raised do
@spot.protectors.find(:all, :include => :wild_boars)
end
end
def test_abstract_method
assert_equal :correct_abstract_method_response, @spot.an_abstract_method
end
def test_missing_target_should_raise
@kibbles.eaters << [@kibbles, @alice, @puma, @spot, @bits]
@spot.destroy_without_callbacks
assert_raises(@association_error) { @kibbles.eaters.reload }
# assert_raises(@association_error) { @kibbles.eater_dogs.reload } # bah AR
end
def test_lazy_loading_is_lazy
# XXX
end
def test_push_with_skip_duplicates_false_doesnt_load_target
# XXX
end
def test_association_foreign_key_is_sane
assert_equal "eater_id", Petfood.reflect_on_association(:eaters).association_foreign_key
end
def test_reflection_instance_methods_are_sane
assert_equal EatersFoodstuff, Petfood.reflect_on_association(:eaters).klass
assert_equal EatersFoodstuff.name, Petfood.reflect_on_association(:eaters).class_name
end
def test_parent_order_orders_parents
@alice.foodstuffs_of_eaters << Petfood.find(:all, :order => "the_petfood_primary_key ASC")
@alice.reload #not necessary
assert_equal [2,1], @alice.foodstuffs_of_eaters.map(&:id)
end
# def test_polymorphic_include
# @kibbles.eaters << [@kibbles, @alice, @puma, @spot, @bits]
# assert @kibbles.eaters.include?(@kibbles.eaters_foodstuffs.find(:all, :include => :eater).first.eater)
# end
#
# def test_double_polymorphic_include
# end
#
# def test_single_child_include
# end
#
# def test_double_child_include
# end
#
# def test_single_include_from_parent
# end
#
# def test_double_include_from_parent
# end
#
# def test_meta_referential_single_include
# end
#
# def test_meta_referential_double_include
# end
#
# def test_meta_referential_single_include
# end
#
# def test_meta_referential_single_double_multi_include
# end
#
# def test_dont_ignore_duplicates
# end
#
# def test_ignore_duplicates
# end
#
# def test_tagging_system_generator
# end
#
# def test_tagging_system_library
# end
end