"error(s) on assignment of multiparameter attributes" date/time fields - ruby-on-rails

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

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 :
that's implementation details, nothing say this date format won't change in next rails version
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.

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

Related

Save rails time_select as string

I have a string field in my databse
class CreateMHolidays < ActiveRecord::Migration
def change
create_table :m_holidays do |t|
t.string :open_schedule, :limit => 50
end
end
end
I am using time_select to get the value for open_schedule field.
<%= f.time_select :open_schedule, {minute_step: 01, include_blank: true,:default =>{:hour => '00', :minute => '00'},:ignore_date => true}, {:class => 'form-control'} %>
In my controller I try
#m_holidays = MHoliday.new(m_holiday_params)
#open_schedule_hrs = (params[:m_holidays]['open_schedule(4i)']).to_s
#open_schedule_mns = (params[:m_holidays]['open_schedule(5i)']).to_s
#m_holidays.open_schedule = #open_schedule_hrs + ':' + #open_schedule_mns
But when I try to save the record I am getting
ActiveRecord::MultiparameterAssignmentErrors (1 error(s) on assignment
of multiparameter attributes [error on assignment [3, 3] to
open_schedule (Missing Parameter - open_schedule(1))])
This is the first time I am using time_select and I must use it with a string field rather than :time. How to go about this? Any help much appreciated
You're getting the ActiveRecord::MultiparameterAssignmentErrors because of the mass parameter assignment on the line #m_holidays = MHoliday.new(m_holiday_params). This might be due to m_holiday_params containing parameters that your MHoliday model doesn't know what to do with.
Try filtering out everything related to the open_schedule input from m_holiday_params. If you have an m_holiday_params method like this:
def m_holiday_params
params.require(:m_holiday).permit('open_schedule(4i)', 'open_schedule(5i)', ...)
end
then omit the open_schedule parameters:
def m_holiday_params
params.require(:m_holiday).permit(...)
end
Then you can manually set up your open_schedule string, as you've already done.

Rails Thinks Field is Nil When it Shouldn't

