Mongoid/Moped invalid day of month getting saved

160 Views Asked by At

I want to have DateTimes with invalid days of the month to be validated. I'm using Mongoid 4alpha2 with Rails 4, and on my model I have a

field :date_of_birth, type: DateTime

When I do a regular "create" from a controller for a date_of_birth with "1988/02/30", the model gets saved with a date_of_birth of "1988/03/1" instead of getting a DateInvalid error, like a regular DateTime.new(1988,2,30) would in rails console. I'm not sure if moped or mongoid is circumventing the Rails validations on DateTime, has anyone else encountered this?

Here's the rails log

Started POST "/drivers" for 127.0.0.1 at 2014-01-16 10:42:40 -0500 Processing by DriversController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"LiKx1ZToNVtNL9FAEgyLNNWW7mABy2BPKPwVVcTtXKk=", "driver"=>{"field_worker_name"=>"Field Worker", "hack_number"=>"38924", "first_name"=>"test", "middle_initial"=>"", "last_name"=>"testing", "date_of_birth"=>"1988/02/30", "gender"=>"", "nationality"=>"", "language"=>"", "street"=>"something", "apartment_number"=>"", "city"=>"something", "state"=>"NY", "zip_code"=>"02398", "cell_phone"=>"", "other_phone"=>"", "email"=>"", "dmv_number"=>"", "state_of_residence"=>"", "has_health_insurance"=>"false", "health_plan"=>"", "date_of_recertification"=>""}, "commit"=>"Add Driver"}

MOPED: 127.0.0.1:27017 COMMAND database=admin command={:ismaster=>1} runtime: 0.5280ms

MOPED: 127.0.0.1:27017 QUERY database=healthfund_development collection=users selector={"$query"=>{"_id"=>BSON::ObjectId('52d59524544b38160c000000')}, "$orderby"=>{:_id=>1}} flags=[] limit=-1 skip=0 batch_size=nil fields=nil runtime: 0.4150ms

MOPED: 127.0.0.1:27017 QUERY database=healthfund_development collection=drivers selector={"hack_number"=>38924} flags=[] limit=-1 skip=0 batch_size=nil fields={:_id=>1} runtime: 1.1000ms

MOPED: 127.0.0.1:27017 INSERT database=healthfund_development collection=drivers documents=[{"field_worker_name"=>"Field Worker", "hack_number"=>38924, "first_name"=>"test", "middle_initial"=>"", "last_name"=>"testing", "date_of_birth"=>1988-03-01 00:00:00 UTC, "gender"=>"", "street"=>"something", "apartment_number"=>"", "city"=>"something", "state"=>"NY", "zip_code"=>"02398", "cell_phone"=>"", "other_phone"=>"", "email"=>"", "nationality"=>"", "language"=>"", "dmv_number"=>"", "state_of_residence"=>"", "has_health_insurance"=>false, "health_plan"=>"", "date_of_recertification"=>nil, "_id"=>38924, "updated_at"=>2014-01-16 15:42:40 UTC, "created_at"=>2014-01-16 15:42:40 UTC}] flags=[] COMMAND database=healthfund_development command={:getlasterror=>1, :w=>1} runtime: 3.4590ms Redirected to http://localhost:3000/drivers/38924 Completed 302 Found in 17ms

1

There are 1 best solutions below

0
On BEST ANSWER

Looks like Mongoid is leaving the date parsing up to MongoDB. Check it in the MongoDB shell:

> new Date('1988/02/30')
ISODate("1988-03-01T08:00:00Z")

That's perfectly acceptable behavior for JavaScript's Date constructor:

Note: Where Date is called as a constructor with more than one argument, if values are greater than their logical range (e.g. 13 is provided as the month value or 70 for the minute value), the adjacent value will be adjusted. E.g. new Date(2013,13,1) is equivalent to new Date(2014,1,1), both create a date for 2014-01-01. Similarly for other values: new Date(2013,2,1,0,70) is equivalent to new Date(2013,2,1,1,10) which both create a date for 2013-02-01T01:10:00.

So as far as MongoDB is concerned, 1988/02/30 is 1988 plus 2 months plus 30 days and since 1988 is a leap year, February plus 30 days in March 1st.

You should be using a :type => Date for a date-of-birth anyway. Of course, Date has the same "treat it like JavaScript" behavior:

class M
  include Mongoid::Document
  field :d, :type => Date
end

m = M.create(:d => '1988/02/30')
m.d
# Tue, 01 Mar 1988

so that doesn't help much.

You could report a bug and see what the Mongoid people think about this. In the mean time, if you need stricter parsing, you could do it yourself:

field :date_of_birth, :type => Date

def date_of_birth=(date)
  super(Date.parse(date))
end

Then you'll get your exception when you say Model.create(:date_of_birth => '1988/02/30').