Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails multiple fields to one model attribute

I have been looking everywhere but can't seem to find a good solution for this.

My form has a date (textfield with datepicker) and a time (textfield with timepicker), which I want to map to an model field called due_at.

So far I been handling it in my controller with separate parameters to join it up to a datetime then set the model field manually, but it's messy and really think this logic should be kept in model/view.

I would like to be able to handle the two form fields to an attribute in the model, then split it back out for errors, edit action etc. Basically a custom way of performing what the standard datetime_select does, but putting my own touch to it.

Is there something that I can put in my model like ?

def due_at=(date, time)
...
end

I been looking a number of places, but can't find out how you would do this. People say to use javascript to populate a hidden field, but just don't seem like the cleanest solution for a pretty simple problem.

Any advice/help would be much appreciated.

Thanks.

like image 449
Andrew Cetinic Avatar asked Jan 18 '23 13:01

Andrew Cetinic


2 Answers

First: please rename your field because created_at may cause conflicts with ActiveRecord.

I did exactly this for a field with the format M/D/YYYY H:M (Hours/Minutes in 24hrs format)

In your model:

attr_accessor :due_date, :due_time

before_validation :make_due_at


def make_due_at
  if @due_date.present? && @due_time.present?
    self.due_at = DateTime.new(@due_date.year, @due_date.month, @due_date.day, @due_time.hour, @due_time.min)
  end
end


def due_date
  return @due_date if @due_date.present?
  return @due_at if @due_at.present?
  return Date.today
end

def due_time
  return @due_time if @due_time.present?
  return @due_at if @due_at.present?
  return Time.now
end 


def due_date=(new_date)
  @due_date = self.string_to_datetime(new_date, I18n.t('date.formats.default'))
end


def due_time=(new_time)
  @due_time = self.string_to_datetime(new_time, I18n.t('time.formats.time'))
end

protected

def string_to_datetime(value, format)
  return value unless value.is_a?(String)

  begin
    DateTime.strptime(value, format)
  rescue ArgumentError
    nil
  end
end

now in the view:

<%= text_field_tag :due_time, I18n.l(@mymodel.due_time, :format => :time) %>
<%= text_field_tag :due_date, I18n.l(@mymodel.due_date, :format => :default) %>

now in the config/locales/en.yml (if english)

  date:
    formats:
      default: "%m/%d/%Y"
  time:
    formats:
      time: "%H:%M"

You may change the date format of course.

like image 73
sled Avatar answered Jan 29 '23 17:01

sled


I think date_time_attribute gem is a good option for you:

class Task < ActiveRecord::Base
  include DateTimeAttribute

  date_time_attribute :due_at
end

It will allow you to set due_at_date and due_at_time separately:

form_for @event do |f|
  f.text_field :due_at_date
  f.text_field :due_at_time
  f.submit
end

# this will work too:
form_for @event do |f|
  f.date_select :due_at_date
  f.time_select :due_at_time, :ignore_date => true
  f.text_field  :due_at_time_zone
  f.submit
end

# or
form_for @event do |f|
  f.date_select :due_at_date
  f.text_field  :due_at_time
  f.submit
end

Here is an example:

task = Task.new
task.due_at                      # => nil

# Date set
task.due_at_date = '2001-02-03'
task.due_at_date                 # => 2001-02-03 00:00:00 +0700
task.due_at_time                 # => 2001-02-03 00:00:00 +0700
task.due_at                      # => 2001-02-03 00:00:00 +0700

# Time set
task.due_at_time = '10:00pm'
task.due_at_time                 # => 2013-12-02 22:00:00 +0800
task.due_at_date                 # => 2001-02-03 00:00:00 +0700
task.due_at                      # => 2001-02-03 22:00:00 +0700

# Time zone is applied as set
task.due_at_time_zone = 'Moscow'
task.due_at                      # => Mon, 03 Feb 2013 22:00:00 MSK +04:00
task.due_at_time_zone = 'London'
task.due_at                      # => Mon, 03 Feb 2013 22:00:00 GMT +00:00

It will also allow you to play with time zones, use Chronic etc.

like image 41
Sergei Zinin Avatar answered Jan 29 '23 17:01

Sergei Zinin