12

How can you convert a mysql datetime field into two form fields (1) date only, (2) time only, and combine both fields back into datetime format on form submit?

This would allow the use of the following gems, but store the dates in a single datetime field:

gem 'bootstrap-datepicker-rails'
gem 'bootstrap-timepicker-rails'

Thanks in advance!

Nicholas.V
  • 1,926
  • 2
  • 15
  • 30

4 Answers4

16

Found the solution with help from @Althaf

Added virtual attributes to model.rb Used before_save callback to convert back to datetime.

before_save :convert_to_datetime

def sched_date_field
  sched_date.strftime("%d/%m/%Y") if sched_date.present?
end 

def sched_time_field
  sched_time.strftime("%I:%M%p") if sched_time.present?
end

def sched_date_field=(date)
  # Change back to datetime friendly format
  @sched_date_field = Date.parse(date).strftime("%Y-%m-%d")
end

def sched_time_field=(time)
  # Change back to datetime friendly format
  @sched_time_field = Time.parse(time).strftime("%H:%M:%S")
end

def convert_to_datetime
  self.sched_time = DateTime.parse("#{@sched_date_field} #{@sched_time_field}")
end

Using Rails 4, needed to add sched_date_field and sched_time_field to strong params in controller.rb

Here are the fields in _form.html.erb

<%= f.label :sched_date_field, "Scheduled Date" %>
<%= f.text_field :sched_date_field, :class => "datepicker" %>

<%= f.label :sched_time_field, "Scheduled Time" %>
<%= f.text_field :sched_time_field, :class => "timepicker" %>
Nicholas.V
  • 1,926
  • 2
  • 15
  • 30
  • 1
    By strict params, do you mean "strong params"? Also, this code seems to raise an undefined variable error on sched_time. – Justus Eapen Feb 04 '14 at 16:03
  • 2
    This is a bit of an edge case, but if you use this technique and require any validation on the `sched_time` field, it will fail. You can try changing `before_save` to `before_validation`. But again, this may fail if you have cascading validations. A little hard to explain in this short comment, but trust me, it will fail. – Karl Dec 31 '14 at 23:49
  • @Karl what do you recommend instead? I'm running into this problem.. I thinking of changing my data model to store date, time and timezone as 3 separate fields. – Turgs Oct 13 '15 at 22:09
  • 1
    @Turgs well, it has been a while, but I think I resolved it by using javascript callbacks on the datepicker and timepicker to combine it back into a single datetime and submit that. – Karl Oct 14 '15 at 23:03
  • @Karl yeh that's the way I just went. I've made it a timestamp throughout and use javascript to split it to render and combine into a hidden field for form submission. – Turgs Oct 15 '15 at 05:08
  • 1
    for those using rails 5 - good news is that rails 5 does all the type conversion for you with virtual attributes, simply use the attributes API: https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html – BenKoshy Oct 15 '18 at 08:40
6

You can use date_time_attribute gem:

class MyModel < ActiveRecord::Base
  include DateTimeAttribute

  date_time_attribute :scheduled_at
end

It will allow you to set schedule_at_date and scheduled_at_time separately. Once attributes are set, values will be combined into schedule_at.

Sergei Zinin
  • 161
  • 1
  • 3
5

You could use virtual attributes See this Railscast and if you have a pro subscription the revised one.

Basically in the view you would the following

<%= f.label :date_field %>
<%= f.text :date_field %>
<%= f.label :time_field %>
<%= f.text :time_field %>

Your database would still keep a field which I'll call full_date

Now in your model you would have to define the above 2 fields as follows.

def date_field  # What this returns will be what is shown in the field
  full_date.strftime("%m-%d'%y") if full_date.present?
end 

def time_field
  full_date.strftime("%I:%M%p") if full_date.present?
end

def time_field=(time)
  full_date = DateTime.parse("#{date_field} #{time_field})
end

Since it looks like you are using Rails 4, you'll have to permit date_field and time_field in your strong parameters.

Althaf Hameez
  • 1,511
  • 1
  • 10
  • 18
0

Alternatively, I set up a solution in the controller that does all the datetime conversions before the object gets created, because changing the data in the model impacted all my tests and validations. "Event" is the object I'm creating here with the datetime values being assigned to it.

#In the controller:
def convert_to_datetime_and_assign(event, params)
  date_field = Date.parse(params[:date_field]).strftime("%Y-%m-%d")
  start_time_field = Time.parse(params[:start_time_field]).strftime("%H:%M:%S")
  end_time_field = Time.parse(params[:end_time_field]).strftime("%H:%M:%S")
  event.start_time = DateTime.parse("#{date_field} #{start_time_field}")
  event.end_time = DateTime.parse("#{date_field} #{end_time_field}")
  event
rescue ArgumentError
  event.errors.add(:start_time, :invalid, message: "Date or time was invalid")
  event
end

in the create and update controller methods I called the method above:

@event = convert_to_datetime_and_assign(@event, event_params)

I added fields for date_field, start_time_field and end_time_field in my forms for creating/updating "events". And in the model I added an accessor to be able to access those values.

attr_accessor :date_field, :start_time_field, :end_time_field
gk12345
  • 416
  • 4
  • 15