One DateTime, two inputs… Doing it in Rails
Let's assume you have an Appointment
model and want to store the date and time it is scheduled. Since you need both date and time values the obvious choice is to go with a single datetime
attribute named starts_at
.
Now, you need to display it in a way the user can enter this value. Rails has a built-in datetime_select
helper. It splits the datetime
attribute into five smaller parts: year, month, day, hour and minute and creates one picker for each of these parts.
This would be enough if we wanted our users to select five different inputs, but our application design is different, it requires only two inputs: one for date and another one for time.
This design prevented us of using Rails' built in solution, so we decided to create two virtual attributes to represent both date and time. Let's dive into this solution.
First of all, let's check our view with these two inputs. I'll skip the date picker part for clarity purposes.
In the snippet above there are two attributes which we’ll need to define: starts_at_time
and starts_at_date
. We'll do so in the Appointment
model by creating a getter and a setter for each:
If we load our view now it should render correctly 🎉. But if you try to create the appointment using the view above you'll see that the values chosen for starts_at_date
and starts_at_time
are not being stored in starts_at
. This happens because we still need to map the persistentstarts_at
to the values stored into the two virtual attributes. The most common way of doing this is defining a before_save
callback:
Now we've done it. Our inputs are working and we're saving the correct value to our starts_at
attribute. We could stop right here, but I don't like this approach very much for a couple os reasons. Firstly, it can create problems with validations. In addition to that, we could see some unexpected behavior on other parts of the system due to the callback. Lastly, the starts_at
attribute won't be set/overwritten until we actually save the appointment.
For this reason I've decided to go with a more straightforward solution that updates the value into starts_at
whenever we set one of the values of starts_at_date
or starts_at_time
:
And that's it 🎉. A simple and easy solution when dealing with datetime
fields with multiple inputs.
In our full solution we used these virtual attributes in a React component using the react-rails gem. In future posts I'll explain why and how we did it, so make sure to follow Guava's blog Goiabada and keep track of all our posts :)