ARel mimic includes with find_by_sql

梦想与她 提交于 2019-12-20 10:39:16

问题


I've got a fairly complex sql query that I'm pretty sure I can't accomplish with ARel (Rails 3.0.10)

Check out the link, but it has a few joins and a where exists clause, and that I'm pretty sure is too complex for ARel.

My problem however is that, before this query was so complex, with ARel I could use includes to add other models that I needed to avoid n+1 issues. Now that I'm using find_by_sql, includes don't work. I still want to be able to fetch these records and attach them to my model instances, the way includes does, but I'm not quite sure how to achieve this.

Can someone point me in the right direction?

I haven't tried joining them in the same query yet. I'm just not sure how they would be mapped to objects (ie. if ActiveRecord would properly map them to the proper class)

I know that when using includes ActiveRecord actually makes a second query, then somehow attaches those rows to the corresponding instances from the original query. Can someone instruct me on how I might do this? Or do I need to join in the same query?


回答1:


Let's pretend that the SQL really can't be reduced to Arel. Not everything can, and we happen to really really want to keep our custom find_by_sql but we also want to use includes.

Then preload_associations is your friend: (Updated for Rails 3.1)

class Person
  def self.custom_query
    friends_and_family = find_by_sql("SELECT * FROM people")
# Rails 3.0 and lower use this: 
#        preload_associations(friends_and_family, [:car, :kids])
# Rails 3.1 and higher use this: 
    ActiveRecord::Associations::Preloader.new(friends_and_family, [:car, :kids]).run
    friends_and_family
  end
end

Note that the 3.1 method is much better, b/c you can apply the eager-loading at any time. Thus you can fetch the objects in your controller, and then just before rendering, you can check the format and eager-load more associations. That's what happens for me - html doens't need the eager loading, but the .json does.

That help?




回答2:


I am pretty sure that you can do even the most complex queries with Arel. Maybe you are being over-skeptical about it.

Check these:

  • Rails 3: Arel for NOT EXISTS?

  • How to do "where exists" in Arel




回答3:


@pedrorolo thanks for the heads up on that not exists arel query, helped me achieve what I needed. Here's the final solution (they key is the final .exists on the GroupChallenge query:

class GroupChallenge < ActiveRecord::Base
  belongs_to :group
  belongs_to :challenge  

  def self.challenges_for_contact(contact_id, group_id=nil)
    group_challenges = GroupChallenge.arel_table
    group_contacts = GroupContact.arel_table
    challenges = Challenge.arel_table
    groups = Group.arel_table

    query = group_challenges.project(1).
              join(group_contacts).on(group_contacts[:group_id].eq(group_challenges[:group_id])).
              where(group_challenges[:challenge_id].eq(challenges[:id])).
              where(group_challenges[:restrict_participants].eq(true)).
              where(group_contacts[:contact_id].eq(contact_id))

    query = query.join(groups).on(groups[:id].eq(group_challenges[:group_id])).where(groups[:id].eq(group_id)) if group_id

    query
  end
end

class Challenge < ActiveRecord::Base
  def self.open_for_participant(contact_id, group_id = nil)
    open.
      joins("LEFT OUTER JOIN challenge_participants as cp ON challenges.id = cp.challenge_id AND cp.contact_id = #{contact_id.to_i}").
        where(['cp.accepted != ? or cp.accepted IS NULL', false]).
      where(GroupChallenge.challenges_for_contact(contact_id, group_id).exists.or(table[:open_to_all].eq(true)))
  end
end


来源:https://stackoverflow.com/questions/7116087/arel-mimic-includes-with-find-by-sql

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