2

I'm trying to write a query for an embedded Mongoid::Document which finds any record where the "address" field is neither nil nor "".

Using a combination of the MongoDB documentation, this issue in the Mongoid bug reports, and the Mongoid documentation, I think that something like this should work:

scope :with_address, where("$or" => [{:address => {"$ne" => nil}}, {:address => {"$ne" => ""}}])

When I run this, the selector looks ok:

1.9.2p290 :002 > report.document.records.with_address
 => #<Mongoid::Criteria
  selector: {"$or"=>[{:address=>{"$ne"=>nil}}, {:address=>{"$ne"=>""}}]},  
  options:  {},
  class:    GlobalBoarding::MerchantPrincipal,
  embedded: true>

But when I look at the results, they contain an entry with a blank address:

1.9.2p290 :007 > report.document.records.with_address.last  
<Record _id: 4f593f245af0501074000122, _type: nil,  version: 1, name: "principal contact 3", title: "", dob: nil, address: "", email: "", phone: "", fax: ""> 

I can't figure out if I'm doing a query wrong, if this is a bug with Mongoid, or if there is some other issue. Does anyone have experience with such a query?

kclair
  • 2,124
  • 1
  • 14
  • 18
  • 4
    Shouldn't the query be using `$and` instead of `$or`? – Dieseltime Mar 09 '12 at 22:52
  • no, that would never match ?? if the value is nil, it won't match "", and vice versa. – kclair Mar 09 '12 at 22:56
  • 1
    I agree, your or doesn't make sense. It's boolean logic, a!=1 or a!=2 will always return true, because it will always be not 1 or not 2. – Eve Freeman Mar 09 '12 at 23:04
  • agh! i knew i was probably getting lost in the logic of or and negative matching! thanks. – kclair Mar 09 '12 at 23:07
  • Agree with @Dieseltime, except, don't use $and, since that's the default when you filter on multiple criteria. $and actually makes it slower in some cases. – Eve Freeman Mar 09 '12 at 23:26

3 Answers3

4

in the end, this is the only way i could find that works to select records where a certain field is not nil and not blank:

scope :with_name, all_of(:name.ne => nil).all_of(:name.ne => "")
kclair
  • 2,124
  • 1
  • 14
  • 18
2

I think you're going to chuckle at this.

Neither nil nor "" is the same as saying: Not nil and not "".

You really mean and, and that can be expressed without $and, using just:

$ne=>nil, $ne=>""
Eve Freeman
  • 32,467
  • 4
  • 86
  • 101
  • Are you referring to mongoid syntax here, or mongo? Trying to extrapolate from your suggestion, I have tried both of these in mongoid: where(:name.ne => nil, :name.ne => ""), where(:address => {"$ne" => "", "$ne" => nil }). in both cases, mongoid appears to be collapsing the criteria, so that the former ends up as {:name=>{"$ne"=>""}} for the selector, and the latter ends up as {:address=>{"$ne"=>nil}} – kclair Mar 09 '12 at 23:43
  • Sorry, yeah the mongo syntax would be: {address:{$ne:null}, address:{$ne:""}} – Eve Freeman Mar 10 '12 at 00:07
  • Looks like excludes is the proper way to do it in mongoid? Model.excludes(address: nil, address: "") -- or maybe not, since it's an embedded doc... – Eve Freeman Mar 10 '12 at 00:12
  • excludes actually has the same behavior: excludes(name: nil, name: "") becomes selector: {:name=>{"$ne"=>""}} – kclair Mar 12 '12 at 17:04
0

You can do the more succint:

scope :with_name, where(:name.nin => ["", nil])

See MongoDB manual.