开发者

ARel mimic includes with find_by_sql

开发者 https://www.devze.com 2023-03-29 08:21 出处:网络
I\'ve got a fairly complex sql query that I\'m pretty sure I can\'t accomplish with ARel (Rails 3.0.10)

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 h开发者_如何学编程as 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?


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?


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


@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
0

精彩评论

暂无评论...
验证码 换一张
取 消