Using Rails includes with conditions on children

ぐ巨炮叔叔 提交于 2020-08-07 05:00:07

问题


I have a model Parent that has many children Child. I want to get all Parent models and show every Child of the Parent as well. This is a classic use case for Rails' includes method, as far as I can tell.

However, I can't get Rails to add conditions to the child models without limiting the Parent models to those that have children.

For example, this only outputs parents that have children:

Parent.includes(:children).where(children: {age: 10}).each do |parent|
  # output parent info
  parent.children.where("age = 10").each do |child|
   #output child info
  end
end

I've looked at Rails includes with conditions but it seems like I'm having the same trouble as the question's OP and neither part of the accepted answer doesn't solve it (it either has only some parents, or resorts to multiple queries).


回答1:


You need to use LEFT JOIN.

Parent.joins("LEFT JOIN children ON parent.id = children.parent_id")
      .where("parent.age = 10 AND children.age = 10")
      .select("parent.*, children.*")

If you want to select rows from the parent table which may or may not have corresponding rows in the children table, you use the LEFT JOIN clause. In case there is no matching row in the children table, the values of the columns in the children table are substituted by the NULL values.




回答2:


This a limitation of the includes method. What you need is an outer join and unfortunately rails doesnt have a good way to force an outer join without using the raw sql syntax (#joins defaults to inner join and #includes eager loads).
try using something along the lines of

Parent.joins('LEFT OUTER JOIN child on child.parent_id = parent.id').where(...)

this should grab all parents, even those without children




回答3:


I ran into this issue, thus stumbling across this question. Sadly, none of the answers so far are solutions. Happily, I have found the solution! Thanks in-part to the docs :) http://apidock.com/rails/ActiveRecord/QueryMethods/includes

As the docs suggest, simply including the association, and then adding a condition to it is not sufficient; you must also "reference" the association references(:children).

Now, additionally you can see that I'm using some syntactic sugar that I recommend for merging in your conditions, versus re-writing them. Use this when possible.

Parent.includes(:children).merge(Child.at_school).references(:children).first

So what I did, and what I suggest doing is setting up a scope for this:

class Parent < ActiveRecord::Model

    has_many :children

    scope :with_children_at_school, -> { includes(:children).merge(Child.at_school).references(:children) }
    # ...
end

And then you can just call Parent.with_children_at_school.first (or whatever else you want to chain on to the end!

I hope this helps!




回答4:


This is not a 100% answer, but one approach is to accept that you wil get all child records returned by the eager loading, but to choose the ones that you then want using a non-ActiveRecord method.

You will includes more child records in the eager loading than you need, so that's less efficient than a perfect solution, but you still get the records you want:

Parent.includes(:children).each do |parent|
  parent.children.select{|child| child.age == 10}.each do |child|
    blah blah...
  end
end

I'm assuming here that you need a lot of flexibility on your select criteria, and that an association based on a scope would not offer such flexibility.




回答5:


The parents who don't have children will have a children.age of NULL, you are only filtering for children.age = 10.

Try

where('children.age = 10 or children.age is null')


来源:https://stackoverflow.com/questions/32208081/using-rails-includes-with-conditions-on-children

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!