aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/reflection_test.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/test/cases/reflection_test.rb')
-rw-r--r--activerecord/test/cases/reflection_test.rb518
1 files changed, 518 insertions, 0 deletions
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
new file mode 100644
index 0000000000..ed19192ad9
--- /dev/null
+++ b/activerecord/test/cases/reflection_test.rb
@@ -0,0 +1,518 @@
+# frozen_string_literal: true
+
+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"
+require "models/recipe"
+
+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(&: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(&: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 :string, @first.column_for_attribute(:title).type
+ assert_equal :string, @first.type_for_attribute("title").type
+ assert_equal :string, @first.type_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
+ assert_equal :integer, @first.column_for_attribute(:id).type
+ assert_equal :integer, @first.type_for_attribute("id").type
+ assert_equal :integer, @first.type_for_attribute(:id).type
+ end
+
+ def test_non_existent_columns_return_null_object
+ column = @first.column_for_attribute("attribute_that_doesnt_exist")
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
+ assert_equal "attribute_that_doesnt_exist", column.name
+ assert_nil column.sql_type
+ assert_nil column.type
+
+ column = @first.column_for_attribute(:attribute_that_doesnt_exist)
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
+ end
+
+ def test_non_existent_types_are_identity_types
+ type = @first.type_for_attribute("attribute_that_doesnt_exist")
+ object = Object.new
+
+ assert_equal object, type.deserialize(object)
+ assert_equal object, type.cast(object)
+ assert_equal object, type.serialize(object)
+
+ type = @first.type_for_attribute(:attribute_that_doesnt_exist)
+ assert_equal object, type.deserialize(object)
+ assert_equal object, type.cast(object)
+ assert_equal object, type.serialize(object)
+ end
+
+ def test_reflection_klass_for_nested_class_name
+ reflection = ActiveRecord::Reflection.create(
+ :has_many,
+ nil,
+ nil,
+ { class_name: "MyApplication::Business::Company" },
+ Customer
+ )
+ 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_includes Customer.reflect_on_all_aggregations, reflection_for_gps_location
+ assert_includes Customer.reflect_on_all_aggregations, reflection_for_balance
+ assert_includes Customer.reflect_on_all_aggregations, 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_not_empty received
+ 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_reflections_should_return_keys_as_strings
+ assert Category.reflections.keys.all? { |key| key.is_a? String }, "Model.reflections is expected to return string for keys"
+ 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_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.cake_designers.count
+ assert_equal 1, hotel.drink_designers.size
+ assert_equal 1, hotel.drink_designers.count
+ assert_equal 2, hotel.chefs.size
+ assert_equal 2, hotel.chefs.count
+ end
+
+ def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti
+ hotel = Hotel.create!
+ hotel.mocktail_designers << MocktailDesigner.create!
+
+ assert_equal 1, hotel.mocktail_designers.size
+ assert_equal 1, hotel.mocktail_designers.count
+ assert_equal 1, hotel.chef_lists.size
+ assert_equal 1, hotel.chef_lists.count
+
+ hotel.mocktail_designers = []
+
+ assert_equal 0, hotel.mocktail_designers.size
+ assert_equal 0, hotel.mocktail_designers.count
+ assert_equal 0, hotel.chef_lists.size
+ assert_equal 0, hotel.chef_lists.count
+ end
+
+ def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
+ hotel = Hotel.create!
+ department = hotel.departments.create!
+ drink = department.chefs.create!(employable: DrinkDesigner.create!)
+ Recipe.create!(chef_id: drink.id, hotel_id: hotel.id)
+
+ expected_sql = capture_sql { hotel.recipes.to_a }
+
+ Hotel.reflect_on_association(:recipes).clear_association_scope_cache
+ hotel.reload
+ hotel.drink_designers.to_a
+ loaded_sql = capture_sql { hotel.recipes.to_a }
+
+ assert_equal expected_sql, loaded_sql
+ end
+
+ def test_nested?
+ assert_not_predicate Author.reflect_on_association(:comments), :nested?
+ assert_predicate 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_predicate 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_type
+ assert_equal "taggable_type", Post.reflect_on_association(:taggings).type.to_s
+ assert_equal "imageable_class", Post.reflect_on_association(:images).type.to_s
+ assert_nil Post.reflect_on_association(:readers).type
+ 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
+ assert_nil Sponsor.reflect_on_association(:sponsor_club).foreign_type
+ end
+
+ def test_collection_association
+ assert_predicate Pirate.reflect_on_association(:birds), :collection?
+ assert_predicate Pirate.reflect_on_association(:parrots), :collection?
+
+ assert_not_predicate Pirate.reflect_on_association(:ship), :collection?
+ assert_not_predicate Ship.reflect_on_association(:pirate), :collection?
+ end
+
+ def test_default_association_validation
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm), :validate?
+
+ assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm), :validate?
+ end
+
+ def test_always_validate_association_if_explicit
+ assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm), :validate?
+ end
+
+ def test_validate_association_if_autosave
+ assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm), :validate?
+ end
+
+ def test_never_validate_association_if_explicit
+ assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm), :validate?
+ assert_not_predicate 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_symbol_for_class_name
+ assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
+ end
+
+ def test_class_for_class_name
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm)
+ end
+ assert_equal "A class was passed to `:class_name` but we are expecting a string.", error.message
+ 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.stub(:klass, category) do
+ assert_equal "categories_products", reflection.join_table
+ end
+
+ reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category)
+ reflection.stub(:klass, product) do
+ assert_equal "categories_products", reflection.join_table
+ end
+ 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.stub(:klass, category) do
+ assert_equal "catalog_categories_products", reflection.join_table
+ end
+
+ reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, {}, category)
+ reflection.stub(:klass, product) do
+ assert_equal "catalog_categories_products", reflection.join_table
+ end
+ 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.stub(:klass, category) do
+ assert_equal "catalog_categories_content_pages", reflection.join_table
+ end
+
+ reflection = ActiveRecord::Reflection.create(:has_many, :pages, nil, {}, category)
+ reflection.stub(:klass, page) do
+ assert_equal "catalog_categories_content_pages", reflection.join_table
+ end
+ 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.stub(:klass, category) do
+ assert_equal "product_categories", reflection.join_table
+ end
+
+ reflection = ActiveRecord::Reflection.create(:has_many, :products, nil, { join_table: "product_categories" }, category)
+ reflection.stub(:klass, product) do
+ assert_equal "product_categories", reflection.join_table
+ end
+ 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