Matching the attribute of the association of nested models with

Suppose I have the following models:

class Post < ActiveRecord::Base has_many :authors class Author < ActiveRecord::Base belongs_to :post 

And let the Author model have a name attribute.

I want to find all posts with the specified author "alice" by this author name. Say there is another author of "bob" who co-authored a post with Alice.

If I search for the first result using includes and where :

 post = Post.includes(:authors).where("authors.name" => "alice").first 

You will see that the message has only one author, even if in fact there are more:

 post.authors #=> [#<Author id: 1, name: "alice", ...>] post.reload post.authors #=> [#<Author id: 1, name: "alice", ...>, #<Author id: 2, name: "bob", ...>] 

It seems that the problem is related to the combination of includes and where , which correctly limits the area for the required record, but at the same time hides all associations, except that it matches.

I want to end up using ActiveRecord::Relation for the chain, so the above reload solution is not really satisfactory. Replacing includes with joins solves this, but does not require loading associations:

 Post.joins(:authors).where("authors.name" => "alice").first.authors #=> [#<Author id: 1, name: "alice", ...>, #<Author id: 2, name: "bob", ...>] Post.joins(:authors).where("authors.name" => "alice").first.authors.loaded? #=> false 

Any suggestions? Thanks in advance, I banged my head on this issue for a while.

+4
source share
2 answers

I see what you are doing as expected behavior, at least how SQL works ... you limit the union of authors, where authors.id = 1, so why should it load others? ActiveRecord simply takes the rows that the database returned, but does not know if there are others without making another request based on posts.id.

Here is one possible solution with a subquery, this will work as a connecting relation and is executed in a single query:

 relation = Post.find_by_id(id: Author.where(id:1).select(:post_id)) 

If you add inclusions, you will see that requests occur in one of two ways:

 relation = relation.includes(:authors) relation.first # 1. Post Load SELECT DISTINCT `posts`.`id`... # 2. SQL SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, ... relation.all.first # 1. SQL SELECT `posts`.`id` AS t0_r0, `posts`.`title` AS t0_r1, ... 

Thus, depending on the script, ActiveRecord decides whether to search for an identifier with a simpler query before loading all related authors. Sometimes it makes sense to run a request in 2 stages.

+1
source

Actually, this is because this code:

 post = Post.includes(:authors).where("authors.name" => "alice").first 

returns the first matching record due to ".first". I think if you do this:

 post = Post.includes(:authors).where("authors.name" => "alice") 

You will receive all messages with "alice" and its other co-authors, if I understand what you are asking correctly.

-one
source

Source: https://habr.com/ru/post/1395539/


All Articles