13

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?

brad
  • 31,987
  • 28
  • 102
  • 155

3 Answers3

24

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?

Rob
  • 4,404
  • 2
  • 32
  • 33
  • 1
    Thanks! I had actually discovered the preload_associations as well and it's exactly what I needed! Forgot to add that here. thx for reminding me. – brad Mar 02 '12 at 21:52
1

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:

Community
  • 1
  • 1
Pedro Rolo
  • 28,273
  • 12
  • 60
  • 94
  • the answer to that `where exists` post doesn't do a where exists, it does a where in. The other one however does use exists so I'll check that out. thx! – brad Aug 24 '11 at 19:24
  • FYI i've actually used that 2nd article to adjust my query, so I'm currently doing a `where in`, So I suppose I don't really need the answer to my original question now. – brad Aug 24 '11 at 19:25
  • 1
    Actually, I have been using Arel3 for a few months now, and on this current project I have SQL queries that do things that are several pages long. You can't really do them in ActiveRecord, because ActiveRecord doesn't support things like UNION (that's still in a patch somewhere) and "WITH" clauses. There are workarounds - like construct it with Arel and then call "to_sql" on it, but then you are left with exactly this posters problem - how to use .includes to reduce roundtrip craziness. – Rob Feb 29 '12 at 19:24
1

@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
brad
  • 31,987
  • 28
  • 102
  • 155
  • the open_for_participant method can be written in AREL. Use `includes` instead of `joins` and the where(and or) can be written as arel_table[:attribute].eq(arel_table[:arel_attribute]).and().or etc... – Pedro Rolo Aug 25 '11 at 10:27
  • The problem there is that I have to alias `challenge_participants` in this case, because I also include challenge_participants (not shown here) for a different reason. I know I can also alias in ARel, but can I call `includes` with an aliased ARel table? Or does it just take symbols? – brad Aug 29 '11 at 14:51
  • I guess you should investigate how to use the operator "include". That is certainly possible to do. – Pedro Rolo Aug 29 '11 at 14:54
  • You may use the operator 'alias' to get an alias to a certain table. ;) (at the end of this page: https://github.com/rails/arel) – Pedro Rolo Aug 29 '11 at 14:57
  • looks like the Rails [includes](http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-includes) method does not take `ARel::Table` objects. I tried something like `Model.includes(arel_table)` and I get an `ActiveRecord::ConfigurationError:` I can't see any `include` like method in ARel either. Any thoughts? – brad Aug 29 '11 at 15:26
  • Sorry, you are right, there's no 'include' method. Though, you may use the 'join' method with a second parameter stating that you want a left outer join! :-) – Pedro Rolo Aug 29 '11 at 15:39
  • ah ok, I'll look into that thx! Man... I wish ARel was documented a bit better. It's hard to know what features exist without docs. – brad Aug 29 '11 at 15:53
  • There are docs: http://www.rdoc.info/github/rails/arel/master, Though, I agree with you, the lack of better documentation makes arel have a somewhat long learning curve. And I don't like its DSL very much. I believe that one day it will be much better... – Pedro Rolo Aug 29 '11 at 16:14