1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/test/models/comment.rb
eileencodes de6b4efa3e
Add option to skip joins for associations.
In a multiple database application, associations can't join across
databases. When set, this option tells Rails to make 2 or more queries
rather than using joins for associations.

Set the option on a has many through association:

```ruby
class Dog
  has_many :treats, through: :humans, disable_joins: true
  has_many :humans
end
```

Then instead of generating join SQL, two queries are used for `@dog.treats`:

```
SELECT "humans"."id" FROM "humans" WHERE "humans"."dog_id" = ?  [["dog_id", 1]]
SELECT "treats".* FROM "treats" WHERE "treats"."human_id" IN (?, ?, ?)  [["human_id", 1], ["human_id", 2], ["human_id", 3]]
```

This code is extracted from a gem we use internally at GitHub which
means the implementation here is used in production daily and isn't
experimental.

I often get the question "why can't Rails do this automatically" so I
figured I'd include the answer in the commit. Rails can't do this
automatically because associations are lazily loaded. `dog.treats` needs
to load `Dog`, then `Human` and then `Treats`. When `dog.treats` is
called Rails pre-generates the SQL that will be run and puts that
information into a reflection object. Because the SQL parts are pre-generated,
as soon as `dog.treats` is loaded it's too late to skip a join. The join
is already available on the object and that join is what's run to load
`treats` from `dog` through `humans`. I think the only way to avoid setting
an option on the association is to rewrite how and when the SQL is
generated for associations which is a large undertaking. Basically the
way that Active Record associations are designed, it is currently
impossible to have Rails figure out to not join (loading the association
will cause the join to occur, and that join will raise an error if the
models don't live in the same db).

The original implementation was written by me and Aaron. Lee helped port
over tests, and I refactored the extraction to better match Rails style.

Co-authored-by: Lee Quarella <leequarella@gmail.com>
Co-authored-by: Aaron Patterson <aaron@rubyonrails.org>
2021-04-19 11:17:31 -04:00

101 lines
2.6 KiB
Ruby

# 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 }
scope :ordered_by_post_id, -> { order("comments.post_id DESC") }
belongs_to :post, counter_cache: true
belongs_to :author, polymorphic: true
belongs_to :resource, polymorphic: true
belongs_to :origin, polymorphic: true
belongs_to :company, foreign_key: "company"
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, inverse_of: :parent
belongs_to :parent, class_name: "Comment", counter_cache: :children_count, inverse_of: :children
alias_attribute :entry, :post
enum label: [:default, :child]
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
belongs_to :ordinary_post, foreign_key: :post_id, class_name: "Post"
has_one :author, through: :post
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