1

I have a 'search' function where I want to pass in an arbitrary 'filter' condition and have matches returned

The following matches any name/email where the filter string is a match:

@people = Person.all
@people = @people.or(
    {'name.first_name' => /#{filter}/i}, 
    {'name.last_name' => /#{filter}/i}, 
    {'email' => /#{filter}/i }
)

The following correctly does the same on the 'tags' array on the Person record:

@people = Person.all
@people = @people.any_in('tags' => [/#{filter}/i])

Can anyone tell me how to combine the two queries, so that a Person is matched if the filter text is found in the name, email or any of the tags?

mu is too short
  • 426,620
  • 70
  • 833
  • 800
Jak Charlton
  • 231
  • 4
  • 9

2 Answers2

1

It turns out there is a method I was missing here ... found indirectly via https://github.com/mongoid/mongoid/issues/2845

Given these two queryables:

a=Person.where({'name.first_name'=> /a/i})
b=Person.where({'name.first_name'=> /j/i})

You can combine them using .selector

Person.or(a.selector, b.selector).to_a
=> selector={"$or"=>[{"name.first_name"=>/a/i}, {"name.first_name"=>/j/i}]}

or

Person.and(a.selector, b.selector).to_a
=> selector={"$and"=>[{"name.first_name"=>/a/i}, {"name.first_name"=>/j/i}]}
Jak Charlton
  • 231
  • 4
  • 9
0

You don't need to use any_in at all. If say:

:array_field => regex

then MongoDB will automatically check each element of array_field against regex for you, you don't have care about the arrayness at all. That means that you can toss the :tags check in with the other conditions:

regex   = /#{filter}/i
@people = Person.where(
    :$or => [
        { 'name.first_name' => regex },
        { 'name.last_name'  => regex },
        { 'email'           => regex },
        { 'tags'            => regex }
    ]
)

I also pull the regex out of the query into a variable to make it clear that you're using the same one for each check and I switched to where as that's more common (at least in my experience).

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Cheers - this will allow me to do it construction the hash with the conditions, my problem will also come back though when it is to do with scopes ... how can you combine the Mongoid DSL methods/queryable's this way? (ie the way you would pass around AR queryables to compose them) – Jak Charlton Jan 25 '14 at 00:15
  • All the scopes will be available on the Mongoid::Criteria that `where` hands you and vice versa: `Person.where(...).some_scope` or `Person.some_scope.where(...)`. – mu is too short Jan 25 '14 at 00:17
  • But again, this doesn't combine the queries, only refines (ie it is an 'and') I can't find how to do an 'or' of Mongoid criteria – Jak Charlton Jan 26 '14 at 00:46
  • Actually it doesn't 'and' - it appears to replace: Person.where({'name.first_name'=> /a/i}).where('name.first_name'=> /b/i).to_a results in selector={"name.first_name"=>/b/i} – Jak Charlton Jan 26 '14 at 00:58
  • Internally it is doing little more than building a Hash and `M.or(array)` is just a different way of saying `:$or => array`. But if you `M.or(array1).or(array2)` you build `:$or => array1 + array2`. Is that what you're looking for? – mu is too short Jan 26 '14 at 01:03