I have a rails form that displays a date in a text_field:
<%= form.text_field :check_in_date %>
The date is rendered as yyyy-m
Expanding Kamil's answer a bit: add the following method to application_helper.rb:
def date_mdY(date)
if date.nil?
""
else
date.strftime("%m-%d-%Y")
end
end
Then you can modify Kamil's answer slightly:
<%= f.text_field :some_date, :value => date_mdY(@model_instance.some_date) %>
Then you can use this helper method in any other view.
I'm using the jQuery datepicker and the date needed to be in a particular format in order for the default date to be set correctly. Thanks for the answer Kamil!
There are two parts to making this work, and a third part to make it all work well. If you just want to copy paste, go ahead and scroll to the end.
Date
to format itself as desiredThere are a bunch of core extensions to Date in ActiveSupport, but none of them create a new date class. If you're working with Date
, which is what Rails returns when you have a date
column in your database, the method converting that date into a string is Date#to_formatted_s
. The ActiveSupport
extensions add this method, and they alias the to_s
method to it, so when you call Date#to_s
(probably implicitly), you get Date.to_formatted_s
.
There are two ways to change the format used by to_formatted_s
:
Date::DATE_FORMATS
).Change the format that :default
maps to. Since to_formatted_s
sets a default value of :default
, when you call Date#to_s
without passing an argument you get format specified by Date::DATE_FORMATS[:default]
. You can override the default on an application-wide basis like this:
Date::DATE_FORMATS[:default] = "%m/%d/%Y"
I have this set in an initializer like config/initializers/date_formats.rb
. It's crude and doesn't support changing with your localizations, but gets Date#to_s
to return the desired format without any monkey patching or explicitly converting your dates to strings and passing in an argument.
By default, your ActiveRecord
instance will cast date columns using good ol' ISO 8601 that looks like this: yyyy-mm-dd
.
If it doesn't match that format exactly (e.g., it's slash delimited), it falls back to using Ruby's Date._parse, which is a little more lenient, but still expects to see days, months, then years: dd/mm/yyyy
.
To get Rails to parse the dates in the format you're collecting them, you just need to replace the cast_value
method with one of your own. You can monkeypatch ActiveRecord::Types::Date
, but it's not much harder to roll your own type inheriting from it.
I like using Chronic to parse date strings from user input because it puts up with more shit, like "Tomorrow", or "Jan 1 99", or even bonkers input like "5/6-99" (May 6, 1999). Since Chronic returns an instance of Time
and we're overriding the supplied cast_value
method, we need to call to_date
on it.
class EasyDate < ActiveRecord::Type::Date
def cast_value(value)
default = super
parsed = Chronic.parse(value)&.to_date if value.is_a?(String)
parsed || default
end
end
Now we just need to tell ActiveRecord to use EasyDate
instead of ActiveRecord::Type::Date
:
class Pony < ApplicationRecord
attribute :birth_date, EasyDate.new
end
That works, but if you're like me, you want to take on a bunch more work right now so you can save 3 seconds in the future. What if we could tell ActiveRecord to always use EasyDate for columns that are just dates? We can.
ActiveRecord gives you all sorts of information about your model that it uses to handle all of the “magic” behind the scenes. One of the methods it gives you us attribute_types
. If you run that in your console, you get back vomit — it's kind of scary, and you just go “oh nevermind”. If you dig into that vomit like some sort of weirdo, though, it's just a hash that looks like this:
{ column_name: instance_of_a_type_object, ... }
Not scary stuff, but since we're starting to poke at internals, the inspected output of these instance_of_a_type_object
can end up looking obtuse and “closed”. Like this:
#<ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Money:0x007ff974d62688
Thankfully, we really only care about one: ActiveRecord::Types::Date
, so we can narrow it down:
date_attributes = attribute_types.select { |_, type| ActiveRecord::Type::Date === type }
That gives us a list of the attributes that we want to upcycle to use EasyDate
. Now we just need to tell ActiveRecord to use EasyDate on those attributes like we did above:
date_attributes.each do |attr_name, _type|
attribute attr_name, EasyDate.new
end
And that basically does it, but we need to glue it all together. If you're using ApplicationRecord
you can drop this right into it and be off to the races:
def inherited(klass)
super
klass.attribute_types.select { |_, type| ActiveRecord::Type::Date === type }.each do |name, _type|
klass.attribute name, EasyDate.new
end
end
If you don't want to “pollute” ApplicationRecord
, you can do like I did and create a module and extend ApplicationRecord
with it. If you're not using ApplicationRecord
, you'll need to create the module and extend ActiveRecord::Base
.
module FixDateFormatting
# the above `inherited` method here
end
# Either this...
class ApplicationRecord < ActiveRecord::Base
extend FixDateFormatting
end
# ...or this:
ActiveRecord::Base.extend(FixDateFormatting)
If you aren't concerned with the how and just want this to work, you can:
gem "chronic"
to your Gemfile and bundle install
ApplicationRecord
config/initializers/date_formatting.rb
Here's the code to copy:
Date::DATE_FORMATS[:default] = "%m/%d/%Y"
module FixDateFormatting
class EasyDate < ActiveRecord::Type::Date
def cast_value(value)
default = super
parsed = Chronic.parse(value)&.to_date if value.is_a?(String)
parsed || default
end
end
def inherited(subclass)
super
date_attributes = subclass.attribute_types.select { |_, type| ActiveRecord::Type::Date === type }
date_attributes.each do |name, _type|
subclass.attribute name, EasyDate.new
end
end
end
Rails.application.config.to_prepare do
ApplicationRecord.extend(FixDateFormatting)
end
Simple one time solution is to use :value
option within text_field
call instead of adding something to ActiveRecord::Base
or CoreExtensions
.
For example:
<%= f.text_field :some_date, value: @model_instance.some_date.strftime("%d-%m-%Y") %>
If you can set the value manually you can use created_at.strftime("%m-%d-%Y") http://snippets.dzone.com/tag/strftime .
For people interested to format in a specific location and not all over the project, several answers advised to check if null and I want to add that using try with strftime will help to make it shorter.
<%= f.fields_for :assets_depreciations do |asset| %>
<tr>
<td>
<%= asset.text_field :depreciation_date, value: asset.object.depreciation_date.try(:strftime, "%d-%m-%Y") %>
</td>
</tr>
<% end %>
My preferred way of handling this is with virtual attributes. There's a short RailsCast on the subject (3m), but the upshot is that virtual attributes will allow you to use a non-standard format when displaying model attributes in a form, or vice versa (i.e., saving form data to a model).
Basically, rather than creating a form field for the check_in_date
attribute, you can define a “virtual attribute” on the model:
class Reservation
def check_in_date_string
check_in_date.strftime('%m-%d-%Y')
end
def check_in_date_string=(date_string)
self.check_in_date = Date.strptime(date_string, '%m-%d-%Y')
end
end
Now, you can create form fields that correspond to check_in_date_string
rather than check_in_date
:
<%= f.text_field :check_in_date_string %>
That's it! No need to explicitly specify a value
option, and when this form is submitted, the model will be updated appropriately.