mirror of
https://github.com/TracksApp/tracks.git
synced 2026-01-07 17:58:50 +01:00
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:
parent
04f1bd87e7
commit
a6501c3d83
57 changed files with 0 additions and 2590 deletions
184
tracks/vendor/plugins/has_many_polymorphs/LICENSE
vendored
184
tracks/vendor/plugins/has_many_polymorphs/LICENSE
vendored
|
|
@ -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.
|
||||
|
||||
|
|
@ -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
|
||||
49
tracks/vendor/plugins/has_many_polymorphs/README
vendored
49
tracks/vendor/plugins/has_many_polymorphs/README
vendored
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
tags_001:
|
||||
name: delicious
|
||||
id: "1"
|
||||
tags_002:
|
||||
name: sexy
|
||||
id: "2"
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
|
||||
require 'has_many_polymorphs'
|
||||
|
|
@ -1 +0,0 @@
|
|||
puts open("#{File.dirname(__FILE__)}/TAGGING_INSTALL").read
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
@ -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"
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
class Aquatic::Fish < ActiveRecord::Base
|
||||
# attr_accessor :after_find_test, :after_initialize_test
|
||||
end
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
class Canine < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
|
||||
def an_abstract_method
|
||||
:correct_abstract_method_response
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
class Cat < ActiveRecord::Base
|
||||
# STI base class
|
||||
self.inheritance_column = 'cat_type'
|
||||
end
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
class Frog < ActiveRecord::Base
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
class Kitten < Cat
|
||||
# has_many :eaters_parents, :dependent => true, :as => 'eater'
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
class Tabby < Cat
|
||||
end
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
class WildBoar < ActiveRecord::Base
|
||||
end
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
module ExtensionModule
|
||||
def a_method
|
||||
:correct_module_result
|
||||
end
|
||||
def self.a_method
|
||||
:incorrect_module_result
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
module OtherExtensionModule
|
||||
def another_method
|
||||
:correct_other_module_result
|
||||
end
|
||||
def self.another_method
|
||||
:incorrect_other_module_result
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue