Search code examples
ruby-on-railsruby-on-rails-4mongoidmongoid4

mongoid scope find the wrong conversation


I have the following scope on my model:

scope :between, -> (sender_id,recipient_id) do
    where(sender_id: sender_id, recipient_id: recipient_id).or(sender_id: recipient_id, recipient_id: sender_id).exists?
  end

this try to find the conversation between two persons, the on my controller I have the following action:

def create
    if Conversation.between(params[:sender_id],params[:recipient_id]).exists?
      @conversation = Conversation.between(params[:sender_id],params[:recipient_id]).first
      redirect_to dashboard_conversation_path(@conversation.id)
    else
      @conversation = Conversation.create!(conversation_params)
      redirect_to dashboard_conversation_path(@conversation.id)
    end
  end

And here's my problem:

I have 3 o more user:

Users, A, B, C, D...no conversations between anyone. I create a conversation between user A and User B. The conversation does not exists, so is created, then if User A wants to start a conversation with User C, the model return false, because the conversation does not exists, son my controller need to create a new one, but, instead of that, the controller is open the conversation between user A and User B, but it have to create a new conversation between user A and User C, and open this conversation.

What I'm doing wrong?? I tried on different browsers and clean my cache.


UPDATE:

after create the first conversation, it always show the first conversation. I mean:

First Conversation

  • User A - User B

Trying to create other conversations between the following users:

  • User A - User C, show conversation User A, B
  • User C - User B, show conversation User A, B
  • User C - User D, show conversation User A, B

Solution

  • The or method doesn't work the way you think it does. If you look at the underlying selector:

    Conversation.where(sender_id: s, recipient_id: r).or(sender_id: r, recipient_id: s).selector
    

    you'll see this:

    {
      "sender_id" => s,
      "recipient_id" => r,
      "$or" => [
        {
          "sender_id" => r,
          "recipient_id" => s,
        }
      ]
    }
    

    Calling or on a query doesn't mean "whatever is in the query already or this extra condition", it simply means "and any of these conditions".

    The selector you want is:

    {
      "$or" => [
        { "sender_id" => s, "recipient_id" => r },
        { "sender_id" => r, "recipient_id" => s }
      ]
    }
    

    and that would be built with a single or call:

    or([
      { sender_id: s, recipient_id: r },
      { sender_id: r, recipient_id: s }
    ])
    

    Presumably the trailing exists? call in your scope isn't there in reality. If it is then you're abusing scope to create a plain old class method and saying def self.between(sender_id, recipient_id) would be a better idea.