I have an Event model which has a start_date and end_date. I have a simple validation to make sure that the end_date is after the start_date, however, the validation keeps failing when a date field is not changed, but another field is updated. In such cases, it interprets the fields are nil, even though in the trace, the record shows the proper field values.
# error
undefined method `<' for nil:NilClass
app/models/event.rb:32:in `end_after_start'
# validation in event.rb
attr_accessible :end_date, :start_date
validate :end_after_start
def end_after_start
if end_date < start_date
errors.add(:end_date, "must be after the start date")
end
end
# request parameters in trace
{"utf8"=>"✓",
"_method"=>"put",
"authenticity_token"=>"DD6rVimJxAJclO4IKfv69Txn8XkJZ4IpHZhh+cHOpg4=",
"event"=>{"name"=>"Birthday",
"start_date"=>"05/16/2013",
"end_date"=>"05/31/2013",
"commit"=>"Submit",
"id"=>"5"}
# _form
<%= f.text_field :start_date, :value => (#event.start_date.strftime("%m/%d/%Y") if #event.start_date.present?) %>
<%= f.text_field :end_date, :value => (#event.end_date.strftime("%m/%d/%Y") if #event.end_date.present?) %>
Even though I see the end_date and start_date populated in the trace parameters, if I add put start_date (or end_date) in the end_after_start validation, it prints as nil to the console.
The problem is your form is formatting your dates as "mm/dd/yyyy" and the fields are being submitted to your application in that format as strings.
There is no implicit conversion of a string in that format to a DateTime so your start_date and end_date are ending up nil.
Try to set:
attr_accessor :end_date, :start_date
If you do not have the columns in your database, you will need that.
attr_accessible allows parameters in a mass association.
attr_accessor sets a getter and a setter.
If you have those columns you could try prepending self..
I got this to work by installing the american_date gem. It allows for my date field to be displayed as MM/DD/YYYY and validates and saves my date correctly.

Outputting a serialized object in Rails

In Rails 2.3.6 I'm storing some serialized data in a database field.
My "feed_event.data" field in my database is stored as text and is (for example) equal to:
{:post=>{:pic=>"http://s3.amazonaws.com/criticalcity/datas/3524/big_thumb/send-a-letter.jpg", :name=>"Un’istruzione perfetta", :id=>1995, :authors=>"Delilah"}, :user=>{:pic=>"http://s3.amazonaws.com/criticalcity/avatars/537/thumb/DSCN2744.JPG", :name=>"Luci!", :id=>537}}
Now I need to output this field as a string (exactly as it is in the database), but when I ask:
puts feed_event.data
outputs:
postpichttp://s3.amazonaws.com/criticalcity/datas/3524/big_thumb/send-a-letter.jpgnameUn’istruzione perfettaid1995authorsDelilahuserpichttp://s3.amazonaws.com/criticalcity/avatars/537/thumb/DSCN2744.JPGnameLuci!
Why?
How can I output it as a yaml string?
UPDATE
In order to create it I have this in my FeedEvent model:
class FeedEvent < ActiveRecord::Base
has_many :user_feed_events, :dependent => :destroy
has_many :users, :through => :user_feed_events
serialize :data
end
And in order to create a new FeedEvent element I do:
feed = FeedEvent.create(:event_type => "comment #{commentable_type}", :type_id => id, :data => {:user => {:id => user.id, :name => user.name, :pic => user.avatar.url(:thumb)}, :comment => {:id => id, :body => body, :commentable_id => commentable_id, :commentable_type => :commentable_type, :commentable_name => commentable.name}})
UPDATE #2
following nzifnab's hint I used the .to_yaml method, but what Rails outputs in this case is:
data: "--- \n:post: \n :pic: http://s3.amazonaws.com/criticalcity/datas/3524/big_thumb/send-a-letter.jpg\n :authors: Delilah\n :name: \"Un\\xE2\\x80\\x99istruzione perfetta\"\n :id: 1995\n:user: \n :pic: http://s3.amazonaws.com/criticalcity/avatars/537/thumb/DSCN2744.JPG\n :name: Luci!\n :id: 537\n"
Also commenting "serialize :data" in the model outputs the same.
Thanks,
Augusto
When you call feed_data.data rails has automatically de-serialized your string. You could print it out like this:
feed_data.data.inspect to get the ruby hash representation as a string, but since it's already de-serialized it for you do you need to do anything else?
you can call everything on it like feed_data.data[:post][:pic]
I'm not sure what method you can use to grab the raw serialized string from the record, but usually you don't need to.
By default, serialization is made in a Hash.
Simply loop it to display it's content:
<% feed_event.data.each do |key, value| %>
<%= "#{key}: #{value}" %>
<% end %>
I'm just unsure about nesting level here but you've got the idea.
as you mentioned in your Update, the right way to do this is to put "serialize :data" in your model.
Then, you can access the data attribute as a Hash, that's the default, and it gets automatically persisted when you save your object.
Important Note:
One important thing for this to work is that you define the database field as text or string -- not as a binary field -- otherwise this will not work correctly!

Ruby on Rails - Multiparameter assignment and attr_accessor and 12_hour_time

Ok, I'm slowly getting a grasp on this, but I need some more help.
I'm using a time_select in my view, therefore I'm dealing with a multiparameter assignment. Check.
<%= pt.time_select :time, :twelve_hour => true, :minute_step => 5 %>
BUT I'm doing the naughty thing and using it with an attribute that isn't in the database:
attr_accessor time
Therefore since it can't look at the db, it can't piece together what the multiparameter assignment is supposed to be and therefore I get the following error:
1 error(s) on assignment of multiparameter attributes
Thus I am using information I found here:
composed_of :time,
:class_name => 'DateTime',
:mapping => [%w(DateTime to_s)],
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
Other helpful links: rubyonrails.org | apidock.com
This remove the error, but now the issue is that the plugin I'm using doesn't function properly, I think. I am using http://code.google.com/p/rails-twelve-hour-time-plugin/. The goal being, I'm trying to get the time select to have 3 drop downs: hh:mm am/pm.
So the question is: how do I need to adjust my composed_of method in order for it to be properly converted by the plugin? OR is there a better method to this madness?
I'm not sure what mappers / constructors / converters I need. Right now the object keeps the hour and minute, except the hour isn't converted to 24 hour (which the plugin is supposed to take care of, I thought).
The issue was that I didn't follow the article properly. I should have been using the Time model.
composed_of :time,
:class_name => 'Time',
:mapping => [%w(Time to_s)],
:constructor => Proc.new{ |item| item },
:converter => Proc.new{ |item| item }
Looks like this is a common problem. For more information about it check out this link. The suggestion given there is to do:
class Whatever < ActiveRecord::Base
...
attr_accessor :arrival_time
columns_hash["arrival_time"] = ActiveRecord::ConnectionAdapters::Column.new("arrival_time", nil, "time")
end
I'm still working out bugs, but I'll update this when I get something working.

Rails Way: Formatting Value Before Setting it in the Model?

I have form fields where the user enters in:
percents: 50.5%
money: $144.99
dates: Wednesday, Jan 12th, 2010
...
The percent and money type attributes are saved as decimal fields with ActiveRecord, and the dates are datetime or date fields.
It's easy to convert between formats in javascript, and you could theoretically convert them to the activerecord acceptable format onsubmit, but that's not a decent solution.
I would like to do something override the accessors in ActiveRecord so when they are set it converts them from any string to the appropriate format, but that's not the best either.
What I don't want is to have to run them through a separate processor object, which would require something like this in a controller:
def create
# params == {:product => {:price => "$144.99", :date => "Wednesday, Jan 12, 2011", :percent => "12.9%"}}
formatted_params = Product.format_params(params[:product])
# format_params == {:product => {:price => 144.99, :date => Wed, 12 Jan 2011, :percent => 12.90}}
#product = Product.new(format_params)
#product.save
# ...
end
I would like for it to be completely transparent. Where is the hook in ActiveRecord so I can do this the Rails Way?
Update
I am just doing this for now: https://gist.github.com/727494
class Product < ActiveRecord::Base
format :price, :except => /\$/
end
product = Product.new(:price => "$199.99")
product.price #=> #<BigDecimal:10b001ef8,'0.19999E3',18(18)>
You could override the setter or getter.
Overriding the setter:
class Product < ActiveRecord::Base
def price=(price)
self[:price] = price.to_s.gsub(/[^0-9\.]/, '')
end
end
Overriding the getter:
class Product < ActiveRecord::Base
def price
self[:price].to_s.gsub(/[^0-9\.]/, ''))
end
end
The difference is that the latter method still stores anything the user entered, but retrieves it formatted, while the first one, stores the formatted version.
These methods will be used when you call Product.new(...) or update_attributes, etc...
You can use a before validation hook to normalize out your params such as before_validation
class Product < ActiveRecord::Base
before_validation :format_params
.....
def format_params
self.price = price.gsub(/[^0-9\.]/, "")
....
end
Use monetize gem for parsing numbers.
Example
Monetize.parse(val).amount

Resources