Question

What's the best way to write a "(x AND y)OR(a AND b)" where query in Rails?

I've just written the following messages method to return messages between two users. The select is to get messages between two users i.e. give me messages from me to them and them to me.

It works but it looks horrid. Is there a simpler/better way of writing this?

class Conversation
  def initialize(me, them)
    @me = me
    @them = them
  end

  def messages
    t = Message.arel_table
    results = Message.where(
      (t[:sender_id].eq(@me.id).and(t[:recipient_id].eq(@them.id))).or(
      t[:sender_id].eq(@them.id).and(t[:recipient_id].eq(@me.id)))
    )
  end
end

NOTE Thanks to Jimmy, I have ended up with:

class Conversation
  def initialize(me, them)
    @me = me
    @them = them
  end

  def messages
    me_to_them = "sender_id = :my_id AND recipient_id = :their_id"
    them_to_me = "sender_id = :their_id AND recipient_id = :my_id"
    Message.where(
      "#{me_to_them} OR #{them_to_me}",
      {:my_id => @me.id, :their_id => @them.id}
    )
  end
end
Was it helpful?

Solution

You can clean it up a little by using a SQL string and the array syntax for value interpolation:

class Conversation < ActiveRecord::Base
  def initialize(me, them)
    @me = me
    @them = them
  end

  def messages
    Message.where(["(sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?)", @me.id, @them.id, @them.id, @me.id])
  end
end

OTHER TIPS

I have built a similar system to this before. My database was structured a little bit differently however to make the queries simpler, and allow for a group conversation (more then 2 people)

private_message_groups 
id | subject

private_message
id | group_id | private_message_group_id | user_id | message

private_message_follow
group_id | user_id | visible 

This way you can simply point the user to a private_message controller. If you want me to go in more depth I can, but the biggest advantage of this system is now you have a forum system.

I had the same problem. I was searching the web for some hours and finally found a method named grouping in Arel::FactoryMethods which simply adds brackets around an expression.

With this your method should look like this:

def messages
  t = Message.arel_table
  results = Message.where(
    t.grouping(t[:sender_id].eq(@me.id).and(t[:recipient_id].eq(@them.id))).or(
    t.grouping(t[:sender_id].eq(@them.id).and(t[:recipient_id].eq(@me.id))))
  )
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top