2

My application has a model "Appointments" which have a start and end attribute both which are datetimes. I am trying to set the date and time parts separately from my form so I can use a separate date and time picker. I thought I should be able to do it like this. From what I ahve read rails should combine the two parts and then parse the combined field as a datetime like it usually would The error I am getting:

2 error(s) on assignment of multiparameter attributes [error on assignment ["2013-09-16", "15:30"] to start (Missing Parameter - start(3)),error on assignment ["2013-09-16", "16:30"] to end (Missing Parameter - end(3))]

These are the request parameters:

{"utf8"=>"✓", "authenticity_token"=>"OtFaIqpHQFnnphmBmDAcannq5Q9GizwqvvwyJffG6Nk=", "appointment"=>{"patient_id"=>"1", "provider_id"=>"1", "start(1s)"=>"2013-09-16", "start(2s)"=>"15:30", "end(1s)"=>"2013-09-16", "end(2s)"=>"16:30", "status"=>"Confirmed"}, "commit"=>"Create Appointment", "action"=>"create", "controller"=>"appointments"}

My Model

class Appointment < ActiveRecord::Base
    belongs_to :patient
    belongs_to :practice
    belongs_to :provider
  validates_associated :patient, :practice, :provider


end

And the relevant part of the view: (its a simple form)

      <%= f.input :"start(1s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Date.parse(params[:start]) }%>
      <%= f.input :"start(2s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Time.parse(params[:start]).strftime('%R') }%>

      <%= f.input :"end(1s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Date.parse(params[:end]) }%>
      <%= f.input :"end(2s)", :as => :string, :input_html => { :class => 'date_time_picker' , :value => Time.parse(params[:end]).strftime('%R') }%>

UPDATE: THis is now how my model looks like, Ive been trying to do getter/setter methods but I am stuck because start-dat, start_time etc are nil in the model and the parameters aren't sent through

class Appointment < ActiveRecord::Base
    belongs_to :patient
    belongs_to :practice
    belongs_to :provider
    validates_associated :patient, :practice, :provider
    before_validation :make_start, :make_end


    ############ Getter Methods for start/end date/time
    def start_time
        return start.strftime("%X") if start
    end

    def end_time
        return self.end.strftime("%X") if self.end
    end

    def start_date
        return start.strftime("%x") if start
    end

    def end_date
        return self.end.strftime("%x") if self.end
    end


    def start_time=(time)
    end

    def end_time=(time)
    end

    def start_date=(date)
    end

    def end_date=(date)
    end


    def make_start
        if defined?(start_date)
            self.start = DateTime.parse( self.start_date + " " + self.start_time)
        end
    end

    def make_end
        if defined?(end_date)
            self.start = DateTime.parse( end_date + " " + end_time)
        end
    end
end
mattclar
  • 237
  • 1
  • 3
  • 15

2 Answers2

2

Are you trying to emulate #date_select ? If yes, see second part of answer.

Date database typecast

If you want to assign a DateTime to database, it has to be a DateTime object. Here you use an array of strings, ["2013-09-16", "15:30"].

You can easily compute a datetime from those strings using regexps :

/(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/ =~ params[ 'start(1s)' ]
/(?<hours>\d+):(?<minutes>\d+)/ =~ params[ 'start(2s)' ]
datetime = DateTime.new( year.to_i, month.to_i, day.to_i, hours.to_i, minutes.to_i )

This will store year, month, day, hours and minutes in local variables and create a new datatime based on it, which you can then assign to your model.

Yet, databases can't store ruby DateTime instances as is, so behind the hood, a conversion is made by rails when saving a date or datetime field to convert it as string. The method used is #to_s(:db), which gives, for example :

DateTime.now.to_s(:db)     # => "2013-09-17 09:41:04"
Time.now.to_date.to_s(:db) # => "2013-09-17"

So you could theoretically simply join your strings to have proper date representation, but that wouldn't be a good idea, because :

  1. that's implementation details, nothing say this date format won't change in next rails version
  2. if you try to use the datetime after assigning it and before saving (like, in a before_save), it will be a string and not a datetime

Using active_record datetime helpers

As this would be a pain to do that all the time, rails has helpers to create and use datetime form inputs.

FormBuilder#datetime_select will take only the attribute you want and build all needed inputs :

<%= f.datetime_select :start %>

This will actually create 5 inputs, named respectively "start(1i)" (year), "start(2i)" (month), "start(3i)" (day), "start(4i)" (hours) and "start(5i)" (minutes).

If it feels familiar, it's because it's the exact data we retrieved for building a datetime in first part of this answer. When you assign a hash to a datatime field with those exact keys, it will build a datetime object using their values, like we did in first part.

The problem in your own code is that you've just provided "start(1i)" and "start(2i)". Rails doesn't understand, since you only passed it the year and month, a lot less than what is required to compute a datetime.

Bob Whitelock
  • 167
  • 3
  • 12
kik
  • 7,867
  • 2
  • 31
  • 32
  • Olivier I have updated my model and attempted to move to getter setter methods butIam still doing something wrong! – mattclar Sep 17 '13 at 13:04
0

See How do ruby on rails multi parameter attributes *really* work (datetime_select)

According to this question, the multiparameter attribute method works for Date but not DateTime objects. In the case of a Date, you would pass year, month and day as separate values, hence the Missing Parameter - start(3), as the expected third parameter is not there.

DateTime, however, requires at least five params for instantiation DateTime.new(2013, 09, 16, 15, 30), so you cannot rely on the automated parsing in your case. You would have to split your params first and in that case, you could easily parse it yourself before saving the object using a before_filter or similar methods.

See the constructor: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/date/rdoc/DateTime.html#method-c-new

and the multiparam description: http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_multiparameter_attributes

Community
  • 1
  • 1
KappaNossi
  • 2,656
  • 1
  • 15
  • 17
  • I have updated my model and attempted to move to getter setter methods butIam still doing something wrong! – mattclar Sep 17 '13 at 13:04