diff options
Diffstat (limited to 'activerecord/test/models')
144 files changed, 3336 insertions, 0 deletions
diff --git a/activerecord/test/models/account.rb b/activerecord/test/models/account.rb new file mode 100644 index 0000000000..0c3cd45a81 --- /dev/null +++ b/activerecord/test/models/account.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Account < ActiveRecord::Base + belongs_to :firm, class_name: "Company" + belongs_to :unautosaved_firm, foreign_key: "firm_id", class_name: "Firm", autosave: false + + alias_attribute :available_credit, :credit_limit + + def self.destroyed_account_ids + @destroyed_account_ids ||= Hash.new { |h, k| h[k] = [] } + end + + # Test private kernel method through collection proxy using has_many. + def self.open + where("firm_name = ?", "37signals") + end + + before_destroy do |account| + if account.firm + Account.destroyed_account_ids[account.firm.id] << account.id + end + end + + validate :check_empty_credit_limit + + private + def check_empty_credit_limit + errors.add("credit_limit", :blank) if credit_limit.blank? + end + + def private_method + "Sir, yes sir!" + end +end diff --git a/activerecord/test/models/admin.rb b/activerecord/test/models/admin.rb new file mode 100644 index 0000000000..a40b5a33b2 --- /dev/null +++ b/activerecord/test/models/admin.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Admin + def self.table_name_prefix + "admin_" + end +end diff --git a/activerecord/test/models/admin/account.rb b/activerecord/test/models/admin/account.rb new file mode 100644 index 0000000000..41fe2d782b --- /dev/null +++ b/activerecord/test/models/admin/account.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Admin::Account < ActiveRecord::Base + has_many :users +end diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb new file mode 100644 index 0000000000..d89b8dd293 --- /dev/null +++ b/activerecord/test/models/admin/randomly_named_c1.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Admin::ClassNameThatDoesNotFollowCONVENTIONS1 < ActiveRecord::Base + self.table_name = :randomly_named_table2 +end + +class Admin::ClassNameThatDoesNotFollowCONVENTIONS2 < ActiveRecord::Base + self.table_name = :randomly_named_table3 +end diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb new file mode 100644 index 0000000000..abb5cb28e7 --- /dev/null +++ b/activerecord/test/models/admin/user.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class Admin::User < ActiveRecord::Base + class Coder + def initialize(default = {}) + @default = default + end + + def dump(o) + ActiveSupport::JSON.encode(o || @default) + end + + def load(s) + s.present? ? ActiveSupport::JSON.decode(s) : @default.clone + end + end + + belongs_to :account + store :params, accessors: [ :token ], coder: YAML + store :settings, accessors: [ :color, :homepage ] + store_accessor :settings, :favorite_food + store :preferences, accessors: [ :remember_login ] + store :json_data, accessors: [ :height, :weight ], coder: Coder.new + store :json_data_empty, accessors: [ :is_a_good_guy ], coder: Coder.new + + def phone_number + read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/, '(\1) \2-\3') + end + + def phone_number=(value) + write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/, "")) + end + + def color + super || "red" + end + + def color=(value) + value = "blue" unless %w(black red green blue).include?(value) + super + end +end diff --git a/activerecord/test/models/aircraft.rb b/activerecord/test/models/aircraft.rb new file mode 100644 index 0000000000..4fdea46cf7 --- /dev/null +++ b/activerecord/test/models/aircraft.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Aircraft < ActiveRecord::Base + self.pluralize_table_names = false + has_many :engines, foreign_key: "car_id" + has_many :wheels, as: :wheelable +end diff --git a/activerecord/test/models/arunit2_model.rb b/activerecord/test/models/arunit2_model.rb new file mode 100644 index 0000000000..5b0da8a249 --- /dev/null +++ b/activerecord/test/models/arunit2_model.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ARUnit2Model < ActiveRecord::Base + self.abstract_class = true +end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb new file mode 100644 index 0000000000..bd12cdf7ef --- /dev/null +++ b/activerecord/test/models/author.rb @@ -0,0 +1,219 @@ +# frozen_string_literal: true + +class Author < ActiveRecord::Base + has_many :posts + has_many :serialized_posts + has_one :post + has_many :very_special_comments, through: :posts + has_many :posts_with_comments, -> { includes(:comments) }, class_name: "Post" + has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, class_name: "Post" + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, class_name: "Post" + has_many :posts_sorted_by_id, -> { order(:id) }, class_name: "Post" + has_many :posts_sorted_by_id_limited, -> { order("posts.id").limit(1) }, class_name: "Post" + has_many :posts_with_categories, -> { includes(:categories) }, class_name: "Post" + has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, class_name: "Post" + has_many :posts_with_special_categorizations, class_name: "PostWithSpecialCategorization" + has_one :post_about_thinking, -> { where("posts.title like '%thinking%'") }, class_name: "Post" + has_one :post_about_thinking_with_last_comment, -> { where("posts.title like '%thinking%'").includes(:last_comment) }, class_name: "Post" + has_many :comments, through: :posts do + def ratings + Rating.joins(:comment).merge(self) + end + end + has_many :comments_containing_the_letter_e, through: :posts, source: :comments + has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments + has_many :comments_with_include, -> { includes(:post).where(posts: { type: "Post" }) }, through: :posts, source: :comments + has_many :comments_for_first_author, -> { for_first_author }, through: :posts, source: :comments + + has_many :first_posts + has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments + + has_one :first_post + has_one :comment_on_first_post, -> { order("posts.id desc, comments.id asc") }, through: :first_post, source: :comments + + has_many :thinking_posts, -> { where(title: "So I was thinking") }, dependent: :delete_all, class_name: "Post" + has_many :welcome_posts, -> { where(title: "Welcome to the weblog") }, class_name: "Post" + + has_many :welcome_posts_with_one_comment, + -> { where(title: "Welcome to the weblog").where("comments_count = ?", 1) }, + class_name: "Post" + has_many :welcome_posts_with_comments, + -> { where(title: "Welcome to the weblog").where(Post.arel_table[:comments_count].gt(0)) }, + class_name: "Post" + + has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts_sorted_by_id, source: :comments + has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments + has_many :funky_comments, through: :posts, source: :comments + has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments + has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments + has_many :readonly_comments, -> { readonly }, through: :posts, source: :comments + + has_many :special_posts + has_many :special_post_comments, through: :special_posts, source: :comments + has_many :special_posts_with_default_scope, class_name: "SpecialPostWithDefaultScope" + + has_many :sti_posts, class_name: "StiPost" + has_many :sti_post_comments, through: :sti_posts, source: :comments + + has_many :special_nonexistent_posts, -> { where("posts.body = 'nonexistent'") }, class_name: "SpecialPost" + has_many :special_nonexistent_post_comments, -> { where("comments.post_id" => 0) }, through: :special_nonexistent_posts, source: :comments + has_many :nonexistent_comments, through: :posts + + has_many :hello_posts, -> { where "posts.body = 'hello'" }, class_name: "Post" + has_many :hello_post_comments, through: :hello_posts, source: :comments + has_many :posts_with_no_comments, -> { where("comments.id" => nil).includes(:comments) }, class_name: "Post" + + has_many :hello_posts_with_hash_conditions, -> { where(body: "hello") }, class_name: "Post" + has_many :hello_post_comments_with_hash_conditions, through: :hello_posts_with_hash_conditions, source: :comments + + has_many :other_posts, class_name: "Post" + has_many :posts_with_callbacks, class_name: "Post", before_add: :log_before_adding, + after_add: :log_after_adding, + before_remove: :log_before_removing, + after_remove: :log_after_removing + has_many :posts_with_proc_callbacks, class_name: "Post", + before_add: Proc.new { |o, r| o.post_log << "before_adding#{r.id || '<new>'}" }, + after_add: Proc.new { |o, r| o.post_log << "after_adding#{r.id || '<new>'}" }, + before_remove: Proc.new { |o, r| o.post_log << "before_removing#{r.id}" }, + after_remove: Proc.new { |o, r| o.post_log << "after_removing#{r.id}" } + has_many :posts_with_multiple_callbacks, class_name: "Post", + before_add: [:log_before_adding, Proc.new { |o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}" }], + after_add: [:log_after_adding, Proc.new { |o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}" }] + has_many :unchangeable_posts, class_name: "Post", before_add: :raise_exception, after_add: :log_after_adding + + has_many :categorizations, -> {} + has_many :categories, through: :categorizations + has_many :named_categories, through: :categorizations + + has_many :special_categorizations + has_many :special_categories, through: :special_categorizations, source: :category + has_one :special_category, through: :special_categorizations, source: :category + + has_many :special_categories_with_conditions, -> { where(categorizations: { special: true }) }, through: :categorizations, source: :category + has_many :nonspecial_categories_with_conditions, -> { where(categorizations: { special: false }) }, through: :categorizations, source: :category + + has_many :categories_like_general, -> { where(name: "General") }, through: :categorizations, source: :category, class_name: "Category" + + has_many :categorized_posts, through: :categorizations, source: :post + has_many :unique_categorized_posts, -> { distinct }, through: :categorizations, source: :post + + has_many :nothings, through: :kateggorisatons, class_name: "Category" + + has_many :author_favorites + has_many :favorite_authors, -> { order("name") }, through: :author_favorites + + has_many :taggings, through: :posts, source: :taggings + has_many :taggings_2, through: :posts, source: :tagging + has_many :tags, through: :posts + has_many :ordered_tags, through: :posts + has_many :post_categories, through: :posts, source: :categories + has_many :tagging_tags, through: :taggings, source: :tag + + has_many :similar_posts, -> { distinct }, through: :tags, source: :tagged_posts + has_many :ordered_posts, -> { distinct }, through: :ordered_tags, source: :tagged_posts + has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, through: :posts, source: :tags + + has_many :tags_with_primary_key, through: :posts + + has_many :books + has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book" + has_many :subscriptions, through: :books + has_many :subscribers, -> { order("subscribers.nick") }, through: :subscriptions + has_many :distinct_subscribers, -> { select("DISTINCT subscribers.*").order("subscribers.nick") }, through: :subscriptions, source: :subscriber + + has_one :essay, primary_key: :name, as: :writer + has_one :essay_category, through: :essay, source: :category + has_one :essay_owner, through: :essay, source: :owner + + has_one :essay_2, primary_key: :name, class_name: "Essay", foreign_key: :author_id + has_one :essay_category_2, through: :essay_2, source: :category + + has_many :essays, primary_key: :name, as: :writer + has_many :essay_categories, through: :essays, source: :category + has_many :essay_owners, through: :essays, source: :owner + + has_many :essays_2, primary_key: :name, class_name: "Essay", foreign_key: :author_id + has_many :essay_categories_2, through: :essays_2, source: :category + + belongs_to :owned_essay, primary_key: :name, class_name: "Essay" + has_one :owned_essay_category, through: :owned_essay, source: :category + + belongs_to :author_address, dependent: :destroy + belongs_to :author_address_extra, dependent: :delete, class_name: "AuthorAddress" + + has_many :category_post_comments, through: :categories, source: :post_comments + + has_many :misc_posts, -> { where(posts: { title: ["misc post by bob", "misc post by mary"] }) }, class_name: "Post" + has_many :misc_post_first_blue_tags, through: :misc_posts, source: :first_blue_tags + + has_many :misc_post_first_blue_tags_2, -> { where(posts: { title: ["misc post by bob", "misc post by mary"] }) }, + through: :posts, source: :first_blue_tags_2 + + has_many :posts_with_default_include, class_name: "PostWithDefaultInclude" + has_many :comments_on_posts_with_default_include, through: :posts_with_default_include, source: :comments + + has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post" + + has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do + def extension_method; end + end + + has_many :posts_with_extension_and_instance, ->(record) { order(:title) }, class_name: "Post" do + def extension_method; end + end + + attr_accessor :post_log + after_initialize :set_post_log + + def set_post_log + @post_log = [] + end + + def label + "#{id}-#{name}" + end + + def social + %w(twitter github) + end + + validates_presence_of :name + + private + def log_before_adding(object) + @post_log << "before_adding#{object.id || '<new>'}" + end + + def log_after_adding(object) + @post_log << "after_adding#{object.id}" + end + + def log_before_removing(object) + @post_log << "before_removing#{object.id}" + end + + def log_after_removing(object) + @post_log << "after_removing#{object.id}" + end + + def raise_exception(object) + raise Exception.new("You can't add a post") + end +end + +class AuthorAddress < ActiveRecord::Base + has_one :author + + def self.destroyed_author_address_ids + @destroyed_author_address_ids ||= [] + end + + before_destroy do |author_address| + AuthorAddress.destroyed_author_address_ids << author_address.id + end +end + +class AuthorFavorite < ActiveRecord::Base + belongs_to :author + belongs_to :favorite_author, class_name: "Author" +end diff --git a/activerecord/test/models/auto_id.rb b/activerecord/test/models/auto_id.rb new file mode 100644 index 0000000000..fd672603bb --- /dev/null +++ b/activerecord/test/models/auto_id.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class AutoId < ActiveRecord::Base + self.table_name = "auto_id_tests" + self.primary_key = "auto_id" +end diff --git a/activerecord/test/models/autoloadable/extra_firm.rb b/activerecord/test/models/autoloadable/extra_firm.rb new file mode 100644 index 0000000000..c46e34c101 --- /dev/null +++ b/activerecord/test/models/autoloadable/extra_firm.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ExtraFirm < Company +end diff --git a/activerecord/test/models/binary.rb b/activerecord/test/models/binary.rb new file mode 100644 index 0000000000..b93f87519f --- /dev/null +++ b/activerecord/test/models/binary.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Binary < ActiveRecord::Base +end diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb new file mode 100644 index 0000000000..be08636ac6 --- /dev/null +++ b/activerecord/test/models/bird.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class Bird < ActiveRecord::Base + belongs_to :pirate + validates_presence_of :name + + accepts_nested_attributes_for :pirate + + attr_accessor :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback + def cancel_save_callback_method + throw(:abort) + end +end diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb new file mode 100644 index 0000000000..afdda1a81e --- /dev/null +++ b/activerecord/test/models/book.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class Book < ActiveRecord::Base + belongs_to :author + + has_many :citations, foreign_key: "book1_id" + has_many :references, -> { distinct }, through: :citations, source: :reference_of + + has_many :subscriptions + has_many :subscribers, through: :subscriptions + + enum status: [:proposed, :written, :published] + enum read_status: { unread: 0, reading: 2, read: 3, forgotten: nil } + enum nullable_status: [:single, :married] + enum language: [:english, :spanish, :french], _prefix: :in + enum author_visibility: [:visible, :invisible], _prefix: true + enum illustrator_visibility: [:visible, :invisible], _prefix: true + enum font_size: [:small, :medium, :large], _prefix: :with, _suffix: true + enum difficulty: [:easy, :medium, :hard], _suffix: :to_read + enum cover: { hard: "hard", soft: "soft" } + + def published! + super + "do publish work..." + end +end diff --git a/activerecord/test/models/boolean.rb b/activerecord/test/models/boolean.rb new file mode 100644 index 0000000000..bee757fb9c --- /dev/null +++ b/activerecord/test/models/boolean.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Boolean < ActiveRecord::Base + def has_fun + super + end +end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb new file mode 100644 index 0000000000..ab92f7025d --- /dev/null +++ b/activerecord/test/models/bulb.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Bulb < ActiveRecord::Base + default_scope { where(name: "defaulty") } + belongs_to :car, touch: true + scope :awesome, -> { where(frickinawesome: true) } + + attr_reader :scope_after_initialize, :attributes_after_initialize + + after_initialize :record_scope_after_initialize + def record_scope_after_initialize + @scope_after_initialize = self.class.all + end + + after_initialize :record_attributes_after_initialize + def record_attributes_after_initialize + @attributes_after_initialize = attributes.dup + end + + def color=(color) + self[:color] = color.upcase + "!" + end + + def self.new(attributes = {}, &block) + bulb_type = (attributes || {}).delete(:bulb_type) + + if bulb_type.present? + bulb_class = "#{bulb_type.to_s.camelize}Bulb".constantize + bulb_class.new(attributes, &block) + else + super + end + end +end + +class CustomBulb < Bulb + after_initialize :set_awesomeness + + def set_awesomeness + self.frickinawesome = true if name == "Dude" + end +end + +class FunkyBulb < Bulb + before_destroy do + raise "before_destroy was called" + end +end + +class FailedBulb < Bulb + before_destroy do + throw(:abort) + end +end diff --git a/activerecord/test/models/cake_designer.rb b/activerecord/test/models/cake_designer.rb new file mode 100644 index 0000000000..0b2a00edfd --- /dev/null +++ b/activerecord/test/models/cake_designer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class CakeDesigner < ActiveRecord::Base + has_one :chef, as: :employable +end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb new file mode 100644 index 0000000000..3d6a7a96c2 --- /dev/null +++ b/activerecord/test/models/car.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Car < ActiveRecord::Base + has_many :bulbs + has_many :all_bulbs, -> { unscope where: :name }, class_name: "Bulb" + has_many :funky_bulbs, class_name: "FunkyBulb", dependent: :destroy + has_many :failed_bulbs, class_name: "FailedBulb", dependent: :destroy + has_many :foo_bulbs, -> { where(name: "foo") }, class_name: "Bulb" + has_many :awesome_bulbs, -> { awesome }, class_name: "Bulb" + + has_one :bulb + + has_many :tyres + has_many :engines, dependent: :destroy, inverse_of: :my_car + has_many :wheels, as: :wheelable, dependent: :destroy + + has_many :price_estimates, as: :estimate_of + + scope :incl_tyres, -> { includes(:tyres) } + scope :incl_engines, -> { includes(:engines) } + + scope :order_using_new_style, -> { order("name asc") } +end + +class CoolCar < Car + default_scope { order("name desc") } +end + +class FastCar < Car + default_scope { order("name desc") } +end diff --git a/activerecord/test/models/carrier.rb b/activerecord/test/models/carrier.rb new file mode 100644 index 0000000000..995a9d3bef --- /dev/null +++ b/activerecord/test/models/carrier.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Carrier < ActiveRecord::Base +end diff --git a/activerecord/test/models/cat.rb b/activerecord/test/models/cat.rb new file mode 100644 index 0000000000..43013964b6 --- /dev/null +++ b/activerecord/test/models/cat.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Cat < ActiveRecord::Base + self.abstract_class = true + + enum gender: [:female, :male] + + default_scope -> { where(is_vegetarian: false) } +end + +class Lion < Cat +end diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb new file mode 100644 index 0000000000..68b0ea90d3 --- /dev/null +++ b/activerecord/test/models/categorization.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Categorization < ActiveRecord::Base + belongs_to :post + belongs_to :category, counter_cache: true + belongs_to :named_category, class_name: "Category", foreign_key: :named_category_name, primary_key: :name + belongs_to :author + + has_many :post_taggings, through: :author, source: :taggings + + belongs_to :author_using_custom_pk, class_name: "Author", foreign_key: :author_id, primary_key: :author_address_extra_id + has_many :authors_using_custom_pk, class_name: "Author", foreign_key: :id, primary_key: :category_id +end + +class SpecialCategorization < ActiveRecord::Base + self.table_name = "categorizations" + default_scope { where(special: true) } + + belongs_to :author + belongs_to :category +end diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb new file mode 100644 index 0000000000..2ccc00bed9 --- /dev/null +++ b/activerecord/test/models/category.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class Category < ActiveRecord::Base + has_and_belongs_to_many :posts + has_and_belongs_to_many :special_posts, class_name: "Post" + has_and_belongs_to_many :other_posts, class_name: "Post" + has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, -> { includes(:authors).order("authors.id") }, class_name: "Post" + + has_and_belongs_to_many :select_testing_posts, + -> { select "posts.*, 1 as correctness_marker" }, + class_name: "Post", + foreign_key: "category_id", + association_foreign_key: "post_id" + + has_and_belongs_to_many :post_with_conditions, + -> { where title: "Yet Another Testing Title" }, + class_name: "Post" + + has_and_belongs_to_many :popular_grouped_posts, -> { group("posts.type").having("sum(comments.post_id) > 2").includes(:comments) }, class_name: "Post" + has_and_belongs_to_many :posts_grouped_by_title, -> { group("title").select("title") }, class_name: "Post" + + def self.what_are_you + "a category..." + end + + has_many :categorizations + has_many :special_categorizations + has_many :post_comments, through: :posts, source: :comments + + has_many :authors, through: :categorizations + has_many :authors_with_select, -> { select "authors.*, categorizations.post_id" }, through: :categorizations, source: :author + + scope :general, -> { where(name: "General") } + + # Should be delegated `ast` and `locked` to `arel`. + def self.ast + raise + end + + def self.locked + raise + end +end + +class SpecialCategory < Category +end diff --git a/activerecord/test/models/chef.rb b/activerecord/test/models/chef.rb new file mode 100644 index 0000000000..ff528644bc --- /dev/null +++ b/activerecord/test/models/chef.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Chef < ActiveRecord::Base + belongs_to :employable, polymorphic: true + has_many :recipes +end + +class ChefList < Chef + belongs_to :employable_list, polymorphic: true +end diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb new file mode 100644 index 0000000000..3d786f27eb --- /dev/null +++ b/activerecord/test/models/citation.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Citation < ActiveRecord::Base + belongs_to :reference_of, class_name: "Book", foreign_key: :book2_id +end diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb new file mode 100644 index 0000000000..2006e05fcf --- /dev/null +++ b/activerecord/test/models/club.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class Club < ActiveRecord::Base + has_one :membership + has_many :memberships, inverse_of: false + has_many :members, through: :memberships + has_one :sponsor + has_one :sponsored_member, through: :sponsor, source: :sponsorable, source_type: "Member" + belongs_to :category + + has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member + + scope :general, -> { left_joins(:category).where(categories: { name: "General" }) } + + private + + def private_method + "I'm sorry sir, this is a *private* club, not a *pirate* club" + end +end + +class SuperClub < ActiveRecord::Base + self.table_name = "clubs" + + has_many :memberships, class_name: "SuperMembership", foreign_key: "club_id" + has_many :members, through: :memberships +end diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb new file mode 100644 index 0000000000..52017dda42 --- /dev/null +++ b/activerecord/test/models/college.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require_dependency "models/arunit2_model" +require "active_support/core_ext/object/with_options" + +class College < ARUnit2Model + has_many :courses + + with_options dependent: :destroy do |assoc| + assoc.has_many :students, -> { where(active: true) } + end +end diff --git a/activerecord/test/models/column.rb b/activerecord/test/models/column.rb new file mode 100644 index 0000000000..d3cd419a00 --- /dev/null +++ b/activerecord/test/models/column.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Column < ActiveRecord::Base + belongs_to :record +end diff --git a/activerecord/test/models/column_name.rb b/activerecord/test/models/column_name.rb new file mode 100644 index 0000000000..c6047c507b --- /dev/null +++ b/activerecord/test/models/column_name.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ColumnName < ActiveRecord::Base + self.table_name = "colnametests" +end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb new file mode 100644 index 0000000000..f0f0576709 --- /dev/null +++ b/activerecord/test/models/comment.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +# `counter_cache` requires association class before `attr_readonly`. +class Post < ActiveRecord::Base; end + +class Comment < ActiveRecord::Base + scope :limit_by, lambda { |l| limit(l) } + scope :containing_the_letter_e, -> { where("comments.body LIKE '%e%'") } + scope :not_again, -> { where("comments.body NOT LIKE '%again%'") } + scope :for_first_post, -> { where(post_id: 1) } + scope :for_first_author, -> { joins(:post).where("posts.author_id" => 1) } + scope :created, -> { all } + + belongs_to :post, counter_cache: true + belongs_to :author, polymorphic: true + belongs_to :resource, polymorphic: true + + has_many :ratings + + belongs_to :first_post, foreign_key: :post_id + belongs_to :special_post_with_default_scope, foreign_key: :post_id + + has_many :children, class_name: "Comment", foreign_key: :parent_id + belongs_to :parent, class_name: "Comment", counter_cache: :children_count + + class ::OopsError < RuntimeError; end + + module OopsExtension + def destroy_all(*) + raise OopsError + end + end + + default_scope { extending OopsExtension } + + scope :oops_comments, -> { extending OopsExtension } + + # Should not be called if extending modules that having the method exists on an association. + def self.greeting + raise + end + + def self.what_are_you + "a comment..." + end + + def self.search_by_type(q) + where("#{QUOTED_TYPE} = ?", q) + end + + def self.all_as_method + all + end + scope :all_as_scope, -> { all } + + def to_s + body + end +end + +class SpecialComment < Comment + default_scope { where(deleted_at: nil) } + + def self.what_are_you + "a special comment..." + end +end + +class SubSpecialComment < SpecialComment +end + +class VerySpecialComment < Comment +end + +class CommentThatAutomaticallyAltersPostBody < Comment + belongs_to :post, class_name: "PostThatLoadsCommentsInAnAfterSaveHook", foreign_key: :post_id + + after_save do |comment| + comment.post.update(body: "Automatically altered") + end +end + +class CommentWithDefaultScopeReferencesAssociation < Comment + default_scope -> { includes(:developer).order("developers.name").references(:developer) } + belongs_to :developer +end + +class CommentWithAfterCreateUpdate < Comment + after_create do + update(body: "bar") + end +end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb new file mode 100644 index 0000000000..fc6488f729 --- /dev/null +++ b/activerecord/test/models/company.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +class AbstractCompany < ActiveRecord::Base + self.abstract_class = true +end + +class Company < AbstractCompany + self.sequence_name = :companies_nonstd_seq + + validates_presence_of :name + + has_one :dummy_account, foreign_key: "firm_id", class_name: "Account" + has_many :contracts + has_many :developers, through: :contracts + + scope :of_first_firm, lambda { + joins(account: :firm). + where("firms.id" => 1) + } + + def arbitrary_method + "I am Jack's profound disappointment" + end + + private + + def private_method + "I am Jack's innermost fears and aspirations" + end + + class SpecialCo < Company + end +end + +module Namespaced + class Company < ::Company + end + + class Firm < ::Company + has_many :clients, class_name: "Namespaced::Client" + end + + class Client < ::Company + end +end + +class Firm < Company + to_param :name + + has_many :clients, -> { order "id" }, dependent: :destroy, before_remove: :log_before_remove, after_remove: :log_after_remove + has_many :unsorted_clients, class_name: "Client" + has_many :unsorted_clients_with_symbol, class_name: :Client + has_many :clients_sorted_desc, -> { order "id DESC" }, class_name: "Client" + has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", inverse_of: :firm + has_many :clients_ordered_by_name, -> { order "name" }, class_name: "Client" + has_many :unvalidated_clients_of_firm, foreign_key: "client_of", class_name: "Client", validate: false + has_many :dependent_clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", dependent: :destroy + has_many :exclusively_dependent_clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all + has_many :limited_clients, -> { limit 1 }, class_name: "Client" + has_many :clients_with_interpolated_conditions, ->(firm) { where "rating > #{firm.rating}" }, class_name: "Client" + has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, class_name: "Client" + has_many :clients_like_ms_with_hash_conditions, -> { where(name: "Microsoft").order("id") }, class_name: "Client" + has_many :plain_clients, class_name: "Client" + has_many :clients_using_primary_key, class_name: "Client", + primary_key: "name", foreign_key: "firm_name" + has_many :clients_using_primary_key_with_delete_all, class_name: "Client", + primary_key: "name", foreign_key: "firm_name", dependent: :delete_all + has_many :clients_grouped_by_firm_id, -> { group("firm_id").select("firm_id") }, class_name: "Client" + has_many :clients_grouped_by_name, -> { group("name").select("name") }, class_name: "Client" + + has_one :account, foreign_key: "firm_id", dependent: :destroy, validate: true + has_one :unvalidated_account, foreign_key: "firm_id", class_name: "Account", validate: false + has_one :account_with_select, -> { select("id, firm_id") }, foreign_key: "firm_id", class_name: "Account" + has_one :readonly_account, -> { readonly }, foreign_key: "firm_id", class_name: "Account" + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account_using_primary_key, -> { order("id") }, primary_key: "firm_id", class_name: "Account" + has_one :account_using_foreign_and_primary_keys, foreign_key: "firm_name", primary_key: "name", class_name: "Account" + has_one :account_with_inexistent_foreign_key, class_name: "Account", foreign_key: "inexistent" + has_one :deletable_account, foreign_key: "firm_id", class_name: "Account", dependent: :delete + + has_one :account_limit_500_with_hash_conditions, -> { where credit_limit: 500 }, foreign_key: "firm_id", class_name: "Account" + + has_one :unautosaved_account, foreign_key: "firm_id", class_name: "Account", autosave: false + has_many :accounts + has_many :unautosaved_accounts, foreign_key: "firm_id", class_name: "Account", autosave: false + + has_many :association_with_references, -> { references(:foo) }, class_name: "Client" + + has_many :developers_with_select, -> { select("id, name, first_name") }, class_name: "Developer" + + has_one :lead_developer, class_name: "Developer" + has_many :projects + + def log + @log ||= [] + end + + private + def log_before_remove(record) + log << "before_remove#{record.id}" + end + + def log_after_remove(record) + log << "after_remove#{record.id}" + end +end + +class DependentFirm < Company + has_one :account, foreign_key: "firm_id", dependent: :nullify + has_many :companies, foreign_key: "client_of", dependent: :nullify + has_one :company, foreign_key: "client_of", dependent: :nullify +end + +class RestrictedWithExceptionFirm < Company + has_one :account, -> { order("id") }, foreign_key: "firm_id", dependent: :restrict_with_exception + has_many :companies, -> { order("id") }, foreign_key: "client_of", dependent: :restrict_with_exception +end + +class RestrictedWithErrorFirm < Company + has_one :account, -> { order("id") }, foreign_key: "firm_id", dependent: :restrict_with_error + has_many :companies, -> { order("id") }, foreign_key: "client_of", dependent: :restrict_with_error +end + +class Client < Company + belongs_to :firm, foreign_key: "client_of" + belongs_to :firm_with_basic_id, class_name: "Firm", foreign_key: "firm_id" + belongs_to :firm_with_select, -> { select("id") }, class_name: "Firm", foreign_key: "firm_id" + belongs_to :firm_with_other_name, class_name: "Firm", foreign_key: "client_of" + belongs_to :firm_with_condition, -> { where "1 = ?", 1 }, class_name: "Firm", foreign_key: "client_of" + belongs_to :firm_with_primary_key, class_name: "Firm", primary_key: "name", foreign_key: "firm_name" + belongs_to :firm_with_primary_key_symbols, class_name: "Firm", primary_key: :name, foreign_key: :firm_name + belongs_to :readonly_firm, -> { readonly }, class_name: "Firm", foreign_key: "firm_id" + belongs_to :bob_firm, -> { where name: "Bob" }, class_name: "Firm", foreign_key: "client_of" + has_many :accounts, through: :firm, source: :accounts + belongs_to :account + + validate do + firm + end + + class RaisedOnSave < RuntimeError; end + attr_accessor :raise_on_save + before_save do + raise RaisedOnSave if raise_on_save + end + + class RaisedOnDestroy < RuntimeError; end + attr_accessor :raise_on_destroy + before_destroy do + raise RaisedOnDestroy if raise_on_destroy + end + + # Record destruction so we can test whether firm.clients.clear has + # is calling client.destroy, deleting from the database, or setting + # foreign keys to NULL. + def self.destroyed_client_ids + @destroyed_client_ids ||= Hash.new { |h, k| h[k] = [] } + end + + before_destroy do |client| + if client.firm + Client.destroyed_client_ids[client.firm.id] << client.id + end + true + end + + before_destroy :overwrite_to_raise + + # Used to test that read and question methods are not generated for these attributes + def rating? + query_attribute :rating + end + + def overwrite_to_raise + end +end + +class ExclusivelyDependentFirm < Company + has_one :account, foreign_key: "firm_id", dependent: :delete + has_many :dependent_sanitized_conditional_clients_of_firm, -> { order("id").where("name = 'BigShot Inc.'") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all + has_many :dependent_hash_conditional_clients_of_firm, -> { order("id").where(name: "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all + has_many :dependent_conditional_clients_of_firm, -> { order("id").where("name = ?", "BigShot Inc.") }, foreign_key: "client_of", class_name: "Client", dependent: :delete_all +end + +class SpecialClient < Client +end + +class VerySpecialClient < SpecialClient +end + +require "models/account" diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb new file mode 100644 index 0000000000..52b7e06a63 --- /dev/null +++ b/activerecord/test/models/company_in_module.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/with_options" + +module MyApplication + module Business + class Company < ActiveRecord::Base + end + + class Firm < Company + has_many :clients, -> { order("id") }, dependent: :destroy + has_many :clients_sorted_desc, -> { order("id DESC") }, class_name: "Client" + has_many :clients_of_firm, -> { order "id" }, foreign_key: "client_of", class_name: "Client" + has_many :clients_like_ms, -> { where("name = 'Microsoft'").order("id") }, class_name: "Client" + has_one :account, class_name: "MyApplication::Billing::Account", dependent: :destroy + end + + class Client < Company + belongs_to :firm, foreign_key: "client_of" + belongs_to :firm_with_other_name, class_name: "Firm", foreign_key: "client_of" + + class Contact < ActiveRecord::Base; end + end + + class Developer < ActiveRecord::Base + has_and_belongs_to_many :projects + validates_length_of :name, within: (3..20) + end + + class Project < ActiveRecord::Base + has_and_belongs_to_many :developers + end + + module Prefixed + def self.table_name_prefix + "prefixed_" + end + + class Company < ActiveRecord::Base + end + + class Firm < Company + self.table_name = "companies" + end + + module Nested + class Company < ActiveRecord::Base + end + end + end + + module Suffixed + def self.table_name_suffix + "_suffixed" + end + + class Company < ActiveRecord::Base + end + + class Firm < Company + self.table_name = "companies" + end + + module Nested + class Company < ActiveRecord::Base + end + end + end + end + + module Billing + class Firm < ActiveRecord::Base + self.table_name = "companies" + end + + module Nested + class Firm < ActiveRecord::Base + self.table_name = "companies" + end + end + + class Account < ActiveRecord::Base + with_options(foreign_key: :firm_id) do |i| + i.belongs_to :firm, class_name: "MyApplication::Business::Firm" + i.belongs_to :qualified_billing_firm, class_name: "MyApplication::Billing::Firm" + i.belongs_to :unqualified_billing_firm, class_name: "Firm" + i.belongs_to :nested_qualified_billing_firm, class_name: "MyApplication::Billing::Nested::Firm" + i.belongs_to :nested_unqualified_billing_firm, class_name: "Nested::Firm" + end + + validate :check_empty_credit_limit + + private + + def check_empty_credit_limit + errors.add("credit_card", :blank) if credit_card.blank? + end + end + end +end diff --git a/activerecord/test/models/computer.rb b/activerecord/test/models/computer.rb new file mode 100644 index 0000000000..582b4a38b5 --- /dev/null +++ b/activerecord/test/models/computer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Computer < ActiveRecord::Base + belongs_to :developer, foreign_key: "developer" +end diff --git a/activerecord/test/models/contact.rb b/activerecord/test/models/contact.rb new file mode 100644 index 0000000000..6e02ff199b --- /dev/null +++ b/activerecord/test/models/contact.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ContactFakeColumns + def self.extended(base) + base.class_eval do + establish_connection(adapter: "fake") + + connection.data_sources = [table_name] + connection.primary_keys = { + table_name => "id" + } + + column :id, :integer + column :name, :string + column :age, :integer + column :avatar, :binary + column :created_at, :datetime + column :awesome, :boolean + column :preferences, :string + column :alternative_id, :integer + + serialize :preferences + + belongs_to :alternative, class_name: "Contact" + end + end + + # mock out self.columns so no pesky db is needed for these tests + def column(name, sql_type = nil, options = {}) + connection.merge_column(table_name, name, sql_type, options) + end +end + +class Contact < ActiveRecord::Base + extend ContactFakeColumns +end + +class ContactSti < ActiveRecord::Base + extend ContactFakeColumns + column :type, :string + + def type; "ContactSti" end +end diff --git a/activerecord/test/models/content.rb b/activerecord/test/models/content.rb new file mode 100644 index 0000000000..14bbee53d8 --- /dev/null +++ b/activerecord/test/models/content.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class Content < ActiveRecord::Base + self.table_name = "content" + has_one :content_position, dependent: :destroy + + def self.destroyed_ids + @destroyed_ids ||= [] + end + + before_destroy do |object| + Content.destroyed_ids << object.id + end +end + +class ContentWhichRequiresTwoDestroyCalls < ActiveRecord::Base + self.table_name = "content" + has_one :content_position, foreign_key: "content_id", dependent: :destroy + + after_initialize do + @destroy_count = 0 + end + + before_destroy do + @destroy_count += 1 + if @destroy_count == 1 + throw :abort + end + end +end + +class ContentPosition < ActiveRecord::Base + belongs_to :content, dependent: :destroy + + def self.destroyed_ids + @destroyed_ids ||= [] + end + + before_destroy do |object| + ContentPosition.destroyed_ids << object.id + end +end diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb new file mode 100644 index 0000000000..f273badd85 --- /dev/null +++ b/activerecord/test/models/contract.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class Contract < ActiveRecord::Base + belongs_to :company + belongs_to :developer, primary_key: :id + belongs_to :firm, foreign_key: "company_id" + + before_save :hi + after_save :bye + + attr_accessor :hi_count, :bye_count + + def hi + @hi_count ||= 0 + @hi_count += 1 + end + + def bye + @bye_count ||= 0 + @bye_count += 1 + end +end diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb new file mode 100644 index 0000000000..0c84a40de2 --- /dev/null +++ b/activerecord/test/models/country.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Country < ActiveRecord::Base + self.primary_key = :country_id + + has_and_belongs_to_many :treaties +end diff --git a/activerecord/test/models/course.rb b/activerecord/test/models/course.rb new file mode 100644 index 0000000000..4f346124ea --- /dev/null +++ b/activerecord/test/models/course.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require_dependency "models/arunit2_model" + +class Course < ARUnit2Model + belongs_to :college + has_many :entrants +end diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb new file mode 100644 index 0000000000..bc501a5ce0 --- /dev/null +++ b/activerecord/test/models/customer.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +class Customer < ActiveRecord::Base + cattr_accessor :gps_conversion_was_run + + composed_of :address, mapping: [ %w(address_street street), %w(address_city city), %w(address_country country) ], allow_nil: true + composed_of :balance, class_name: "Money", mapping: %w(balance amount) + composed_of :gps_location, allow_nil: true + composed_of :non_blank_gps_location, class_name: "GpsLocation", allow_nil: true, mapping: %w(gps_location gps_location), + converter: lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps) } + composed_of :fullname, mapping: %w(name to_s), constructor: Proc.new { |name| Fullname.parse(name) }, converter: :parse + composed_of :fullname_no_converter, mapping: %w(name to_s), class_name: "Fullname" +end + +class Address + attr_reader :street, :city, :country + + def initialize(street, city, country) + @street, @city, @country = street, city, country + end + + def close_to?(other_address) + city == other_address.city && country == other_address.country + end + + def ==(other) + other.is_a?(self.class) && other.street == street && other.city == city && other.country == country + end +end + +class Money + attr_reader :amount, :currency + + EXCHANGE_RATES = { "USD_TO_DKK" => 6, "DKK_TO_USD" => 0.6 } + + def initialize(amount, currency = "USD") + @amount, @currency = amount, currency + end + + def exchange_to(other_currency) + Money.new((amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor, other_currency) + end +end + +class GpsLocation + attr_reader :gps_location + + def initialize(gps_location) + @gps_location = gps_location + end + + def latitude + gps_location.split("x").first + end + + def longitude + gps_location.split("x").last + end + + def ==(other) + latitude == other.latitude && longitude == other.longitude + end +end + +class Fullname + attr_reader :first, :last + + def self.parse(str) + return nil unless str + + if str.is_a?(Hash) + new(str[:first], str[:last]) + else + new(*str.to_s.split) + end + end + + def initialize(first, last = nil) + @first, @last = first, last + end + + def to_s + "#{first} #{last.upcase}" + end +end diff --git a/activerecord/test/models/customer_carrier.rb b/activerecord/test/models/customer_carrier.rb new file mode 100644 index 0000000000..6cb9d5239d --- /dev/null +++ b/activerecord/test/models/customer_carrier.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class CustomerCarrier < ActiveRecord::Base + cattr_accessor :current_customer + + belongs_to :customer + belongs_to :carrier + + default_scope -> { + if current_customer + where(customer: current_customer) + else + all + end + } +end diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb new file mode 100644 index 0000000000..d25ceeafb1 --- /dev/null +++ b/activerecord/test/models/dashboard.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Dashboard < ActiveRecord::Base + self.primary_key = :dashboard_id +end diff --git a/activerecord/test/models/default.rb b/activerecord/test/models/default.rb new file mode 100644 index 0000000000..90f1046d87 --- /dev/null +++ b/activerecord/test/models/default.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Default < ActiveRecord::Base +end diff --git a/activerecord/test/models/department.rb b/activerecord/test/models/department.rb new file mode 100644 index 0000000000..868b9bf4bf --- /dev/null +++ b/activerecord/test/models/department.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Department < ActiveRecord::Base + has_many :chefs + belongs_to :hotel +end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb new file mode 100644 index 0000000000..8881c69368 --- /dev/null +++ b/activerecord/test/models/developer.rb @@ -0,0 +1,281 @@ +# frozen_string_literal: true + +require "ostruct" + +module DeveloperProjectsAssociationExtension2 + def find_least_recent + order("id ASC").first + end +end + +class Developer < ActiveRecord::Base + self.ignored_columns = %w(first_name last_name) + + has_and_belongs_to_many :projects do + def find_most_recent + order("id DESC").first + end + end + + belongs_to :mentor + + accepts_nested_attributes_for :projects + + has_and_belongs_to_many :shared_computers, class_name: "Computer" + + has_and_belongs_to_many :projects_extended_by_name, + -> { extending(DeveloperProjectsAssociationExtension) }, + class_name: "Project", + join_table: "developers_projects", + association_foreign_key: "project_id" + + has_and_belongs_to_many :projects_extended_by_name_twice, + -> { extending(DeveloperProjectsAssociationExtension, DeveloperProjectsAssociationExtension2) }, + class_name: "Project", + join_table: "developers_projects", + association_foreign_key: "project_id" + + has_and_belongs_to_many :projects_extended_by_name_and_block, + -> { extending(DeveloperProjectsAssociationExtension) }, + class_name: "Project", + join_table: "developers_projects", + association_foreign_key: "project_id" do + def find_least_recent + order("id ASC").first + end + end + + has_and_belongs_to_many :special_projects, join_table: "developers_projects", association_foreign_key: "project_id" + has_and_belongs_to_many :sym_special_projects, + join_table: :developers_projects, + association_foreign_key: "project_id", + class_name: "SpecialProject" + + has_many :audit_logs + has_many :contracts + has_many :firms, through: :contracts, source: :firm + has_many :comments, ->(developer) { where(body: "I'm #{developer.name}") } + has_many :ratings, through: :comments + has_one :ship, dependent: :nullify + + belongs_to :firm + has_many :contracted_projects, class_name: "Project" + + scope :jamises, -> { where(name: "Jamis") } + + validates_inclusion_of :salary, in: 50000..200000 + validates_length_of :name, within: 3..20 + + before_create do |developer| + developer.audit_logs.build message: "Computer created" + end + + attr_accessor :last_name + define_attribute_method "last_name" + + def log=(message) + audit_logs.build message: message + end + + after_find :track_instance_count + cattr_accessor :instance_count + + def track_instance_count + self.class.instance_count ||= 0 + self.class.instance_count += 1 + end + private :track_instance_count +end + +class SubDeveloper < Developer +end + +class SymbolIgnoredDeveloper < ActiveRecord::Base + self.table_name = "developers" + self.ignored_columns = [:first_name, :last_name] + + attr_accessor :last_name + define_attribute_method "last_name" +end + +class AuditLog < ActiveRecord::Base + belongs_to :developer, validate: true + belongs_to :unvalidated_developer, class_name: "Developer" +end + +class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, join_table: "developers_projects", foreign_key: "developer_id" + before_destroy :raise_if_projects_empty! + + def raise_if_projects_empty! + raise if projects.empty? + end +end + +class DeveloperWithSelect < ActiveRecord::Base + self.table_name = "developers" + default_scope { select("name") } +end + +class DeveloperWithIncludes < ActiveRecord::Base + self.table_name = "developers" + has_many :audit_logs, foreign_key: :developer_id + default_scope { includes(:audit_logs) } +end + +class DeveloperFilteredOnJoins < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + + def self.default_scope + joins(:projects).where(projects: { name: "Active Controller" }) + end +end + +class DeveloperOrderedBySalary < ActiveRecord::Base + self.table_name = "developers" + default_scope { order("salary DESC") } + + scope :by_name, -> { order("name DESC") } +end + +class DeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + default_scope { where("name = 'David'") } +end + +class LazyLambdaDeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + default_scope lambda { where(name: "David") } +end + +class LazyBlockDeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + default_scope { where(name: "David") } +end + +class CallableDeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + default_scope OpenStruct.new(call: where(name: "David")) +end + +class ClassMethodDeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + + def self.default_scope + where(name: "David") + end +end + +class ClassMethodReferencingScopeDeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + scope :david, -> { where(name: "David") } + + def self.default_scope + david + end +end + +class LazyBlockReferencingScopeDeveloperCalledDavid < ActiveRecord::Base + self.table_name = "developers" + scope :david, -> { where(name: "David") } + default_scope { david } +end + +class DeveloperCalledJamis < ActiveRecord::Base + self.table_name = "developers" + + default_scope { where(name: "Jamis") } + scope :poor, -> { where("salary < 150000") } + scope :david, -> { where name: "David" } + scope :david2, -> { unscoped.where name: "David" } +end + +class PoorDeveloperCalledJamis < ActiveRecord::Base + self.table_name = "developers" + + default_scope -> { where(name: "Jamis", salary: 50000) } +end + +class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis + self.table_name = "developers" + + default_scope -> { where(salary: 50000) } +end + +class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base + self.table_name = "developers" + + default_scope -> { where(name: "Jamis") } + default_scope -> { where(salary: 50000) } +end + +module SalaryDefaultScope + extend ActiveSupport::Concern + + included { default_scope { where(salary: 50000) } } +end + +class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis + self.table_name = "developers" + + include SalaryDefaultScope +end + +class EagerDeveloperWithDefaultScope < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + + default_scope { includes(:projects) } +end + +class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + + def self.default_scope + includes(:projects) + end +end + +class EagerDeveloperWithLambdaDefaultScope < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + + default_scope lambda { includes(:projects) } +end + +class EagerDeveloperWithBlockDefaultScope < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + + default_scope { includes(:projects) } +end + +class EagerDeveloperWithCallableDefaultScope < ActiveRecord::Base + self.table_name = "developers" + has_and_belongs_to_many :projects, -> { order("projects.id") }, foreign_key: "developer_id", join_table: "developers_projects" + + default_scope OpenStruct.new(call: includes(:projects)) +end + +class ThreadsafeDeveloper < ActiveRecord::Base + self.table_name = "developers" + + def self.default_scope + Thread.current[:default_scope_delay].call + limit(1) + end +end + +class CachedDeveloper < ActiveRecord::Base + self.table_name = "developers" + self.cache_timestamp_format = :number +end + +class DeveloperWithIncorrectlyOrderedHasManyThrough < ActiveRecord::Base + self.table_name = "developers" + has_many :companies, through: :contracts + has_many :contracts, foreign_key: :developer_id +end diff --git a/activerecord/test/models/dog.rb b/activerecord/test/models/dog.rb new file mode 100644 index 0000000000..75d284ac25 --- /dev/null +++ b/activerecord/test/models/dog.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Dog < ActiveRecord::Base + belongs_to :breeder, class_name: "DogLover", counter_cache: :bred_dogs_count + belongs_to :trainer, class_name: "DogLover", counter_cache: :trained_dogs_count + belongs_to :doglover, foreign_key: :dog_lover_id, class_name: "DogLover", counter_cache: true +end diff --git a/activerecord/test/models/dog_lover.rb b/activerecord/test/models/dog_lover.rb new file mode 100644 index 0000000000..aabe914f77 --- /dev/null +++ b/activerecord/test/models/dog_lover.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class DogLover < ActiveRecord::Base + has_many :trained_dogs, class_name: "Dog", foreign_key: :trainer_id, dependent: :destroy + has_many :bred_dogs, class_name: "Dog", foreign_key: :breeder_id + has_many :dogs +end diff --git a/activerecord/test/models/doubloon.rb b/activerecord/test/models/doubloon.rb new file mode 100644 index 0000000000..febadc3a5a --- /dev/null +++ b/activerecord/test/models/doubloon.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class AbstractDoubloon < ActiveRecord::Base + # This has functionality that might be shared by multiple classes. + + self.abstract_class = true + belongs_to :pirate +end + +class Doubloon < AbstractDoubloon + # This uses an abstract class that defines attributes and associations. + + self.table_name = "doubloons" +end diff --git a/activerecord/test/models/drink_designer.rb b/activerecord/test/models/drink_designer.rb new file mode 100644 index 0000000000..eb6701b84e --- /dev/null +++ b/activerecord/test/models/drink_designer.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class DrinkDesigner < ActiveRecord::Base + has_one :chef, as: :employable +end + +class MocktailDesigner < DrinkDesigner +end diff --git a/activerecord/test/models/edge.rb b/activerecord/test/models/edge.rb new file mode 100644 index 0000000000..a04ab103de --- /dev/null +++ b/activerecord/test/models/edge.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# This class models an edge in a directed graph. +class Edge < ActiveRecord::Base + belongs_to :source, class_name: "Vertex", foreign_key: "source_id" + belongs_to :sink, class_name: "Vertex", foreign_key: "sink_id" +end diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb new file mode 100644 index 0000000000..902006b314 --- /dev/null +++ b/activerecord/test/models/electron.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Electron < ActiveRecord::Base + belongs_to :molecule + + validates_presence_of :name +end diff --git a/activerecord/test/models/engine.rb b/activerecord/test/models/engine.rb new file mode 100644 index 0000000000..396a52b3b9 --- /dev/null +++ b/activerecord/test/models/engine.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Engine < ActiveRecord::Base + belongs_to :my_car, class_name: "Car", foreign_key: "car_id", counter_cache: :engines_count +end diff --git a/activerecord/test/models/entrant.rb b/activerecord/test/models/entrant.rb new file mode 100644 index 0000000000..2c086e451f --- /dev/null +++ b/activerecord/test/models/entrant.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Entrant < ActiveRecord::Base + belongs_to :course +end diff --git a/activerecord/test/models/essay.rb b/activerecord/test/models/essay.rb new file mode 100644 index 0000000000..e59db4d877 --- /dev/null +++ b/activerecord/test/models/essay.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Essay < ActiveRecord::Base + belongs_to :author + belongs_to :writer, primary_key: :name, polymorphic: true + belongs_to :category, primary_key: :name + has_one :owner, primary_key: :name +end diff --git a/activerecord/test/models/event.rb b/activerecord/test/models/event.rb new file mode 100644 index 0000000000..a7cdc39e5c --- /dev/null +++ b/activerecord/test/models/event.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Event < ActiveRecord::Base + validates_uniqueness_of :title +end diff --git a/activerecord/test/models/eye.rb b/activerecord/test/models/eye.rb new file mode 100644 index 0000000000..f3608b62ef --- /dev/null +++ b/activerecord/test/models/eye.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class Eye < ActiveRecord::Base + attr_reader :after_create_callbacks_stack + attr_reader :after_update_callbacks_stack + attr_reader :after_save_callbacks_stack + + # Callbacks configured before the ones has_one sets up. + after_create :trace_after_create + after_update :trace_after_update + after_save :trace_after_save + + has_one :iris + accepts_nested_attributes_for :iris + + # Callbacks configured after the ones has_one sets up. + after_create :trace_after_create2 + after_update :trace_after_update2 + after_save :trace_after_save2 + + def trace_after_create + (@after_create_callbacks_stack ||= []) << !iris.persisted? + end + alias trace_after_create2 trace_after_create + + def trace_after_update + (@after_update_callbacks_stack ||= []) << iris.has_changes_to_save? + end + alias trace_after_update2 trace_after_update + + def trace_after_save + (@after_save_callbacks_stack ||= []) << iris.has_changes_to_save? + end + alias trace_after_save2 trace_after_save +end + +class Iris < ActiveRecord::Base + belongs_to :eye +end diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb new file mode 100644 index 0000000000..948435136d --- /dev/null +++ b/activerecord/test/models/face.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class Face < ActiveRecord::Base + belongs_to :man, inverse_of: :face + belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_face + # Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly` + belongs_to :poly_man_without_inverse, polymorphic: true + # These is a "broken" inverse_of for the purposes of testing + belongs_to :horrible_man, class_name: "Man", inverse_of: :horrible_face + belongs_to :horrible_polymorphic_man, polymorphic: true, inverse_of: :horrible_polymorphic_face + + validate do + man + end +end diff --git a/activerecord/test/models/family.rb b/activerecord/test/models/family.rb new file mode 100644 index 0000000000..0713dba1a6 --- /dev/null +++ b/activerecord/test/models/family.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Family < ActiveRecord::Base + has_many :family_trees, -> { where(token: nil) } + has_many :members, through: :family_trees +end diff --git a/activerecord/test/models/family_tree.rb b/activerecord/test/models/family_tree.rb new file mode 100644 index 0000000000..a8ea907c05 --- /dev/null +++ b/activerecord/test/models/family_tree.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class FamilyTree < ActiveRecord::Base + belongs_to :member, class_name: "User", foreign_key: "member_id" + belongs_to :family +end diff --git a/activerecord/test/models/friendship.rb b/activerecord/test/models/friendship.rb new file mode 100644 index 0000000000..9f1712a8ec --- /dev/null +++ b/activerecord/test/models/friendship.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Friendship < ActiveRecord::Base + belongs_to :friend, class_name: "Person" + # friend_too exists to test a bug, and probably shouldn't be used elsewhere + belongs_to :friend_too, foreign_key: "friend_id", class_name: "Person", counter_cache: :friends_too_count + belongs_to :follower, class_name: "Person" +end diff --git a/activerecord/test/models/guid.rb b/activerecord/test/models/guid.rb new file mode 100644 index 0000000000..ec71c37690 --- /dev/null +++ b/activerecord/test/models/guid.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Guid < ActiveRecord::Base +end diff --git a/activerecord/test/models/guitar.rb b/activerecord/test/models/guitar.rb new file mode 100644 index 0000000000..649b998665 --- /dev/null +++ b/activerecord/test/models/guitar.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Guitar < ActiveRecord::Base + has_many :tuning_pegs, index_errors: true + accepts_nested_attributes_for :tuning_pegs +end diff --git a/activerecord/test/models/hotel.rb b/activerecord/test/models/hotel.rb new file mode 100644 index 0000000000..1a433c3cab --- /dev/null +++ b/activerecord/test/models/hotel.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Hotel < ActiveRecord::Base + has_many :departments + has_many :chefs, through: :departments + has_many :cake_designers, source_type: "CakeDesigner", source: :employable, through: :chefs + has_many :drink_designers, source_type: "DrinkDesigner", source: :employable, through: :chefs + + has_many :chef_lists, as: :employable_list + has_many :mocktail_designers, through: :chef_lists, source: :employable, source_type: "MocktailDesigner" + + has_many :recipes, through: :chefs +end diff --git a/activerecord/test/models/image.rb b/activerecord/test/models/image.rb new file mode 100644 index 0000000000..b4808293cc --- /dev/null +++ b/activerecord/test/models/image.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Image < ActiveRecord::Base + belongs_to :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class +end diff --git a/activerecord/test/models/interest.rb b/activerecord/test/models/interest.rb new file mode 100644 index 0000000000..899b8f9b9d --- /dev/null +++ b/activerecord/test/models/interest.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Interest < ActiveRecord::Base + belongs_to :man, inverse_of: :interests + belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_interests + belongs_to :zine, inverse_of: :interests +end diff --git a/activerecord/test/models/invoice.rb b/activerecord/test/models/invoice.rb new file mode 100644 index 0000000000..1851792ed5 --- /dev/null +++ b/activerecord/test/models/invoice.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Invoice < ActiveRecord::Base + has_many :line_items, autosave: true + before_save { |record| record.balance = record.line_items.map(&:amount).sum } +end diff --git a/activerecord/test/models/item.rb b/activerecord/test/models/item.rb new file mode 100644 index 0000000000..8d079d56e6 --- /dev/null +++ b/activerecord/test/models/item.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AbstractItem < ActiveRecord::Base + self.abstract_class = true + has_one :tagging, as: :taggable +end + +class Item < AbstractItem +end diff --git a/activerecord/test/models/job.rb b/activerecord/test/models/job.rb new file mode 100644 index 0000000000..52817a8435 --- /dev/null +++ b/activerecord/test/models/job.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Job < ActiveRecord::Base + has_many :references + has_many :people, through: :references + belongs_to :ideal_reference, class_name: "Reference" + + has_many :agents, through: :people +end diff --git a/activerecord/test/models/joke.rb b/activerecord/test/models/joke.rb new file mode 100644 index 0000000000..436ffb6471 --- /dev/null +++ b/activerecord/test/models/joke.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Joke < ActiveRecord::Base + self.table_name = "funny_jokes" +end + +class GoodJoke < ActiveRecord::Base + self.table_name = "funny_jokes" +end diff --git a/activerecord/test/models/keyboard.rb b/activerecord/test/models/keyboard.rb new file mode 100644 index 0000000000..d200e0fb56 --- /dev/null +++ b/activerecord/test/models/keyboard.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Keyboard < ActiveRecord::Base + self.primary_key = "key_number" +end diff --git a/activerecord/test/models/legacy_thing.rb b/activerecord/test/models/legacy_thing.rb new file mode 100644 index 0000000000..e0210c8922 --- /dev/null +++ b/activerecord/test/models/legacy_thing.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class LegacyThing < ActiveRecord::Base + self.locking_column = :version +end diff --git a/activerecord/test/models/lesson.rb b/activerecord/test/models/lesson.rb new file mode 100644 index 0000000000..e546339689 --- /dev/null +++ b/activerecord/test/models/lesson.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class LessonError < Exception +end + +class Lesson < ActiveRecord::Base + has_and_belongs_to_many :students + before_destroy :ensure_no_students + + def ensure_no_students + raise LessonError unless students.empty? + end +end diff --git a/activerecord/test/models/line_item.rb b/activerecord/test/models/line_item.rb new file mode 100644 index 0000000000..3a51cf03b2 --- /dev/null +++ b/activerecord/test/models/line_item.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class LineItem < ActiveRecord::Base + belongs_to :invoice, touch: true +end diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb new file mode 100644 index 0000000000..b2fd305d66 --- /dev/null +++ b/activerecord/test/models/liquid.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Liquid < ActiveRecord::Base + self.table_name = :liquid + has_many :molecules, -> { distinct } +end diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb new file mode 100644 index 0000000000..3acd89a48e --- /dev/null +++ b/activerecord/test/models/man.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Man < ActiveRecord::Base + has_one :face, inverse_of: :man + has_one :polymorphic_face, class_name: "Face", as: :polymorphic_man, inverse_of: :polymorphic_man + has_one :polymorphic_face_without_inverse, class_name: "Face", as: :poly_man_without_inverse + has_many :interests, inverse_of: :man + has_many :polymorphic_interests, class_name: "Interest", as: :polymorphic_man, inverse_of: :polymorphic_man + # These are "broken" inverse_of associations for the purposes of testing + has_one :dirty_face, class_name: "Face", inverse_of: :dirty_man + has_many :secret_interests, class_name: "Interest", inverse_of: :secret_man + has_one :mixed_case_monkey +end diff --git a/activerecord/test/models/matey.rb b/activerecord/test/models/matey.rb new file mode 100644 index 0000000000..a77ac21e96 --- /dev/null +++ b/activerecord/test/models/matey.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Matey < ActiveRecord::Base + belongs_to :pirate + belongs_to :target, class_name: "Pirate" +end diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb new file mode 100644 index 0000000000..4315ba1941 --- /dev/null +++ b/activerecord/test/models/member.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class Member < ActiveRecord::Base + has_one :current_membership + has_one :selected_membership + has_one :membership + has_one :club, through: :current_membership + has_one :selected_club, through: :selected_membership, source: :club + has_one :favourite_club, -> { where "memberships.favourite = ?", true }, through: :membership, source: :club + has_one :hairy_club, -> { where clubs: { name: "Moustache and Eyebrow Fancier Club" } }, through: :membership, source: :club + has_one :sponsor, as: :sponsorable + has_one :sponsor_club, through: :sponsor + has_one :member_detail, inverse_of: false + has_one :organization, through: :member_detail + belongs_to :member_type + + has_many :nested_member_types, through: :member_detail, source: :member_type + has_one :nested_member_type, through: :member_detail, source: :member_type + + has_many :nested_sponsors, through: :sponsor_club, source: :sponsor + has_one :nested_sponsor, through: :sponsor_club, source: :sponsor + + has_many :organization_member_details, through: :member_detail + has_many :organization_member_details_2, through: :organization, source: :member_details + + has_one :club_category, through: :club, source: :category + has_one :general_club, -> { general }, through: :current_membership, source: :club + + has_many :current_memberships, -> { where favourite: true } + has_many :clubs, through: :current_memberships + + has_many :tenant_memberships + has_many :tenant_clubs, through: :tenant_memberships, class_name: "Club", source: :club + + has_one :club_through_many, through: :current_memberships, source: :club + + belongs_to :admittable, polymorphic: true + has_one :premium_club, through: :admittable +end + +class SelfMember < ActiveRecord::Base + self.table_name = "members" + has_and_belongs_to_many :friends, class_name: "SelfMember", join_table: "member_friends" +end diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb new file mode 100644 index 0000000000..87f7aab9a2 --- /dev/null +++ b/activerecord/test/models/member_detail.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class MemberDetail < ActiveRecord::Base + belongs_to :member, inverse_of: false + belongs_to :organization + has_one :member_type, through: :member + has_one :membership, through: :member + + has_many :organization_member_details, through: :organization, source: :member_details +end diff --git a/activerecord/test/models/member_type.rb b/activerecord/test/models/member_type.rb new file mode 100644 index 0000000000..b49b168d03 --- /dev/null +++ b/activerecord/test/models/member_type.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class MemberType < ActiveRecord::Base + has_many :members +end diff --git a/activerecord/test/models/membership.rb b/activerecord/test/models/membership.rb new file mode 100644 index 0000000000..09ee7544b3 --- /dev/null +++ b/activerecord/test/models/membership.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class Membership < ActiveRecord::Base + enum type: %i(Membership CurrentMembership SuperMembership SelectedMembership TenantMembership) + belongs_to :member + belongs_to :club +end + +class CurrentMembership < Membership + belongs_to :member + belongs_to :club +end + +class SuperMembership < Membership + belongs_to :member, -> { order("members.id DESC") } + belongs_to :club +end + +class SelectedMembership < Membership + def self.default_scope + select("'1' as foo") + end +end + +class TenantMembership < Membership + cattr_accessor :current_member + + belongs_to :member + belongs_to :club + + default_scope -> { + if current_member + where(member: current_member) + else + all + end + } +end diff --git a/activerecord/test/models/mentor.rb b/activerecord/test/models/mentor.rb new file mode 100644 index 0000000000..2fbb62c435 --- /dev/null +++ b/activerecord/test/models/mentor.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Mentor < ActiveRecord::Base + has_many :developers +end diff --git a/activerecord/test/models/minimalistic.rb b/activerecord/test/models/minimalistic.rb new file mode 100644 index 0000000000..c67b086853 --- /dev/null +++ b/activerecord/test/models/minimalistic.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Minimalistic < ActiveRecord::Base +end diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb new file mode 100644 index 0000000000..d9d331798a --- /dev/null +++ b/activerecord/test/models/minivan.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Minivan < ActiveRecord::Base + self.primary_key = :minivan_id + + belongs_to :speedometer + has_one :dashboard, through: :speedometer + + attr_readonly :color +end diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb new file mode 100644 index 0000000000..8e92f68817 --- /dev/null +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class MixedCaseMonkey < ActiveRecord::Base + belongs_to :man +end diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb new file mode 100644 index 0000000000..7da08a85c4 --- /dev/null +++ b/activerecord/test/models/molecule.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Molecule < ActiveRecord::Base + belongs_to :liquid + has_many :electrons + + accepts_nested_attributes_for :electrons +end diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb new file mode 100644 index 0000000000..fa2ea900c7 --- /dev/null +++ b/activerecord/test/models/movie.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Movie < ActiveRecord::Base + self.primary_key = "movieid" + + validates_presence_of :name +end diff --git a/activerecord/test/models/node.rb b/activerecord/test/models/node.rb new file mode 100644 index 0000000000..ae46c76b46 --- /dev/null +++ b/activerecord/test/models/node.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Node < ActiveRecord::Base + belongs_to :tree, touch: true + belongs_to :parent, class_name: "Node", touch: true, optional: true + has_many :children, class_name: "Node", foreign_key: :parent_id, dependent: :destroy +end diff --git a/activerecord/test/models/non_primary_key.rb b/activerecord/test/models/non_primary_key.rb new file mode 100644 index 0000000000..e954375989 --- /dev/null +++ b/activerecord/test/models/non_primary_key.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class NonPrimaryKey < ActiveRecord::Base +end diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb new file mode 100644 index 0000000000..3f8728af5e --- /dev/null +++ b/activerecord/test/models/notification.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Notification < ActiveRecord::Base + validates_presence_of :message +end diff --git a/activerecord/test/models/numeric_data.rb b/activerecord/test/models/numeric_data.rb new file mode 100644 index 0000000000..666e1a5778 --- /dev/null +++ b/activerecord/test/models/numeric_data.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class NumericData < ActiveRecord::Base + self.table_name = "numeric_data" + # Decimal columns with 0 scale being automatically treated as integers + # is deprecated, and will be removed in a future version of Rails. + attribute :world_population, :big_integer + attribute :my_house_population, :big_integer + attribute :atoms_in_universe, :big_integer +end diff --git a/activerecord/test/models/order.rb b/activerecord/test/models/order.rb new file mode 100644 index 0000000000..36866b398f --- /dev/null +++ b/activerecord/test/models/order.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Order < ActiveRecord::Base + belongs_to :billing, class_name: "Customer", foreign_key: "billing_customer_id" + belongs_to :shipping, class_name: "Customer", foreign_key: "shipping_customer_id" +end diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb new file mode 100644 index 0000000000..099e7e38e0 --- /dev/null +++ b/activerecord/test/models/organization.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Organization < ActiveRecord::Base + has_many :member_details + has_many :members, through: :member_details + + has_many :authors, primary_key: :name + has_many :author_essay_categories, through: :authors, source: :essay_categories + + has_one :author, primary_key: :name + has_one :author_owned_essay_category, through: :author, source: :owned_essay_category + + has_many :posts, through: :author, source: :posts + + scope :clubs, -> { from("clubs") } +end diff --git a/activerecord/test/models/other_dog.rb b/activerecord/test/models/other_dog.rb new file mode 100644 index 0000000000..a0fda5ae1b --- /dev/null +++ b/activerecord/test/models/other_dog.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require_dependency "models/arunit2_model" + +class OtherDog < ARUnit2Model + self.table_name = "dogs" +end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb new file mode 100644 index 0000000000..5fa50d9918 --- /dev/null +++ b/activerecord/test/models/owner.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class Owner < ActiveRecord::Base + self.primary_key = :owner_id + has_many :pets, -> { order "pets.name desc" } + has_many :toys, through: :pets + has_many :persons, through: :pets + + belongs_to :last_pet, class_name: "Pet" + scope :including_last_pet, -> { + select(' + owners.*, ( + select p.pet_id from pets p + where p.owner_id = owners.owner_id + order by p.name desc + limit 1 + ) as last_pet_id + ').includes(:last_pet) + } + + after_commit :execute_blocks + + accepts_nested_attributes_for :pets, allow_destroy: true + + def blocks + @blocks ||= [] + end + + def on_after_commit(&block) + blocks << block + end + + def execute_blocks + blocks.each do |block| + block.call(self) + end + @blocks = [] + end +end diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb new file mode 100644 index 0000000000..ba9ddb8c6a --- /dev/null +++ b/activerecord/test/models/parrot.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Parrot < ActiveRecord::Base + self.inheritance_column = :parrot_sti_class + + has_and_belongs_to_many :pirates + has_and_belongs_to_many :treasures + has_many :loots, as: :looter + alias_attribute :title, :name + + validates_presence_of :name + + attribute :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback + def cancel_save_callback_method + throw(:abort) + end + + before_update :increment_updated_count + def increment_updated_count + self.updated_count += 1 + end +end + +class LiveParrot < Parrot +end + +class DeadParrot < Parrot + belongs_to :killer, class_name: "Pirate", foreign_key: :killer_id +end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb new file mode 100644 index 0000000000..5cba1e440e --- /dev/null +++ b/activerecord/test/models/person.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +class Person < ActiveRecord::Base + has_many :readers + has_many :secure_readers + has_one :reader + + has_many :posts, through: :readers + has_many :secure_posts, through: :secure_readers + has_many :posts_with_no_comments, -> { includes(:comments).where("comments.id is null").references(:comments) }, + through: :readers, source: :post + + has_many :friendships, foreign_key: "friend_id" + # friends_too exists to test a bug, and probably shouldn't be used elsewhere + has_many :friends_too, foreign_key: "friend_id", class_name: "Friendship" + has_many :followers, through: :friendships + + has_many :references + has_many :bad_references + has_many :fixed_bad_references, -> { where favourite: true }, class_name: "BadReference" + has_one :favourite_reference, -> { where "favourite=?", true }, class_name: "Reference" + has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, through: :readers, source: :post + has_many :first_posts, -> { where(id: [1, 2]) }, through: :readers + + has_many :jobs, through: :references + has_many :jobs_with_dependent_destroy, source: :job, through: :references, dependent: :destroy + has_many :jobs_with_dependent_delete_all, source: :job, through: :references, dependent: :delete_all + has_many :jobs_with_dependent_nullify, source: :job, through: :references, dependent: :nullify + + belongs_to :primary_contact, class_name: "Person" + has_many :agents, class_name: "Person", foreign_key: "primary_contact_id" + has_many :agents_of_agents, through: :agents, source: :agents + belongs_to :number1_fan, class_name: "Person" + + has_many :personal_legacy_things, dependent: :destroy + + has_many :agents_posts, through: :agents, source: :posts + has_many :agents_posts_authors, through: :agents_posts, source: :author + has_many :essays, primary_key: "first_name", foreign_key: "writer_id" + + scope :males, -> { where(gender: "M") } +end + +class PersonWithDependentDestroyJobs < ActiveRecord::Base + self.table_name = "people" + + has_many :references, foreign_key: :person_id + has_many :jobs, source: :job, through: :references, dependent: :destroy +end + +class PersonWithDependentDeleteAllJobs < ActiveRecord::Base + self.table_name = "people" + + has_many :references, foreign_key: :person_id + has_many :jobs, source: :job, through: :references, dependent: :delete_all +end + +class PersonWithDependentNullifyJobs < ActiveRecord::Base + self.table_name = "people" + + has_many :references, foreign_key: :person_id + has_many :jobs, source: :job, through: :references, dependent: :nullify +end + +class LoosePerson < ActiveRecord::Base + self.table_name = "people" + self.abstract_class = true + + has_one :best_friend, class_name: "LoosePerson", foreign_key: :best_friend_id + belongs_to :best_friend_of, class_name: "LoosePerson", foreign_key: :best_friend_of_id + has_many :best_friends, class_name: "LoosePerson", foreign_key: :best_friend_id + + accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends +end + +class LooseDescendant < LoosePerson; end + +class TightPerson < ActiveRecord::Base + self.table_name = "people" + + has_one :best_friend, class_name: "TightPerson", foreign_key: :best_friend_id + belongs_to :best_friend_of, class_name: "TightPerson", foreign_key: :best_friend_of_id + has_many :best_friends, class_name: "TightPerson", foreign_key: :best_friend_id + + accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends +end + +class TightDescendant < TightPerson; end + +class RichPerson < ActiveRecord::Base + self.table_name = "people" + + has_and_belongs_to_many :treasures, join_table: "peoples_treasures" + + before_validation :run_before_create, on: :create + before_validation :run_before_validation + + private + + def run_before_create + self.first_name = first_name.to_s + "run_before_create" + end + + def run_before_validation + self.first_name = first_name.to_s + "run_before_validation" + end +end + +class NestedPerson < ActiveRecord::Base + self.table_name = "people" + + has_one :best_friend, class_name: "NestedPerson", foreign_key: :best_friend_id + accepts_nested_attributes_for :best_friend, update_only: true + + def comments=(new_comments) + raise RuntimeError + end + + def best_friend_first_name=(new_name) + assign_attributes(best_friend_attributes: { first_name: new_name }) + end +end + +class Insure + INSURES = %W{life annuality} + + def self.load(mask) + INSURES.select do |insure| + (1 << INSURES.index(insure)) & mask.to_i > 0 + end + end + + def self.dump(insures) + numbers = insures.map { |insure| INSURES.index(insure) } + numbers.inject(0) { |sum, n| sum + (1 << n) } + end +end + +class SerializedPerson < ActiveRecord::Base + self.table_name = "people" + + serialize :insures, Insure +end diff --git a/activerecord/test/models/personal_legacy_thing.rb b/activerecord/test/models/personal_legacy_thing.rb new file mode 100644 index 0000000000..ed8b70cfcc --- /dev/null +++ b/activerecord/test/models/personal_legacy_thing.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class PersonalLegacyThing < ActiveRecord::Base + self.locking_column = :version + belongs_to :person, counter_cache: true +end diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb new file mode 100644 index 0000000000..9bda2109e6 --- /dev/null +++ b/activerecord/test/models/pet.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class Pet < ActiveRecord::Base + attr_accessor :current_user + + self.primary_key = :pet_id + belongs_to :owner, touch: true + has_many :toys + has_many :pet_treasures + has_many :treasures, through: :pet_treasures + has_many :persons, through: :treasures, source: :looter, source_type: "Person" + + class << self + attr_accessor :after_destroy_output + end + + after_destroy do |record| + Pet.after_destroy_output = record.current_user + end +end diff --git a/activerecord/test/models/pet_treasure.rb b/activerecord/test/models/pet_treasure.rb new file mode 100644 index 0000000000..47b9f57fad --- /dev/null +++ b/activerecord/test/models/pet_treasure.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class PetTreasure < ActiveRecord::Base + self.table_name = "pets_treasures" + + belongs_to :pet + belongs_to :treasure +end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb new file mode 100644 index 0000000000..c8617d1cfe --- /dev/null +++ b/activerecord/test/models/pirate.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +class Pirate < ActiveRecord::Base + belongs_to :parrot, validate: true + belongs_to :non_validated_parrot, class_name: "Parrot" + has_and_belongs_to_many :parrots, -> { order("parrots.id ASC") }, validate: true + has_and_belongs_to_many :non_validated_parrots, class_name: "Parrot" + has_and_belongs_to_many :parrots_with_method_callbacks, class_name: "Parrot", + before_add: :log_before_add, + after_add: :log_after_add, + before_remove: :log_before_remove, + after_remove: :log_after_remove + has_and_belongs_to_many :parrots_with_proc_callbacks, class_name: "Parrot", + before_add: proc { |p, pa| p.ship_log << "before_adding_proc_parrot_#{pa.id || '<new>'}" }, + after_add: proc { |p, pa| p.ship_log << "after_adding_proc_parrot_#{pa.id || '<new>'}" }, + before_remove: proc { |p, pa| p.ship_log << "before_removing_proc_parrot_#{pa.id}" }, + after_remove: proc { |p, pa| p.ship_log << "after_removing_proc_parrot_#{pa.id}" } + has_and_belongs_to_many :autosaved_parrots, class_name: "Parrot", autosave: true + + has_many :treasures, as: :looter + has_many :treasure_estimates, through: :treasures, source: :price_estimates + + has_one :ship + has_one :update_only_ship, class_name: "Ship" + has_one :non_validated_ship, class_name: "Ship" + has_many :birds, -> { order("birds.id ASC") } + has_many :birds_with_method_callbacks, class_name: "Bird", + before_add: :log_before_add, + after_add: :log_after_add, + before_remove: :log_before_remove, + after_remove: :log_after_remove + has_many :birds_with_proc_callbacks, class_name: "Bird", + before_add: proc { |p, b| p.ship_log << "before_adding_proc_bird_#{b.id || '<new>'}" }, + after_add: proc { |p, b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}" }, + before_remove: proc { |p, b| p.ship_log << "before_removing_proc_bird_#{b.id}" }, + after_remove: proc { |p, b| p.ship_log << "after_removing_proc_bird_#{b.id}" } + has_many :birds_with_reject_all_blank, class_name: "Bird" + + has_one :foo_bulb, -> { where name: "foo" }, foreign_key: :car_id, class_name: "Bulb" + + accepts_nested_attributes_for :parrots, :birds, allow_destroy: true, reject_if: proc(&:empty?) + accepts_nested_attributes_for :ship, allow_destroy: true, reject_if: proc(&:empty?) + accepts_nested_attributes_for :update_only_ship, update_only: true + accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks, + :birds_with_method_callbacks, :birds_with_proc_callbacks, allow_destroy: true + accepts_nested_attributes_for :birds_with_reject_all_blank, reject_if: :all_blank + + validates_presence_of :catchphrase + + def ship_log + @ship_log ||= [] + end + + def reject_empty_ships_on_create(attributes) + attributes.delete("_reject_me_if_new").present? && !persisted? + end + + attr_accessor :cancel_save_from_callback, :parrots_limit + before_save :cancel_save_callback_method, if: :cancel_save_from_callback + def cancel_save_callback_method + throw(:abort) + end + + private + def log_before_add(record) + log(record, "before_adding_method") + end + + def log_after_add(record) + log(record, "after_adding_method") + end + + def log_before_remove(record) + log(record, "before_removing_method") + end + + def log_after_remove(record) + log(record, "after_removing_method") + end + + def log(record, callback) + ship_log << "#{callback}_#{record.class.name.downcase}_#{record.id || '<new>'}" + end +end + +class DestructivePirate < Pirate + has_one :dependent_ship, class_name: "Ship", foreign_key: :pirate_id, dependent: :destroy +end + +class FamousPirate < ActiveRecord::Base + self.table_name = "pirates" + has_many :famous_ships + validates_presence_of :catchphrase, on: :conference +end diff --git a/activerecord/test/models/possession.rb b/activerecord/test/models/possession.rb new file mode 100644 index 0000000000..9b843e1525 --- /dev/null +++ b/activerecord/test/models/possession.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Possession < ActiveRecord::Base + self.table_name = "having" +end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb new file mode 100644 index 0000000000..54eb5e6783 --- /dev/null +++ b/activerecord/test/models/post.rb @@ -0,0 +1,338 @@ +# frozen_string_literal: true + +class Post < ActiveRecord::Base + class CategoryPost < ActiveRecord::Base + self.table_name = "categories_posts" + belongs_to :category + belongs_to :post + end + + module NamedExtension + def author + "lifo" + end + end + + module NamedExtension2 + def greeting + "hullo" + end + end + + scope :containing_the_letter_a, -> { where("body LIKE '%a%'") } + scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") } + scope :ranked_by_comments, -> { order(arel_attribute(:comments_count).desc) } + + scope :limit_by, lambda { |l| limit(l) } + scope :locked, -> { lock } + + belongs_to :author + belongs_to :readonly_author, -> { readonly }, class_name: "Author", foreign_key: :author_id + + belongs_to :author_with_posts, -> { includes(:posts) }, class_name: "Author", foreign_key: :author_id + belongs_to :author_with_address, -> { includes(:author_address) }, class_name: "Author", foreign_key: :author_id + + def first_comment + super.body + end + has_one :first_comment, -> { order("id ASC") }, class_name: "Comment" + has_one :last_comment, -> { order("id desc") }, class_name: "Comment" + + scope :with_special_comments, -> { joins(:comments).where(comments: { type: "SpecialComment" }) } + scope :with_very_special_comments, -> { joins(:comments).where(comments: { type: "VerySpecialComment" }) } + scope :with_post, ->(post_id) { joins(:comments).where(comments: { post_id: post_id }) } + + scope :with_comments, -> { preload(:comments) } + scope :with_tags, -> { preload(:taggings) } + + scope :tagged_with, ->(id) { joins(:taggings).where(taggings: { tag_id: id }) } + scope :tagged_with_comment, ->(comment) { joins(:taggings).where(taggings: { comment: comment }) } + + scope :typographically_interesting, -> { containing_the_letter_a.or(titled_with_an_apostrophe) } + + has_many :comments do + def find_most_recent + order("id DESC").first + end + + def newest + created.last + end + + def the_association + proxy_association + end + + def with_content(content) + self.detect { |comment| comment.body == content } + end + end + + has_many :comments_with_extend, extend: NamedExtension, class_name: "Comment", foreign_key: "post_id" do + def greeting + "hello" + end + end + + has_many :comments_with_extend_2, extend: [NamedExtension, NamedExtension2], class_name: "Comment", foreign_key: "post_id" + + has_many :author_favorites, through: :author + has_many :author_categorizations, through: :author, source: :categorizations + has_many :author_addresses, through: :author + has_many :author_address_extra_with_address, + through: :author_with_address, + source: :author_address_extra + + has_one :very_special_comment + has_one :very_special_comment_with_post, -> { includes(:post) }, class_name: "VerySpecialComment" + has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order("posts.id") }, class_name: "VerySpecialComment" + has_many :special_comments + has_many :nonexistent_comments, -> { where "comments.id < 0" }, class_name: "Comment" + + has_many :special_comments_ratings, through: :special_comments, source: :ratings + has_many :special_comments_ratings_taggings, through: :special_comments_ratings, source: :taggings + + has_many :category_posts, class_name: "CategoryPost" + has_many :scategories, through: :category_posts, source: :category + has_and_belongs_to_many :categories + has_and_belongs_to_many :special_categories, join_table: "categories_posts", association_foreign_key: "category_id" + + has_many :taggings, as: :taggable, counter_cache: :tags_count + has_many :tags, through: :taggings do + def add_joins_and_select + select("tags.*, authors.id as author_id") + .joins("left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id") + .to_a + end + end + + has_many :indestructible_taggings, as: :taggable, counter_cache: :indestructible_tags_count + has_many :indestructible_tags, through: :indestructible_taggings, source: :tag + + has_many :taggings_with_delete_all, class_name: "Tagging", as: :taggable, dependent: :delete_all, counter_cache: :taggings_with_delete_all_count + has_many :taggings_with_destroy, class_name: "Tagging", as: :taggable, dependent: :destroy, counter_cache: :taggings_with_destroy_count + + has_many :tags_with_destroy, through: :taggings, source: :tag, dependent: :destroy, counter_cache: :tags_with_destroy_count + has_many :tags_with_nullify, through: :taggings, source: :tag, dependent: :nullify, counter_cache: :tags_with_nullify_count + + has_many :misc_tags, -> { where tags: { name: "Misc" } }, through: :taggings, source: :tag + has_many :funky_tags, through: :taggings, source: :tag + has_many :super_tags, through: :taggings + has_many :ordered_tags, through: :taggings + has_many :tags_with_primary_key, through: :taggings, source: :tag_with_primary_key + has_one :tagging, as: :taggable + + has_many :first_taggings, -> { where taggings: { comment: "first" } }, as: :taggable, class_name: "Tagging" + has_many :first_blue_tags, -> { where tags: { name: "Blue" } }, through: :first_taggings, source: :tag + + has_many :first_blue_tags_2, -> { where taggings: { comment: "first" } }, through: :taggings, source: :blue_tag + + has_many :invalid_taggings, -> { where "taggings.id < 0" }, as: :taggable, class_name: "Tagging" + has_many :invalid_tags, through: :invalid_taggings, source: :tag + + has_many :categorizations, foreign_key: :category_id + has_many :authors, through: :categorizations + + has_many :categorizations_using_author_id, primary_key: :author_id, foreign_key: :post_id, class_name: "Categorization" + has_many :authors_using_author_id, through: :categorizations_using_author_id, source: :author + + has_many :taggings_using_author_id, primary_key: :author_id, as: :taggable, class_name: "Tagging" + has_many :tags_using_author_id, through: :taggings_using_author_id, source: :tag + + has_many :images, as: :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class + has_one :main_image, as: :imageable, foreign_key: :imageable_identifier, foreign_type: :imageable_class, class_name: "Image" + + has_many :standard_categorizations, class_name: "Categorization", foreign_key: :post_id + has_many :author_using_custom_pk, through: :standard_categorizations + has_many :authors_using_custom_pk, through: :standard_categorizations + has_many :named_categories, through: :standard_categorizations + + has_many :readers + has_many :secure_readers + has_many :readers_with_person, -> { includes(:person) }, class_name: "Reader" + has_many :people, through: :readers + has_many :single_people, through: :readers + has_many :people_with_callbacks, source: :person, through: :readers, + before_add: lambda { |owner, reader| log(:added, :before, reader.first_name) }, + after_add: lambda { |owner, reader| log(:added, :after, reader.first_name) }, + before_remove: lambda { |owner, reader| log(:removed, :before, reader.first_name) }, + after_remove: lambda { |owner, reader| log(:removed, :after, reader.first_name) } + has_many :skimmers, -> { where skimmer: true }, class_name: "Reader" + has_many :impatient_people, through: :skimmers, source: :person + + has_many :lazy_readers + has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, class_name: "LazyReader" + + has_many :lazy_people, through: :lazy_readers, source: :person + has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, class_name: "LazyReader" + has_many :lazy_people_unscope_skimmers, through: :lazy_readers_unscope_skimmers, source: :person + + def self.top(limit) + ranked_by_comments.limit_by(limit) + end + + def self.written_by(author) + where(id: author.posts.pluck(:id)) + end + + def self.reset_log + @log = [] + end + + def self.log(message = nil, side = nil, new_record = nil) + return @log if message.nil? + @log << [message, side, new_record] + end +end + +class SpecialPost < Post; end + +class StiPost < Post + has_one :special_comment, class_name: "SpecialComment" +end + +class AbstractStiPost < Post + self.abstract_class = true +end + +class SubStiPost < StiPost + self.table_name = Post.table_name +end + +class SubAbstractStiPost < AbstractStiPost; end + +class FirstPost < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + default_scope { where(id: 1) } + + has_many :comments, foreign_key: :post_id + has_one :comment, foreign_key: :post_id +end + +class TaggedPost < Post + has_many :taggings, -> { rewhere(taggable_type: "TaggedPost") }, as: :taggable + has_many :tags, through: :taggings +end + +class PostWithDefaultInclude < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + default_scope { includes(:comments) } + has_many :comments, foreign_key: :post_id +end + +class PostWithSpecialCategorization < Post + has_many :categorizations, foreign_key: :post_id + default_scope { where(type: "PostWithSpecialCategorization").joins(:categorizations).where(categorizations: { special: true }) } +end + +class PostWithDefaultScope < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + default_scope { order(:title) } +end + +class PostWithPreloadDefaultScope < ActiveRecord::Base + self.table_name = "posts" + + has_many :readers, foreign_key: "post_id" + + default_scope { preload(:readers) } +end + +class PostWithIncludesDefaultScope < ActiveRecord::Base + self.table_name = "posts" + + has_many :readers, foreign_key: "post_id" + + default_scope { includes(:readers) } +end + +class SpecialPostWithDefaultScope < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + default_scope { where(id: [1, 5, 6]) } +end + +class PostThatLoadsCommentsInAnAfterSaveHook < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + has_many :comments, class_name: "CommentThatAutomaticallyAltersPostBody", foreign_key: :post_id + + after_save do |post| + post.comments.load + end +end + +class PostWithAfterCreateCallback < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + has_many :comments, foreign_key: :post_id + + after_create do |post| + update_attribute(:author_id, comments.first.id) + end +end + +class PostWithCommentWithDefaultScopeReferencesAssociation < ActiveRecord::Base + self.inheritance_column = :disabled + self.table_name = "posts" + has_many :comment_with_default_scope_references_associations, foreign_key: :post_id + has_one :first_comment, class_name: "CommentWithDefaultScopeReferencesAssociation", foreign_key: :post_id +end + +class SerializedPost < ActiveRecord::Base + serialize :title +end + +class ConditionalStiPost < Post + default_scope { where(title: "Untitled") } +end + +class SubConditionalStiPost < ConditionalStiPost +end + +class FakeKlass + extend ActiveRecord::Delegation::DelegateCache + + inherited self + + class << self + def connection + Post.connection + end + + def table_name + "posts" + end + + def attribute_alias?(name) + false + end + + def sanitize_sql(sql) + sql + end + + def sanitize_sql_for_order(sql) + sql + end + + def arel_attribute(name, table) + table[name] + end + + def enforce_raw_sql_whitelist(*args) + # noop + end + + def arel_table + Post.arel_table + end + + def predicate_builder + Post.predicate_builder + end + end +end diff --git a/activerecord/test/models/price_estimate.rb b/activerecord/test/models/price_estimate.rb new file mode 100644 index 0000000000..f1f88d8d8d --- /dev/null +++ b/activerecord/test/models/price_estimate.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class PriceEstimate < ActiveRecord::Base + belongs_to :estimate_of, polymorphic: true + belongs_to :thing, polymorphic: true +end diff --git a/activerecord/test/models/professor.rb b/activerecord/test/models/professor.rb new file mode 100644 index 0000000000..abc23f40ff --- /dev/null +++ b/activerecord/test/models/professor.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require_dependency "models/arunit2_model" + +class Professor < ARUnit2Model + has_and_belongs_to_many :courses +end diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb new file mode 100644 index 0000000000..846cef625b --- /dev/null +++ b/activerecord/test/models/project.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class Project < ActiveRecord::Base + belongs_to :mentor + has_and_belongs_to_many :developers, -> { distinct.order "developers.name desc, developers.id desc" } + has_and_belongs_to_many :readonly_developers, -> { readonly }, class_name: "Developer" + has_and_belongs_to_many :non_unique_developers, -> { order "developers.name desc, developers.id desc" }, class_name: "Developer" + has_and_belongs_to_many :limited_developers, -> { limit 1 }, class_name: "Developer" + has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, class_name: "Developer" + has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(name: "David").distinct }, class_name: "Developer" + has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, class_name: "Developer" + has_and_belongs_to_many :developers_with_callbacks, class_name: "Developer", before_add: Proc.new { |o, r| o.developers_log << "before_adding#{r.id || '<new>'}" }, + after_add: Proc.new { |o, r| o.developers_log << "after_adding#{r.id || '<new>'}" }, + before_remove: Proc.new { |o, r| o.developers_log << "before_removing#{r.id}" }, + after_remove: Proc.new { |o, r| o.developers_log << "after_removing#{r.id}" } + has_and_belongs_to_many :well_paid_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer" + belongs_to :firm + has_one :lead_developer, through: :firm, inverse_of: :contracted_projects + + begin + previous_value, ActiveRecord::Base.belongs_to_required_by_default = + ActiveRecord::Base.belongs_to_required_by_default, true + has_and_belongs_to_many :developers_required_by_default, class_name: "Developer" + ensure + ActiveRecord::Base.belongs_to_required_by_default = previous_value + end + + attr_accessor :developers_log + after_initialize :set_developers_log + + def set_developers_log + @developers_log = [] + end + + def self.all_as_method + all + end + scope :all_as_scope, -> { all } +end + +class SpecialProject < Project +end diff --git a/activerecord/test/models/publisher.rb b/activerecord/test/models/publisher.rb new file mode 100644 index 0000000000..53677197c4 --- /dev/null +++ b/activerecord/test/models/publisher.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module Publisher +end diff --git a/activerecord/test/models/publisher/article.rb b/activerecord/test/models/publisher/article.rb new file mode 100644 index 0000000000..355c22dcc5 --- /dev/null +++ b/activerecord/test/models/publisher/article.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Publisher::Article < ActiveRecord::Base + has_and_belongs_to_many :magazines + has_and_belongs_to_many :tags +end diff --git a/activerecord/test/models/publisher/magazine.rb b/activerecord/test/models/publisher/magazine.rb new file mode 100644 index 0000000000..425ede8df2 --- /dev/null +++ b/activerecord/test/models/publisher/magazine.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Publisher::Magazine < ActiveRecord::Base + has_and_belongs_to_many :articles +end diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb new file mode 100644 index 0000000000..f90a7b9336 --- /dev/null +++ b/activerecord/test/models/randomly_named_c1.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base + self.table_name = :randomly_named_table1 +end diff --git a/activerecord/test/models/rating.rb b/activerecord/test/models/rating.rb new file mode 100644 index 0000000000..cf06bc6931 --- /dev/null +++ b/activerecord/test/models/rating.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Rating < ActiveRecord::Base + belongs_to :comment + has_many :taggings, as: :taggable +end diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb new file mode 100644 index 0000000000..d25627e430 --- /dev/null +++ b/activerecord/test/models/reader.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Reader < ActiveRecord::Base + belongs_to :post + belongs_to :person, inverse_of: :readers + belongs_to :single_person, class_name: "Person", foreign_key: :person_id, inverse_of: :reader + belongs_to :first_post, -> { where(id: [2, 3]) } +end + +class SecureReader < ActiveRecord::Base + self.table_name = "readers" + + belongs_to :secure_post, class_name: "Post", foreign_key: "post_id" + belongs_to :secure_person, inverse_of: :secure_readers, class_name: "Person", foreign_key: "person_id" +end + +class LazyReader < ActiveRecord::Base + self.table_name = "readers" + default_scope -> { where(skimmer: true) } + + scope :skimmers_or_not, -> { unscope(where: :skimmer) } + + belongs_to :post + belongs_to :person +end diff --git a/activerecord/test/models/recipe.rb b/activerecord/test/models/recipe.rb new file mode 100644 index 0000000000..e53f5c8fb1 --- /dev/null +++ b/activerecord/test/models/recipe.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Recipe < ActiveRecord::Base + belongs_to :chef +end diff --git a/activerecord/test/models/record.rb b/activerecord/test/models/record.rb new file mode 100644 index 0000000000..63143e296a --- /dev/null +++ b/activerecord/test/models/record.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class Record < ActiveRecord::Base +end diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb new file mode 100644 index 0000000000..2a7a1e3b77 --- /dev/null +++ b/activerecord/test/models/reference.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Reference < ActiveRecord::Base + belongs_to :person + belongs_to :job + + has_many :agents_posts_authors, through: :person + + class << self; attr_accessor :make_comments; end + self.make_comments = false + + before_destroy :make_comments + + def make_comments + if self.class.make_comments + person.update comments: "Reference destroyed" + end + end +end + +class BadReference < ActiveRecord::Base + self.table_name = "references" + default_scope { where(favourite: false) } +end diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb new file mode 100644 index 0000000000..bc829ec67f --- /dev/null +++ b/activerecord/test/models/reply.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "models/topic" + +class Reply < Topic + belongs_to :topic, foreign_key: "parent_id", counter_cache: true + belongs_to :topic_with_primary_key, class_name: "Topic", primary_key: "title", foreign_key: "parent_title", counter_cache: "replies_count" + has_many :replies, class_name: "SillyReply", dependent: :destroy, foreign_key: "parent_id" +end + +class UniqueReply < Reply + belongs_to :topic, foreign_key: "parent_id", counter_cache: true + validates_uniqueness_of :content, scope: "parent_id" +end + +class SillyUniqueReply < UniqueReply +end + +class WrongReply < Reply + validate :errors_on_empty_content + validate :title_is_wrong_create, on: :create + + validate :check_empty_title + validate :check_content_mismatch, on: :create + validate :check_wrong_update, on: :update + validate :check_author_name_is_secret, on: :special_case + + def check_empty_title + errors[:title] << "Empty" unless attribute_present?("title") + end + + def errors_on_empty_content + errors[:content] << "Empty" unless attribute_present?("content") + end + + def check_content_mismatch + if attribute_present?("title") && attribute_present?("content") && content == "Mismatch" + errors[:title] << "is Content Mismatch" + end + end + + def title_is_wrong_create + errors[:title] << "is Wrong Create" if attribute_present?("title") && title == "Wrong Create" + end + + def check_wrong_update + errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update" + end + + def check_author_name_is_secret + errors[:author_name] << "Invalid" unless author_name == "secret" + end +end + +class SillyReply < Reply + belongs_to :reply, foreign_key: "parent_id", counter_cache: :replies_count +end + +module Web + class Reply < Web::Topic + belongs_to :topic, foreign_key: "parent_id", counter_cache: true, class_name: "Web::Topic" + end +end diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb new file mode 100644 index 0000000000..7973219a79 --- /dev/null +++ b/activerecord/test/models/ship.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class Ship < ActiveRecord::Base + self.record_timestamps = false + + belongs_to :pirate + belongs_to :update_only_pirate, class_name: "Pirate" + belongs_to :developer, dependent: :destroy + has_many :parts, class_name: "ShipPart" + has_many :treasures + + accepts_nested_attributes_for :parts, allow_destroy: true + accepts_nested_attributes_for :pirate, allow_destroy: true, reject_if: proc(&:empty?) + accepts_nested_attributes_for :update_only_pirate, update_only: true + + validates_presence_of :name + + attr_accessor :cancel_save_from_callback + before_save :cancel_save_callback_method, if: :cancel_save_from_callback + def cancel_save_callback_method + throw(:abort) + end +end + +class ShipWithoutNestedAttributes < ActiveRecord::Base + self.table_name = "ships" + has_many :prisoners, inverse_of: :ship, foreign_key: :ship_id + has_many :parts, class_name: "ShipPart", foreign_key: :ship_id + + validates :name, presence: true +end + +class Prisoner < ActiveRecord::Base + belongs_to :ship, autosave: true, class_name: "ShipWithoutNestedAttributes", inverse_of: :prisoners +end + +class FamousShip < ActiveRecord::Base + self.table_name = "ships" + belongs_to :famous_pirate + validates_presence_of :name, on: :conference +end diff --git a/activerecord/test/models/ship_part.rb b/activerecord/test/models/ship_part.rb new file mode 100644 index 0000000000..f6d7a8ae5e --- /dev/null +++ b/activerecord/test/models/ship_part.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class ShipPart < ActiveRecord::Base + belongs_to :ship + has_many :trinkets, class_name: "Treasure", as: :looter + accepts_nested_attributes_for :trinkets, allow_destroy: true + accepts_nested_attributes_for :ship + + validates_presence_of :name +end diff --git a/activerecord/test/models/shop.rb b/activerecord/test/models/shop.rb new file mode 100644 index 0000000000..92afe70b92 --- /dev/null +++ b/activerecord/test/models/shop.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Shop + class Collection < ActiveRecord::Base + has_many :products, dependent: :nullify + end + + class Product < ActiveRecord::Base + has_many :variants, dependent: :delete_all + belongs_to :type + + class Type < ActiveRecord::Base + has_many :products + end + end + + class Variant < ActiveRecord::Base + end +end diff --git a/activerecord/test/models/shop_account.rb b/activerecord/test/models/shop_account.rb new file mode 100644 index 0000000000..97fb058331 --- /dev/null +++ b/activerecord/test/models/shop_account.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class ShopAccount < ActiveRecord::Base + belongs_to :customer + belongs_to :customer_carrier + + has_one :carrier, through: :customer_carrier +end diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb new file mode 100644 index 0000000000..e456907a22 --- /dev/null +++ b/activerecord/test/models/speedometer.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Speedometer < ActiveRecord::Base + self.primary_key = :speedometer_id + belongs_to :dashboard + + has_many :minivans +end diff --git a/activerecord/test/models/sponsor.rb b/activerecord/test/models/sponsor.rb new file mode 100644 index 0000000000..f190860fd1 --- /dev/null +++ b/activerecord/test/models/sponsor.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Sponsor < ActiveRecord::Base + belongs_to :sponsor_club, class_name: "Club", foreign_key: "club_id" + belongs_to :sponsorable, polymorphic: true + belongs_to :thing, polymorphic: true, foreign_type: :sponsorable_type, foreign_key: :sponsorable_id + belongs_to :sponsorable_with_conditions, -> { where name: "Ernie" }, polymorphic: true, + foreign_type: "sponsorable_type", foreign_key: "sponsorable_id" +end diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb new file mode 100644 index 0000000000..473c145f4c --- /dev/null +++ b/activerecord/test/models/string_key_object.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class StringKeyObject < ActiveRecord::Base + self.primary_key = :id +end diff --git a/activerecord/test/models/student.rb b/activerecord/test/models/student.rb new file mode 100644 index 0000000000..e750798f74 --- /dev/null +++ b/activerecord/test/models/student.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Student < ActiveRecord::Base + has_and_belongs_to_many :lessons + belongs_to :college +end diff --git a/activerecord/test/models/subscriber.rb b/activerecord/test/models/subscriber.rb new file mode 100644 index 0000000000..b21969ca2d --- /dev/null +++ b/activerecord/test/models/subscriber.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Subscriber < ActiveRecord::Base + self.primary_key = "nick" + has_many :subscriptions + has_many :books, through: :subscriptions +end + +class SpecialSubscriber < Subscriber +end diff --git a/activerecord/test/models/subscription.rb b/activerecord/test/models/subscription.rb new file mode 100644 index 0000000000..d1d5d21621 --- /dev/null +++ b/activerecord/test/models/subscription.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class Subscription < ActiveRecord::Base + belongs_to :subscriber, counter_cache: :books_count + belongs_to :book +end diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb new file mode 100644 index 0000000000..c1a8890a8a --- /dev/null +++ b/activerecord/test/models/tag.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Tag < ActiveRecord::Base + has_many :taggings + has_many :taggables, through: :taggings + has_one :tagging + + has_many :tagged_posts, through: :taggings, source: "taggable", source_type: "Post" +end + +class OrderedTag < Tag + self.table_name = "tags" + + has_many :ordered_taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id", class_name: "Tagging" + has_many :tagged_posts, through: :ordered_taggings, source: "taggable", source_type: "Post" +end diff --git a/activerecord/test/models/tagging.rb b/activerecord/test/models/tagging.rb new file mode 100644 index 0000000000..6d4230f6f4 --- /dev/null +++ b/activerecord/test/models/tagging.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# test that attr_readonly isn't called on the :taggable polymorphic association +module Taggable +end + +class Tagging < ActiveRecord::Base + belongs_to :tag, -> { includes(:tagging) } + belongs_to :super_tag, class_name: "Tag", foreign_key: "super_tag_id" + belongs_to :invalid_tag, class_name: "Tag", foreign_key: "tag_id" + belongs_to :ordered_tag, class_name: "OrderedTag", foreign_key: "tag_id" + belongs_to :blue_tag, -> { where tags: { name: "Blue" } }, class_name: "Tag", foreign_key: :tag_id + belongs_to :tag_with_primary_key, class_name: "Tag", foreign_key: :tag_id, primary_key: :custom_primary_key + belongs_to :taggable, polymorphic: true, counter_cache: :tags_count + has_many :things, through: :taggable +end + +class IndestructibleTagging < Tagging + before_destroy { throw :abort } +end diff --git a/activerecord/test/models/task.rb b/activerecord/test/models/task.rb new file mode 100644 index 0000000000..dabe3ce06b --- /dev/null +++ b/activerecord/test/models/task.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Task < ActiveRecord::Base + def updated_at + ending + end +end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb new file mode 100644 index 0000000000..8cd4dc352a --- /dev/null +++ b/activerecord/test/models/topic.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +class Topic < ActiveRecord::Base + scope :base, -> { all } + scope :written_before, lambda { |time| + if time + where "written_on < ?", time + end + } + scope :approved, -> { where(approved: true) } + scope :rejected, -> { where(approved: false) } + + scope :scope_with_lambda, lambda { all } + + scope :by_lifo, -> { where(author_name: "lifo") } + scope :replied, -> { where "replies_count > 0" } + + scope "approved_as_string", -> { where(approved: true) } + scope :anonymous_extension, -> {} do + def one + 1 + end + end + + scope :with_object, Class.new(Struct.new(:klass)) { + def call + klass.where(approved: true) + end + }.new(self) + + module NamedExtension + def two + 2 + end + end + + has_many :replies, dependent: :destroy, foreign_key: "parent_id", autosave: true + has_many :approved_replies, -> { approved }, class_name: "Reply", foreign_key: "parent_id", counter_cache: "replies_count" + + has_many :unique_replies, dependent: :destroy, foreign_key: "parent_id" + has_many :silly_unique_replies, dependent: :destroy, foreign_key: "parent_id" + + serialize :content + + before_create :default_written_on + before_destroy :destroy_children + + def parent + Topic.find(parent_id) + end + + # trivial method for testing Array#to_xml with :methods + def topic_id + id + end + + alias_attribute :heading, :title + + before_validation :before_validation_for_transaction + before_save :before_save_for_transaction + before_destroy :before_destroy_for_transaction + + after_save :after_save_for_transaction + after_create :after_create_for_transaction + + after_initialize :set_email_address + + attr_accessor :change_approved_before_save + before_save :change_approved_callback + + class_attribute :after_initialize_called + after_initialize do + self.class.after_initialize_called = true + end + + def approved=(val) + @custom_approved = val + write_attribute(:approved, val) + end + + private + + def default_written_on + self.written_on = Time.now unless attribute_present?("written_on") + end + + def destroy_children + self.class.where("parent_id = #{id}").delete_all + end + + def set_email_address + unless persisted? + self.author_email_address = "test@test.com" + end + end + + def before_validation_for_transaction; end + def before_save_for_transaction; end + def before_destroy_for_transaction; end + def after_save_for_transaction; end + def after_create_for_transaction; end + + def change_approved_callback + self.approved = change_approved_before_save unless change_approved_before_save.nil? + end +end + +class ImportantTopic < Topic + serialize :important, Hash +end + +class DefaultRejectedTopic < Topic + default_scope -> { where(approved: false) } +end + +class BlankTopic < Topic + # declared here to make sure that dynamic finder with a bang can find a model that responds to `blank?` + def blank? + true + end +end + +module Web + class Topic < ActiveRecord::Base + has_many :replies, dependent: :destroy, foreign_key: "parent_id", class_name: "Web::Reply" + end +end diff --git a/activerecord/test/models/toy.rb b/activerecord/test/models/toy.rb new file mode 100644 index 0000000000..4a5697eeb1 --- /dev/null +++ b/activerecord/test/models/toy.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Toy < ActiveRecord::Base + self.primary_key = :toy_id + belongs_to :pet + + scope :with_pet, -> { joins(:pet) } +end diff --git a/activerecord/test/models/traffic_light.rb b/activerecord/test/models/traffic_light.rb new file mode 100644 index 0000000000..0b88815cbd --- /dev/null +++ b/activerecord/test/models/traffic_light.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class TrafficLight < ActiveRecord::Base + serialize :state, Array + serialize :long_state, Array +end diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb new file mode 100644 index 0000000000..b51db56c37 --- /dev/null +++ b/activerecord/test/models/treasure.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class Treasure < ActiveRecord::Base + has_and_belongs_to_many :parrots + belongs_to :looter, polymorphic: true + # No counter_cache option given + belongs_to :ship + + has_many :price_estimates, as: :estimate_of + has_and_belongs_to_many :rich_people, join_table: "peoples_treasures", validate: false + + accepts_nested_attributes_for :looter +end + +class HiddenTreasure < Treasure +end diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb new file mode 100644 index 0000000000..5c1d75aa09 --- /dev/null +++ b/activerecord/test/models/treaty.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Treaty < ActiveRecord::Base + self.primary_key = :treaty_id + + has_and_belongs_to_many :countries +end diff --git a/activerecord/test/models/tree.rb b/activerecord/test/models/tree.rb new file mode 100644 index 0000000000..77050c5fff --- /dev/null +++ b/activerecord/test/models/tree.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Tree < ActiveRecord::Base + has_many :nodes, dependent: :destroy +end diff --git a/activerecord/test/models/tuning_peg.rb b/activerecord/test/models/tuning_peg.rb new file mode 100644 index 0000000000..6e052e1d0f --- /dev/null +++ b/activerecord/test/models/tuning_peg.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class TuningPeg < ActiveRecord::Base + belongs_to :guitar + validates_numericality_of :pitch +end diff --git a/activerecord/test/models/tyre.rb b/activerecord/test/models/tyre.rb new file mode 100644 index 0000000000..d627026585 --- /dev/null +++ b/activerecord/test/models/tyre.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Tyre < ActiveRecord::Base + belongs_to :car + + def self.custom_find(id) + find(id) + end + + def self.custom_find_by(*args) + find_by(*args) + end +end diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb new file mode 100644 index 0000000000..3efbc45d2a --- /dev/null +++ b/activerecord/test/models/user.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "models/job" + +class User < ActiveRecord::Base + has_secure_token + has_secure_token :auth_token + + has_and_belongs_to_many :jobs_pool, + class_name: "Job", + join_table: "jobs_pool" + + has_one :family_tree, -> { where(token: nil) }, foreign_key: "member_id" + has_one :family, through: :family_tree + has_many :family_members, through: :family, source: :members +end + +class UserWithNotification < User + after_create -> { Notification.create! message: "A new user has been created." } +end diff --git a/activerecord/test/models/uuid_child.rb b/activerecord/test/models/uuid_child.rb new file mode 100644 index 0000000000..9fce361cc8 --- /dev/null +++ b/activerecord/test/models/uuid_child.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class UuidChild < ActiveRecord::Base + belongs_to :uuid_parent +end diff --git a/activerecord/test/models/uuid_item.rb b/activerecord/test/models/uuid_item.rb new file mode 100644 index 0000000000..41f68c4c18 --- /dev/null +++ b/activerecord/test/models/uuid_item.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class UuidItem < ActiveRecord::Base +end + +class UuidValidatingItem < UuidItem + validates_uniqueness_of :uuid +end diff --git a/activerecord/test/models/uuid_parent.rb b/activerecord/test/models/uuid_parent.rb new file mode 100644 index 0000000000..05db61855e --- /dev/null +++ b/activerecord/test/models/uuid_parent.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class UuidParent < ActiveRecord::Base + has_many :uuid_children +end diff --git a/activerecord/test/models/vegetables.rb b/activerecord/test/models/vegetables.rb new file mode 100644 index 0000000000..cfaab08ed1 --- /dev/null +++ b/activerecord/test/models/vegetables.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Vegetable < ActiveRecord::Base + validates_presence_of :name + + def self.inheritance_column + "custom_type" + end +end + +class Cucumber < Vegetable +end + +class Cabbage < Vegetable +end + +class GreenCabbage < Cabbage +end + +class KingCole < GreenCabbage +end + +class RedCabbage < Cabbage + belongs_to :seller, class_name: "Company" +end diff --git a/activerecord/test/models/vehicle.rb b/activerecord/test/models/vehicle.rb new file mode 100644 index 0000000000..c9b3338522 --- /dev/null +++ b/activerecord/test/models/vehicle.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Vehicle < ActiveRecord::Base + self.abstract_class = true + default_scope -> { where("tires_count IS NOT NULL") } +end + +class Bus < Vehicle +end diff --git a/activerecord/test/models/vertex.rb b/activerecord/test/models/vertex.rb new file mode 100644 index 0000000000..0ad8114898 --- /dev/null +++ b/activerecord/test/models/vertex.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# This class models a vertex in a directed graph. +class Vertex < ActiveRecord::Base + has_many :sink_edges, class_name: "Edge", foreign_key: "source_id" + has_many :sinks, through: :sink_edges + + has_and_belongs_to_many :sources, + class_name: "Vertex", join_table: "edges", + foreign_key: "sink_id", association_foreign_key: "source_id" +end diff --git a/activerecord/test/models/warehouse_thing.rb b/activerecord/test/models/warehouse_thing.rb new file mode 100644 index 0000000000..099633af55 --- /dev/null +++ b/activerecord/test/models/warehouse_thing.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class WarehouseThing < ActiveRecord::Base + self.table_name = "warehouse-things" + + validates_uniqueness_of :value +end diff --git a/activerecord/test/models/wheel.rb b/activerecord/test/models/wheel.rb new file mode 100644 index 0000000000..8db57d181e --- /dev/null +++ b/activerecord/test/models/wheel.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Wheel < ActiveRecord::Base + belongs_to :wheelable, polymorphic: true, counter_cache: true, touch: true +end diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb new file mode 100644 index 0000000000..fc0a52d6ad --- /dev/null +++ b/activerecord/test/models/without_table.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class WithoutTable < ActiveRecord::Base + default_scope -> { where(published: true) } +end diff --git a/activerecord/test/models/zine.rb b/activerecord/test/models/zine.rb new file mode 100644 index 0000000000..6f361665ef --- /dev/null +++ b/activerecord/test/models/zine.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Zine < ActiveRecord::Base + has_many :interests, inverse_of: :zine +end |