LocaleDating
LocaleDating does what Rails should be doing. It makes working with dates and times as text in forms painless. It works with your I18n locales to use your desired formats and respects the user's timezone.
LocaleDating generates wrapper methods around the attributes you want to support editing Date, Time, and DateTime values as text using any desired I18n locale format.
Reason for Existence
This simple library was born out of the frustration experienced when using Rails 3.2.x and Ruby 1.9.2. Ruby changed the default date parsing format from mm/dd/yyyy to dd/mm/yyyy. When letting a user edit a date or a time on a form as text, Rails could no longer correctly parse a US date by default.
Common solutions seem to result in:
- explicitly converting the date to text and assigning to the form input
- parsing the form's param in the controller action and assigning to the model
While solving this problem, we get some other nice benefits. More on that after the examples.
How it Works
Use the defined I18n locale formats from your application.
# /config/locales/en.yml
en:
date:
formats:
default: '%m/%d/%Y'
short: '%m/%d/%Y'
long: ! '%Y-%m-%d'
ymd: ! '%Y-%m-%d'
datetime:
formats:
default: '%m/%d/%Y'
short: '%m/%d/%Y'
long: ! '%m/%d/%Y %I:%M%p'
time:
am: am
formats:
default: ! '%m/%d/%Y %I:%M%p'
short: ! '%I:%M%p'
long: ! '%m/%d/%Y %I:%M%p'
pm: pm
Specify which model attributes should have the plugin applied.
# /app/models/person.rb
class Person < ActiveRecord::Base
locale_date :born_on
locale_datetime :last_seen_at
locale_time :start_time
end
Behavior and Benefits
LocaleDating doesn't override the model's attribute. So you can still access the native data type directly as needed.
p = Person.new
p.born_on = Date.new(2010, 10, 4)
LocaleDating creates wrapper methods for accessing the underlying value as text through your desired locale format. It uses locale's 'default' format if no format is specified.
Because the attribute isn't overridden, you can specify multiple supported formats for a single attribute.
# /app/models/person.rb
class Person < ActiveRecord::Base
locale_datetime :last_seen_at
locale_datetime :last_seen_at, :format => :long
locale_datetime :last_seen_at, :format => :short, :ending => :shortened
locale_datetime :last_seen_at, :format => :special, :name => :last_seen_so_special
end
Multiple attributes can be assigned at one time if they are of the same data type.
# /app/models/person.rb
class Person < ActiveRecord::Base
locale_date :born_on, :died_on, :married_on
end
Generated wrapper methods:
- when using the default format on :last_seen_at, it will be 'last_seen_at_as_text'
- when using the format :long on :last_seen_at, it will be 'last_seen_at_as_long'
- when specifying the ending 'shortened', it will be 'last_seen_at_shortened'
- when specifying the name 'last_seen_so_special', it uses it and will be 'last_seen_so_special'
In a view, you specify which version you want used by referencing the name like this:
<%= form_for @person do %>
<%= f.label :born_on_as_text, 'Born On' %>:
<%= f.text_field :born_on_as_text %><br />
<% end %>
The input's value is the :born_on value converted to text using the locale specified format. The controller receives the user's modified text and passes it to the wrapper method. The wrapper method parses the text using the format specified for the locale and respecting the user's timezone.
Its that easy! LocaleDating gives a super simple approach to only add the behavior to the attributes you want while being customizable enough to work for your unique situations.