2 fields, 1 model attribute, date not parsing - ruby-on-rails

I have 2 fields that is asscociated with 1 attribute in my model. I am applying it in 2 of my attributes: start_at, and end_at. I am using Timepicker Plugin for jQuery.
I based my answer here: https://stackoverflow.com/questions/18461798/rails-4-convert-datetime-into-separate-date-and-time-fields#=
MY PROBLEM IS THAT THE DATE IS NOT PARSING PROPERLY.
Below are my codes:
Appointment model:
before_save :convert_to_datetime
attr_accessor :start_date, :start_time, :end_date, :end_time
def start_date
start_at.strftime("%d/%m/%Y") if start_at.present?
end
def start_time
start_at.strftime("%I:%M%p") if start_at.present?
end
def start_date=(date)
# Change back to datetime friendly format
#start_date = Date.parse(date).strftime("%Y-%m-%d")
end
def start_time=(time)
# Change back to datetime friendly format
#start_time = Time.parse(time).strftime("%H:%M:%S")
end
def end_date
end_at.strftime("%d/%m/%Y") if end_at.present?
end
def end_time
end_at.strftime("%I:%M%p") if end_at.present?
end
def end_date=(date)
# Change back to datetime friendly format
#end_date = Date.parse(date).strftime("%Y-%m-%d")
end
def end_time=(time)
# Change back to datetime friendly format
#end_time = Time.parse(time).strftime("%H:%M:%S")
end
def convert_to_datetime
self.start_at = DateTime.parse("#{#start_date} #{#start_time}")
self.end_at = DateTime.parse("#{#end_date} #{#end_time}")
end
Strong params:
params.require(:task).permit(:category_id, :subcategory_id, :title, :description, :pay_offer, :pay_type, :county_id, :area_id, appointments_attributes: [:id, :start_date, :start_time, :end_date, :end_time])
If you are wondering, appointment is a nested attribute of task model.
Here is the error:
ArgumentError (invalid date):
app/models/appointment.rb:26:in `parse'
app/models/appointment.rb:26:in `start_date='
app/controllers/tasks_controller.rb:9:in `create'
Its referring to this line: #start_date = Date.parse(date).strftime("%Y-%m-%d")
LOG:
"appointments_attributes"=>{"0"=>{"start_date"=>"3/20/2015",
"start_time"=>"12:30 AM",
"end_date"=>"3/21/2015",
"end_time"=>"01:30 AM"}}},
"commit"=>"Create Task"}
Please help. :(

The problem is that Ruby is expecting date in %d/%m/%Y format, and you are passing it in %m/%d/%Y (notice that day and month have changed place). Date "3/20/2015" is invalid because there is no 20th month. :)
Instead of using just Date.parse you should use strptime which allows you to specify date format that you want to parse.
Date.strptime(date, "%m/%d/%Y")

Related

Rails: can one modify date attribute in model by callback? E.g.: Date.current + 1 (d + other)

I have a table column - valid_to, which should represent a date: 30 days from the time the entry was saved into database.
But how can I do such a thing in model?
E.g. in controller I can do such thing this way:
#baby = baby.create(baby_params.
merge( :valid_to => DateTime.current + 30 )
In view I can use hidden field in the form:
<%= f.hidden_field :valid_to => DateTime.current + 30 %>
so is there a way to do such a thing in model? I tried defining self.valid_to before_validation but for no avail: in irb my valid_to column is just nil. To add: I store it as datetime not string.
UPDATE
solution in the end was:
before_validation :set_valid_to, :on => :create
def set_valid_to
self[:valid_to] = 30.days.from_now
end
and lived this thing in module, but that's another story...
The below should work if you only want it done on initial record creation. If you want it updated every time it's saved use before_save instead of before_create.
class Baby < ActiveRecord::Base
before_create :set_valid_to
private
def set_valid_to
self.valid_to = 30.days.from_now
end
end
in irb:
#baby = baby.create
#baby.valid_to = Time.now + 30.days
#baby.save

Rails: Mapping a Date from form parameters into ActiveModel object

I have a filter-class which includes ActiveModel and consists of two dates:
class MealFilter
include ActiveModel::Model
attribute :day_from, Date
attribute :day_to, Date
end
That model is rendered into a form as following:
<%= form_for(#filter) do |f| %>
<div class="form-group form-group--date">
<%= f.label :day_from %>
<%= f.date_select :day_from %>
</div>
<div class="form-group form-group--date">
<%= f.label :day_to %>
<%= f.date_select :day_to %>
</div>
<% end %>
The problem is now, when the form gets submitted, it sends this parameters to the controller:
{"utf8"=>"✓", "meal_filter"=>{"day_from(1i)"=>"2016", "day_from(2i)"=>"1", "day_from(3i)"=>"29", "day_to(1i)"=>"2016", "day_to(2i)"=>"1", "day_to(3i)"=>"30"}, "commit"=>"Filter"}
I extract the values via Controller parameters:
def meal_filter_params
if params.has_key? :meal_filter
params.require(:meal_filter).permit(:day_from, :day_to)
end
end
if I now assign the params[:meal_filter] to my MealFilter class with #filter = MealFilter.new(meal_filter_params), my date fields are not updated correctly. It seams that the 1i, 2i, 3i parts are not correctly assigned to the dates.
However, this works fine if used an ActiveRecord class.
Do I miss some include? Does anyone know, where this magic mapping is implemented if not in ActiveModel::Model?
I came across this issue while upgrading an app from Rails 3.2 to 5.0
How I sorted it out is as follows:
For Rails 5
class MealFilter
include ActiveRecord::AttributeAssignment
attr_reader :day_from
def initialize(attributes = {})
self.attributes = attributes || {}
end
def day_from=(value)
#day_from = ActiveRecord::Type::Date.new.cast(value)
end
end
For Rails 4.2
class MealFilter
include ActiveRecord::AttributeAssignment
attr_accessor :day_from
def initialize(attributes = {})
self.attributes = attributes || {}
end
def type_for_attribute(name)
case name
when "day_from" then ActiveRecord::Type::Date.new
end
end
end
Then you can do:
attributes = { "day_from(3i)" => "1", "day_from(2i)" => "9", "day_from(1i)" => "2020" }
meal_filter = MealFilter.new(attributes)
meal_filter.day_from
# => Tue, 01 Sep 2020
Ok, found a solution.
What I needed was the MultiparameterAssignment which is actually implemented in ActiveRecord but not in ActiveModel.
As far as I can see, there is an open pull request (https://github.com/rails/rails/pull/8189) that should resolve this issue.
But in the meantime, some clever guy wrote a module that can be included into the model: https://gist.github.com/mhuggins/6c3d343fd800cf88f28e
All you need to do is to include the concern and define a class_for_attribute method that returns the class where your attribute should be mapped to - in my case Date.
You can simply access the 1i, 2i and 3i parameters from the date_select helper and combine them to make new Date in a before_validation callback:
class DateOfBirth
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Validations::Callbacks
attribute :date_of_birth, :date
attribute "date_of_birth(3i)", :string
attribute "date_of_birth(2i)", :string
attribute "date_of_birth(1i)", :string
before_validation :make_a_date
validates :date_of_birth, presence: { message: "You need to enter a valid date of birth" }
def make_a_date
year = self.send("date_of_birth(1i)").to_i
month = self.send("date_of_birth(2i)").to_i
day = self.send("date_of_birth(3i)").to_i
begin # catch invalid dates, e.g. 31 Feb
self.date_of_birth = Date.new(year, month, day)
rescue ArgumentError
return
end
end
end

Time format in ActiveAdmin

I have no idea how i can format time in ActiveAdmin.
This is my index list:
index do
selectable_column
column :book
column :user
column :time
actions
end
How can i format field :time as %H:%i:%s? Is it possible.
I need something like:
:format => :short
In config/initializers/active_admin.rb, look for localize_format:
config.localize_format = '%H:%i:%s'
If you want to change it every where then modify the format in config/locale/en.yml like so
en:
time:
formats:
long: "%Y-%m-%d %H:%M:%S"
else for just your present column then do
column(:time) { |time| object.time.strftime( "%Y-%m-%d %H:%M:%S") }
The Good way: Use a decorator.
The Poor way: column (:time) {|obj| obj.time.to_s(:short)}
With a decorator (create a decorators directory)
app/decorators/results_decorator.rb
class ResultsDecorator
def intialize(result)
#result = result
end
def time
unless #result.time.nil?
#result.time.to_s(:short)
end
end
end
app/admin/result.rb
index do
selectable_column
column :book
column :user
column ('Time') {|result| ResultsDecorator.new(result).time }
actions
end

How to handle date fields in a non-model Rails form?

I am creating a non-model object that will be used with a Rails form builder by using ActiveModel. This is a Rails 3 project. Here's an example of what I have so far:
class SalesReport
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :promotion_code, :start_date, :end_date
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
I happen to be using HAML and simple_form, but that's not important. Ultimately, I'm just using standard Rails date select fields:
= simple_form_for [:admin, #report], as: :report, url: admin_reports_path do |f|
= f.input :promotion_code, label: 'Promo Code'
= f.input :start_date, as: :date
= f.input :end_date, as: :date
= f.button :submit
Rails splits up the date fields into individual fields, so when the form is submitted, there are actually 3 date fields that are submitted:
{
"report" => {
"start_date(1i)" => "2014",
"start_date(2i)" => "4",
"start_date(3i)" => "1"
}
}
In my SalesReport object, I'm assigning the params to my attr methods, but I'm getting an error that I don't have a start_date(1i)= method, which I obviously haven't defined. Ultimately, I'd like to end up with a Date object that I can use instead of 3 separate fields.
How should I handle these date fields in my non-model object?
In your initialization you could manually assign the values from attributes to the class methods and down below override you start_date and end_date setter methods.
class SalesReport
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :promotion_code, :start_date, :end_date
def initialize(attributes = {})
#promotion_code = attributes['promotion_code']
year = attributes['start_date(1i)']
month = attributes['start_date(2i)']
day = attributes['start_date(3i)']
self.start_date = [year, month, day]
end
def start_date=(value)
if value.is_a?(Array)
#start_date = Date.new(value[0].to_i, value[1].to_i, value[2].to_i)
else
#start_date = value
end
end
def persisted?
false
end
end
This should allow you to give the setter a Date instance or an Array with the separate date elements and the setter will assign the correct date to #start_date.
Just do the same for #end_date.
Hope this can help you.

Set virtual attributes using real attributes

I have a model with a virtual attribute for a time interval:
attr_accessible :description, :time_end, :time_start, :duration
belongs_to :timesheet
def duration
if attribute_present?("time_start") and attribute_present?("time_end")
ChronicDuration.output(self.time_end - self.time_start)
else
ChronicDuration.output(0)
end
end
def duration=(d)
self.time_end = self.time_start + d
end
However, when creating a new object, Rails tries to set duration before start, leading to an error. How can I make sure that duration is set after start?
error:
undefined method `+' for nil:NilClass
params:
{"utf8"=>"✓",
"authenticity_token"=>"dg+CysIxZORyV3cwvD+LdWckFdHgecGDFDBNOip+iKo=",
"entry"=>{"time_start"=>"now",
"duration"=>"2h",
"description"=>""},
"commit"=>"Create Entry"}
A few things
Worth reading about: and vs && in ruby - http://devblog.avdi.org/2010/08/02/using-and-and-or-in-ruby/
some alternates to using attribute_present? method
# opposite of blank? - http://api.rubyonrails.org/classes/Object.html#method-i-present-3F
if time_start.present? && time_end.present?
# short hand syntax for present?
if time_start? && time_end?
I don't think your problem is with duration being set before time_start, assuming time_start is a datetime or time database type
try this in rails console
entry = Entry.new
entry.time_start = "now"
# => "now"
entry.time_start
# => nil
you are passing strings into time objects and rails / ruby just sets the value to nil.
If time_end and time_start were strings I still don't think your code would give you the result you want?
def duration=(d)
self.time_end = self.time_start + d
end
# params: time_start = "now"
# params: duration = "2h"
# time_end would be: now2h
if I am wrong about duration= running before time_start is set, an alternative would be something like this using a before_save callback
class Entry < ActiveRecord::Base
before_save :set_time_end
attr_accessor :duration
attr_accessible :description, :time_end, :time_start, :duration
belongs_to :timesheet
def duration
if time_start? && time_end?
ChronicDuration.output(self.time_end - self.time_start)
else
ChronicDuration.output(0)
end
end
def set_time_end
return nil if time_start.blank?
self.time_end = self.time_start + self.duration
end
end
1.) Its not clever to name an attribute end because thats a keyword and it might cause some trouble.
2.) Please post your params hash

Resources