mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
f8d2899d12
This allows us to decouple AssociationReflection and ThroughReflection making ThroughReflection it's own Reflection bucket in a way. The benefit of this is to be able to remove checks against the macro's symbol for exmaple `macro == :belongs_to`. Get all tests passing again Some of the methods that used to be inherited from MacroReflection through AssociationReflection were no longer getting passed through. They needed to be duplicated into the ThroughReflection. I will extract these out into a separate class. Refactor shared methods into strategy object Now that we've separated ThroughReflection and AssociationReflection we can combine shared methods into one class to avoid duplication. Break out class for each type of reflection This creates a class for each reflection type (has_many, has_one, belongs_to and habtm). We then can remove the need to set the macro symbol in each initialization. Tests were updated to reflect these changes because creation of these reflections is now different. Remove need for @collection instance var We now define `collection?` as `false` by default and set it to `true` in `has_and_belongs_to_many` and `has_many` reflections. This removes the need for the `@collection` instance variable. Raise exception on unknown macro types We shouldn't accept just any macro when creating reflections. An unrecongnized AssociationReflection raises an error. Tests in `reflection_test` were updated to reflect these new changes. `:has_and_belongs_to_many` macro tests were removed because we no longer internally return HABTM.
450 lines
18 KiB
Ruby
450 lines
18 KiB
Ruby
require "cases/helper"
|
|
require 'models/topic'
|
|
require 'models/customer'
|
|
require 'models/company'
|
|
require 'models/company_in_module'
|
|
require 'models/ship'
|
|
require 'models/pirate'
|
|
require 'models/price_estimate'
|
|
require 'models/essay'
|
|
require 'models/author'
|
|
require 'models/organization'
|
|
require 'models/post'
|
|
require 'models/tagging'
|
|
require 'models/category'
|
|
require 'models/book'
|
|
require 'models/subscriber'
|
|
require 'models/subscription'
|
|
require 'models/tag'
|
|
require 'models/sponsor'
|
|
require 'models/edge'
|
|
require 'models/hotel'
|
|
require 'models/chef'
|
|
require 'models/department'
|
|
require 'models/cake_designer'
|
|
require 'models/drink_designer'
|
|
|
|
class ReflectionTest < ActiveRecord::TestCase
|
|
include ActiveRecord::Reflection
|
|
|
|
fixtures :topics, :customers, :companies, :subscribers, :price_estimates
|
|
|
|
def setup
|
|
@first = Topic.find(1)
|
|
end
|
|
|
|
def test_human_name
|
|
assert_equal "Price estimate", PriceEstimate.model_name.human
|
|
assert_equal "Subscriber", Subscriber.model_name.human
|
|
end
|
|
|
|
def test_read_attribute_names
|
|
assert_equal(
|
|
%w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort,
|
|
@first.attribute_names.sort
|
|
)
|
|
end
|
|
|
|
def test_columns
|
|
assert_equal 18, Topic.columns.length
|
|
end
|
|
|
|
def test_columns_are_returned_in_the_order_they_were_declared
|
|
column_names = Topic.columns.map { |column| column.name }
|
|
assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
|
|
end
|
|
|
|
def test_content_columns
|
|
content_columns = Topic.content_columns
|
|
content_column_names = content_columns.map {|column| column.name}
|
|
assert_equal 13, content_columns.length
|
|
assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort
|
|
end
|
|
|
|
def test_column_string_type_and_limit
|
|
assert_equal :string, @first.column_for_attribute("title").type
|
|
assert_equal 250, @first.column_for_attribute("title").limit
|
|
end
|
|
|
|
def test_column_null_not_null
|
|
subscriber = Subscriber.first
|
|
assert subscriber.column_for_attribute("name").null
|
|
assert !subscriber.column_for_attribute("nick").null
|
|
end
|
|
|
|
def test_human_name_for_column
|
|
assert_equal "Author name", @first.column_for_attribute("author_name").human_name
|
|
end
|
|
|
|
def test_integer_columns
|
|
assert_equal :integer, @first.column_for_attribute("id").type
|
|
end
|
|
|
|
def test_non_existent_columns_return_nil
|
|
assert_deprecated do
|
|
assert_nil @first.column_for_attribute("attribute_that_doesnt_exist")
|
|
end
|
|
end
|
|
|
|
def test_reflection_klass_for_nested_class_name
|
|
reflection = ActiveRecord::Reflection.create(:has_many, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base)
|
|
assert_nothing_raised do
|
|
assert_equal MyApplication::Business::Company, reflection.klass
|
|
end
|
|
end
|
|
|
|
def test_irregular_reflection_class_name
|
|
ActiveSupport::Inflector.inflections do |inflect|
|
|
inflect.irregular 'plural_irregular', 'plurales_irregulares'
|
|
end
|
|
reflection = ActiveRecord::Reflection.create(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base)
|
|
assert_equal 'PluralIrregular', reflection.class_name
|
|
end
|
|
|
|
def test_aggregation_reflection
|
|
reflection_for_address = AggregateReflection.new(
|
|
:address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
|
|
)
|
|
|
|
reflection_for_balance = AggregateReflection.new(
|
|
:balance, nil, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
|
|
)
|
|
|
|
reflection_for_gps_location = AggregateReflection.new(
|
|
:gps_location, nil, { }, Customer
|
|
)
|
|
|
|
assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
|
|
assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
|
|
assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
|
|
|
|
assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
|
|
|
|
assert_equal Address, Customer.reflect_on_aggregation(:address).klass
|
|
|
|
assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
|
|
end
|
|
|
|
def test_reflect_on_all_autosave_associations
|
|
expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] }
|
|
received = Pirate.reflect_on_all_autosave_associations
|
|
|
|
assert !received.empty?
|
|
assert_not_equal Pirate.reflect_on_all_associations.length, received.length
|
|
assert_equal expected, received
|
|
end
|
|
|
|
def test_has_many_reflection
|
|
reflection_for_clients = ActiveRecord::Reflection.create(:has_many, :clients, nil, { :order => "id", :dependent => :destroy }, Firm)
|
|
|
|
assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
|
|
|
|
assert_equal Client, Firm.reflect_on_association(:clients).klass
|
|
assert_equal 'companies', Firm.reflect_on_association(:clients).table_name
|
|
|
|
assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass
|
|
assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name
|
|
end
|
|
|
|
def test_has_one_reflection
|
|
reflection_for_account = ActiveRecord::Reflection.create(:has_one, :account, nil, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
|
|
assert_equal reflection_for_account, Firm.reflect_on_association(:account)
|
|
|
|
assert_equal Account, Firm.reflect_on_association(:account).klass
|
|
assert_equal 'accounts', Firm.reflect_on_association(:account).table_name
|
|
end
|
|
|
|
def test_belongs_to_inferred_foreign_key_from_assoc_name
|
|
Company.belongs_to :foo
|
|
assert_equal "foo_id", Company.reflect_on_association(:foo).foreign_key
|
|
Company.belongs_to :bar, :class_name => "Xyzzy"
|
|
assert_equal "bar_id", Company.reflect_on_association(:bar).foreign_key
|
|
Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id"
|
|
assert_equal "xyzzy_id", Company.reflect_on_association(:baz).foreign_key
|
|
end
|
|
|
|
def test_association_reflection_in_modules
|
|
ActiveRecord::Base.store_full_sti_class = false
|
|
|
|
assert_reflection MyApplication::Business::Firm,
|
|
:clients_of_firm,
|
|
:klass => MyApplication::Business::Client,
|
|
:class_name => 'Client',
|
|
:table_name => 'companies'
|
|
|
|
assert_reflection MyApplication::Billing::Account,
|
|
:firm,
|
|
:klass => MyApplication::Business::Firm,
|
|
:class_name => 'MyApplication::Business::Firm',
|
|
:table_name => 'companies'
|
|
|
|
assert_reflection MyApplication::Billing::Account,
|
|
:qualified_billing_firm,
|
|
:klass => MyApplication::Billing::Firm,
|
|
:class_name => 'MyApplication::Billing::Firm',
|
|
:table_name => 'companies'
|
|
|
|
assert_reflection MyApplication::Billing::Account,
|
|
:unqualified_billing_firm,
|
|
:klass => MyApplication::Billing::Firm,
|
|
:class_name => 'Firm',
|
|
:table_name => 'companies'
|
|
|
|
assert_reflection MyApplication::Billing::Account,
|
|
:nested_qualified_billing_firm,
|
|
:klass => MyApplication::Billing::Nested::Firm,
|
|
:class_name => 'MyApplication::Billing::Nested::Firm',
|
|
:table_name => 'companies'
|
|
|
|
assert_reflection MyApplication::Billing::Account,
|
|
:nested_unqualified_billing_firm,
|
|
:klass => MyApplication::Billing::Nested::Firm,
|
|
:class_name => 'Nested::Firm',
|
|
:table_name => 'companies'
|
|
ensure
|
|
ActiveRecord::Base.store_full_sti_class = true
|
|
end
|
|
|
|
def test_reflection_should_not_raise_error_when_compared_to_other_object
|
|
assert_not_equal Object.new, Firm._reflections['clients']
|
|
end
|
|
|
|
def test_has_and_belongs_to_many_reflection
|
|
assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro
|
|
assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name
|
|
end
|
|
|
|
def test_has_many_through_reflection
|
|
assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
|
|
end
|
|
|
|
def test_chain
|
|
expected = [
|
|
Organization.reflect_on_association(:author_essay_categories),
|
|
Author.reflect_on_association(:essays),
|
|
Organization.reflect_on_association(:authors)
|
|
]
|
|
actual = Organization.reflect_on_association(:author_essay_categories).chain
|
|
|
|
assert_equal expected, actual
|
|
end
|
|
|
|
def test_scope_chain
|
|
expected = [
|
|
[Tagging.reflect_on_association(:tag).scope, Post.reflect_on_association(:first_blue_tags).scope],
|
|
[Post.reflect_on_association(:first_taggings).scope],
|
|
[Author.reflect_on_association(:misc_posts).scope]
|
|
]
|
|
actual = Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
|
|
assert_equal expected, actual
|
|
|
|
expected = [
|
|
[
|
|
Tagging.reflect_on_association(:blue_tag).scope,
|
|
Post.reflect_on_association(:first_blue_tags_2).scope,
|
|
Author.reflect_on_association(:misc_post_first_blue_tags_2).scope
|
|
],
|
|
[],
|
|
[]
|
|
]
|
|
actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
|
|
assert_equal expected, actual
|
|
end
|
|
|
|
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
|
|
@hotel = Hotel.create!
|
|
@department = @hotel.departments.create!
|
|
@department.chefs.create!(employable: CakeDesigner.create!)
|
|
@department.chefs.create!(employable: DrinkDesigner.create!)
|
|
|
|
assert_equal 1, @hotel.cake_designers.size
|
|
assert_equal 1, @hotel.drink_designers.size
|
|
assert_equal 2, @hotel.chefs.size
|
|
end
|
|
|
|
def test_nested?
|
|
assert !Author.reflect_on_association(:comments).nested?
|
|
assert Author.reflect_on_association(:tags).nested?
|
|
|
|
# Only goes :through once, but the through_reflection is a has_and_belongs_to_many, so this is
|
|
# a nested through association
|
|
assert Category.reflect_on_association(:post_comments).nested?
|
|
end
|
|
|
|
def test_association_primary_key
|
|
# Normal association
|
|
assert_equal "id", Author.reflect_on_association(:posts).association_primary_key.to_s
|
|
assert_equal "name", Author.reflect_on_association(:essay).association_primary_key.to_s
|
|
assert_equal "name", Essay.reflect_on_association(:writer).association_primary_key.to_s
|
|
|
|
# Through association (uses the :primary_key option from the source reflection)
|
|
assert_equal "nick", Author.reflect_on_association(:subscribers).association_primary_key.to_s
|
|
assert_equal "name", Author.reflect_on_association(:essay_category).association_primary_key.to_s
|
|
assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested
|
|
end
|
|
|
|
def test_association_primary_key_raises_when_missing_primary_key
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author)
|
|
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key }
|
|
|
|
through = Class.new(ActiveRecord::Reflection::ThroughReflection) {
|
|
define_method(:source_reflection) { reflection }
|
|
}.new(reflection)
|
|
assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key }
|
|
end
|
|
|
|
def test_active_record_primary_key
|
|
assert_equal "nick", Subscriber.reflect_on_association(:subscriptions).active_record_primary_key.to_s
|
|
assert_equal "name", Author.reflect_on_association(:essay).active_record_primary_key.to_s
|
|
end
|
|
|
|
def test_active_record_primary_key_raises_when_missing_primary_key
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :author, nil, {}, Edge)
|
|
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key }
|
|
end
|
|
|
|
def test_foreign_type
|
|
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s
|
|
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s
|
|
end
|
|
|
|
def test_collection_association
|
|
assert Pirate.reflect_on_association(:birds).collection?
|
|
assert Pirate.reflect_on_association(:parrots).collection?
|
|
|
|
assert !Pirate.reflect_on_association(:ship).collection?
|
|
assert !Ship.reflect_on_association(:pirate).collection?
|
|
end
|
|
|
|
def test_default_association_validation
|
|
assert ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm).validate?
|
|
|
|
assert !ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm).validate?
|
|
assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm).validate?
|
|
end
|
|
|
|
def test_always_validate_association_if_explicit
|
|
assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :validate => true }, Firm).validate?
|
|
assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :validate => true }, Firm).validate?
|
|
assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :validate => true }, Firm).validate?
|
|
end
|
|
|
|
def test_validate_association_if_autosave
|
|
assert ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true }, Firm).validate?
|
|
assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true }, Firm).validate?
|
|
assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true }, Firm).validate?
|
|
end
|
|
|
|
def test_never_validate_association_if_explicit
|
|
assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { :autosave => true, :validate => false }, Firm).validate?
|
|
assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { :autosave => true, :validate => false }, Firm).validate?
|
|
assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate?
|
|
end
|
|
|
|
def test_foreign_key
|
|
assert_equal "author_id", Author.reflect_on_association(:posts).foreign_key.to_s
|
|
assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s
|
|
end
|
|
|
|
def test_through_reflection_scope_chain_does_not_modify_other_reflections
|
|
orig_conds = Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect
|
|
Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
|
|
assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect
|
|
end
|
|
|
|
def test_symbol_for_class_name
|
|
assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
|
|
end
|
|
|
|
def test_join_table
|
|
category = Struct.new(:table_name, :pluralize_table_names).new('categories', true)
|
|
product = Struct.new(:table_name, :pluralize_table_names).new('products', true)
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product)
|
|
reflection.stubs(:klass).returns(category)
|
|
assert_equal 'categories_products', reflection.join_table
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category)
|
|
reflection.stubs(:klass).returns(product)
|
|
assert_equal 'categories_products', reflection.join_table
|
|
end
|
|
|
|
def test_join_table_with_common_prefix
|
|
category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true)
|
|
product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true)
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, product)
|
|
reflection.stubs(:klass).returns(category)
|
|
assert_equal 'catalog_categories_products', reflection.join_table
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category)
|
|
reflection.stubs(:klass).returns(product)
|
|
assert_equal 'catalog_categories_products', reflection.join_table
|
|
end
|
|
|
|
def test_join_table_with_different_prefix
|
|
category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true)
|
|
page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true)
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, {}, page)
|
|
reflection.stubs(:klass).returns(category)
|
|
assert_equal 'catalog_categories_content_pages', reflection.join_table
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category)
|
|
reflection.stubs(:klass).returns(page)
|
|
assert_equal 'catalog_categories_content_pages', reflection.join_table
|
|
end
|
|
|
|
def test_join_table_can_be_overridden
|
|
category = Struct.new(:table_name, :pluralize_table_names).new('categories', true)
|
|
product = Struct.new(:table_name, :pluralize_table_names).new('products', true)
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :categories, nil, { :join_table => 'product_categories' }, product)
|
|
reflection.stubs(:klass).returns(category)
|
|
assert_equal 'product_categories', reflection.join_table
|
|
|
|
reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { :join_table => 'product_categories' }, category)
|
|
reflection.stubs(:klass).returns(product)
|
|
assert_equal 'product_categories', reflection.join_table
|
|
end
|
|
|
|
def test_includes_accepts_symbols
|
|
hotel = Hotel.create!
|
|
department = hotel.departments.create!
|
|
department.chefs.create!
|
|
|
|
assert_nothing_raised do
|
|
assert_equal department.chefs, Hotel.includes([departments: :chefs]).first.chefs
|
|
end
|
|
end
|
|
|
|
def test_includes_accepts_strings
|
|
hotel = Hotel.create!
|
|
department = hotel.departments.create!
|
|
department.chefs.create!
|
|
|
|
assert_nothing_raised do
|
|
assert_equal department.chefs, Hotel.includes(['departments' => 'chefs']).first.chefs
|
|
end
|
|
end
|
|
|
|
def test_reflect_on_association_accepts_symbols
|
|
assert_nothing_raised do
|
|
assert_equal Hotel.reflect_on_association(:departments).name, :departments
|
|
end
|
|
end
|
|
|
|
def test_reflect_on_association_accepts_strings
|
|
assert_nothing_raised do
|
|
assert_equal Hotel.reflect_on_association("departments").name, :departments
|
|
end
|
|
end
|
|
|
|
private
|
|
def assert_reflection(klass, association, options)
|
|
assert reflection = klass.reflect_on_association(association)
|
|
options.each do |method, value|
|
|
assert_equal(value, reflection.send(method))
|
|
end
|
|
end
|
|
end
|