Power of Eloquence

Working with Datetime fields in Rails as presentation layer

| Comments

When developing a Rails application that deals with a lot of form inputs, you’ve probably come across seeing this often.

Your typical Rails form setup
# Profile form submit <%= form_tag(@contact) do %> <%= label_tag (:name, "Name") %> <%= text_field_tag (:name) %> <br/> <%= label_tag (:email, "Email") %> <%= email_field (:email) %> <br/> <%= label_tag (:address, "Address") %> <%= text_area_tag (:address) %> <br/> ......... <%= submit_tag("Submit") %> <% end %>

As per ruby/rails guides, this is the most basic and most common setup for capturing user input data into our system.

We need to find a way to create one-to-one object mapping between the form fields and their respective ActiveRecord model’s members and properties.

And, we couldn’t have done much better without the help of Rails HTML form helpers to create these data-binding mechanisms within our form input fields against our model objects.

For the majority of the cases, form helpers such as text_field_tag, email_field, text_area_tag are simple to use as we don’t need to provide much configuration properties. They’re just plain text thus Rails can do object-relational mapping easy - without much afterthought.

However, when working with capturing date/time fields such as user’s date of birth, things starting to get more interesting.

If you do something like this

A DateTime object bound to text input field
<%= text_field(:date_of_birth) %>

What you’ll end up is you see a text input field that renders the full UTC format of datetime representation, like so

DateTime in full UTC format
2017-12-12T04:05:06+00:00

This is probably not what you really want to do.

When I searched online for these solutions, there are not many available guides that are deemed useful for our common pattern of use here.

So, I thought I’d share some of my thoughts, findings and learnings.

In the new Rails 4/5, it now comes with several new HTML5 form helper tags we can utilise. We have the following:

  • select_date
  • select_datetime
  • date_select
  • datetime_select

The two tags with prefixes select_ are mainly used for rendering date and datetime values as a view only, while the others with suffixes _select are used for creating and updating date and datetime fields that are data-bound to the ActiveRecord model. Thus, it’s important to know this distinction when placing on your forms.

Their simple uses are.

Example of select_date tag
# uses plain ruby DateTime object <%= select_date(Time.now) %>

vs

Binding a DateTime field against ActiveRecord model
# Must have a ActiveRecord Model object to bind # assuming form is a valid Active Model object with a datetime property called date_of_birth <%= form.date_select(:date_of_birth) %>

What goes behind the scene of these tag helpers is that Rails handles the view abstraction of rendering HTML5 select tags, usually in three or more. Thus you’ll end up seeing something like this.

HTML5 dropdown select tags
<select id=“date_of_birth_year" name=“date_of_birth[year]"> ... </select> <select id=“date_of_birth_month" name=“date_of_birth_[month]"> ... </select> <select id=“date_of_birth_day" name=“date_of_birth_[day]"> ... </select>

In addition, these helper tags help you to dynamically populate the select options tags on the fly and pre-select the values so you don’t have to worry about handing date ranges towards the front end, which is pretty neat.

Those tag helpers accept two extra arguments; one for configuring pre-render datetime logic; the other is for toggling HTML attribute options for select tags. Both them uses ruby hashes.

The following are the common options I used

{prompt: {...}, order: [:day, :month :time]}

prompt - used for setting up a default option text value for all select tags. They can either be predefined by the helpers, or you can customize them yourself, eg

prompt : {day: 'Day', month: 'Month', Year: 'Year'}

This will set the day, month and year select fields to have their default text values respectively.

order - for reordering the select tags around.

By default, the normal order, out of the box is year, month and day. So this is very useful if you want to cater the correct locale settings for your user demographics that are most accustomed to.

For HTML attribute options, we have

  • disabled - to disabled select for interacting. Good for read-only fields.
  • required - to make fields required so the user is required to submit these to the form.
  • autofocus - to give fields autofocus.
  • class - to add CSS class names for all select tags, useful for styling or user interaction purposes like jQuery.
  • style - for inline CSS styles for all select tags, though not recommended for best CSS practices.

Putting things all together, we get, eg.

DateTime select dropdowns that are required along with default prompt messages with reordered elements
<%= form.date_select(:date_of_birth, {prompt: {day: 'Select day', month: 'Select month', Year: 'Select year'}, order: [:day, :month :time]}, {required: true}) %>

That’s it. This is sufficed enough to meet our main requirements for collecting user’s datetime data.

But you might wonder. What if our requirement is to allow a user to select a specific date range, rather than having to select individual date components like above?

We can use another useful tag helper Rails provides us, which is this.

New HTML5 Date Range Field
<%= date_field(:profile, :date_of_birth) %>

However the problem with using date_field tag is, at the time of writing, it’s not cross-browser compatible yet for Safari and Opera. So you need to bear in mind of your users’ demographic base when they interact your site or app. A better alternative to this is to download and install a jQuery datepicker plugin that does the heavy datepicker functionality for you. Then, you use text_field tag for the plugin to pick up, as far as back-end implementation is concerned. No cross-browser compatibility issues anymore. Thus, this also does a good job.

Aside from using form helper tags for the presentation layer, what about supporting other formats like JSON/XML?

Simple.

We do the following:

JSON

Use strftime and then parse its JSON format towards the front-end API
format.json { render json: Time.now.strftime("%d-%m-%Y %H:%M:%S") }

XML

Rails-specific only though.
format.xml { render xml: Time.now.xmlschema } # Alternatively, you can also use stftime format too. They do exactly the same thing.

That’s it.

Till next time.

Happy Coding!

Comments