How to handle like one two separate date and time fields? - ruby-on-rails

How to make two separated fields so can be handled to one datetime field in database?
I'm using jquery datetime picker that puts values like value in code example below.
Parameters includes only field with time but ignores field with date.
If I comment field with time then date is sent and saved but parameter looks like "2013-23-02" and it isn't forwarded like multi parameter.
#model
field :start_at, type: DateTime
#view
= f.text_field :start_at, :value => "2013-02-23"
= f.text_field :start_at, :value => "20:20:20"

you can use virtual attributes and callback (this is not tested but you should get the idea)
# model
attr_writer :start_at_time, :start_at_date
before_validation :build_start_at
def start_at_date
#start_at_date ||= start_at.to_date
end
def start_at_time
#start_at_time ||= start_at.strftime("%H:%M")
end
def build_start_at
self.start_at = Time.parse "#{start_at_date} #{start_at_time}"
end
# view
= f.text_field :start_at_date
= f.text_field :start_at_time

Related

getter not getting values for new record on validation rails 4

I have a form_for for creating a new record. I have set getter and setter methods to access form field in my view. Below are my getter ans setter methods with my form view,
Getter & Setter Methods respectively :
def manufacturer_model_name
self.manufacturer_models.pluck(:name).join(', ') unless self.manufacturer_models.blank?
end
def manufacturer_model_name=(names)
names = names.split(',').map{|n| n.strip}.delete_if(&:empty?) if names.present?
names.uniq.each do |name|
id = ManufacturerModel.where(:name => name, :manufacturer_id => manufacturer.id).first_or_create.id
if self.new_record?
self.user_skill_manufacturer_models.build(:user_skill_id => self.id, :manufacturer_model_id => id)
else
self.user_skill_manufacturer_models.where(:user_skill_id => self.id, :manufacturer_model_id => id).first_or_create.id
end
end if names.present?
end
Form View:
= f.text_field :manufacturer_model_name, :class => 'form-control
My problem is that, my input field is autocomplete with multiple set to true, to get multiple values. when user enters multiple comma separated values and submits form and if there are any errors on the form, my new action is rendered and user losts all the entered values forcing him to reenter all again. How can I solve this problem?
It would be better to make a manufacturer_model_name_form field or some such via attr_accessor, and then parse that in validate. It would look something like this:
class ManufacturerModel < ActiveRecord::Base
attr_accessor :manufacturer_model_name_form
validate :validate_manufacturer_model_name
def validate_manufacturer_model_name
errors.add(:manufacturer_model_name, 'not a valid manufacturer model name') if !!true # perform your validation here in place of `!!true`
end
end
Then, in your form, you would use manufacturer_model_name_form instead of manufacturer_model_name:
= f.text_field :manufacturer_model_name_form, :class => 'form-control'

Rails 4: Accept year as string and store as date

In my Rails 4 app, I'd like to accept a year as a string in a text field but store it as a date in the database, in case I ever decide to accept full dates. However, when I try to do this, I keep getting a nil date value. I've verified that the controller is properly passing along the parameters to the model, but at some point before the validation happens, the date has been set to nil.
Is what I'm trying to do possible? And if so, what critical step have I missed? Thanks!
This is the relevant part of the form:
<p>
<%= f.label :publication_date %><br>
<%= f.text_field :publication_date, :size => 4 %>
</p>
And the schema:
create_table "books", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
t.date "publication_date"
...
end
And the model validation:
validates :publication_date, length: { is: 4 }, numericality: true
before_validation :check_pub_date
def check_pub_date
# this prints nil
logger.debug "Publication date: #{self.publication_date}"
end
before_save :convert_pub_year
def convert_pub_year
# Never gets here because validation never passes
logger.debug "Publication year: #{self.publication_date}"
if self.publication_date
self.publication_date = DateTime.strptime(self.publication_date, "%Y")
end
end
Given that your model is expecting a full date to come through, you are hitting a problem when you are passing only a string from your form - I would create a simple text_field to capture the year and go from there...
In your view
<%= text_field_tag :pub_year %>
In your controller
publication_year = params[:pub_year]
#book.publication_date = DateTime.strptime(publication_year, "%Y")
or, of course, factor the parsing of year into a full date into the model.
Edit - expanding on this, it's also possible to create a property in your model that doesn't get saved to the database if you prefer, so in your model, you would have this:
attr_accessor :pub_year
Then, in your convert_pub_year, substitute in pub_year instead of publication_date
def convert_pub_year
if self.pub_year
self.publication_date = DateTime.strptime(self.pub_year, "%Y")
end
end
You controller would need to allow the pub_year parameter in addition to any others you want - so in the private methods - something like the following:
def book_params
params.require(:book).permit(:title, :pub_year)
end
And finally in your view, remove publication_date' and includepub_year`:
<%= f.label :pub_year %><br>
<%= f.text_field :pub_year %>
Hope that helps
The problem exists because validation occurs AFTER assignment of the string passed from the controller, i.e. what you are validating is not the string but the attribute after the type cast to a date. If this fails (because - as in your case - the date is incomplete and consists only of a year), the attribute is nil.
For situations like yours you might want to look into the attribute_before_type_cast functions/variables which will give you access to the original date string BEFORE it was type cast into nil.

Change default population string for form field

I'm building a form on ROR4, using the simple_form gem, to modify my user's data. The thing is that my database is normalized to have the user's first and last name in lower case. So I have the following in my User model:
before_save :lowercase_names
def lowercase_names
self.first_name.downcase!
self.last_name.downcase!
end
But of course when I populate my upadate form with the user object I get the following:
First name: carlos
Last name: ledezma
I was wondering if there is a way to override this behavior so Rails would print instead:
First name: Carlos
Last name: Ledezma
That is, the titleized version of the fields.
Thanks in advance for the help
In your form set the value to titleized name
=f.input :first_name, :value => #user.first_name.titleize
The above line will vary depending on if you are using simple form or not. But it will give you the basic idea. You are overwriting the value of the input field
If you want it to be changed everywhere, then override getter for first_name and last_name
def last_name
self.read_attribute(:last_name).titleize
end
def first_name
self.read_attribute(:first_name).titleize
end
Be aware that this will titleize your first name and last name everywhere you call the getter
Try using the value attribute of the text_field helper in your form to set the shown value as the capitalized first and last name:
<%= f.input :first_name, :value => f.object.first_name.capitalize %>
<%= f.input :last_name, :value => f.object.last_name.capitalize %>
Updated
Also, you can override getter method in User model like:
def first_name
self.read_attribute(:first_name).capitalize
end
def last_name
self.read_attribute(:last_name).capitalize
end

Rails 4 - Convert datetime into separate date and time fields

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!
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" %>
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.
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.
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

Validating field's presence fails even though the field is not blank

I'm trying to fill out an array with values from checkboxes. It works just fine when creating a record, but fails validation when editing. The params look right, which is what really confuses me:
"record"=>{... "type_array"=>["accounting"], ...}
It looks the same as the params from creating a new record. The fields in New.html.erb and Edit.html.erb also use the same markup.
Edit.html.erb
<div class="field">
<%= f.label :type_array, "What type of record?" %><br />
<% ["accounting", "agriculture", "automotive"].each do |type| %>
<%= check_box_tag 'record[type_array][]', type, (true if #record.type_list.include? type),
:id => type %>
<%= label_tag type, type.titleize, :class => type %><br />
<% end %>
</div>
Parts of Record.rb
validates :type_array, :presence => true
attr_accessor :type_array
attr_accessible :type_array
before_validation :set_type_list
private
def set_type_list
self.type_list = type_array.join ',' if type_array.present?
end
Am I missing something? When I remove the type_array validation and fill out the form, it acts like type_array is empty. Somewhere along the line, it must be lost or something.
I appreciate any help.
(Sidenote: if anyone has a better way to do the list of checkboxes, let me know)
Delete the line attr_accessor :type_array.
This creates accessor methods to a new instance variable, not to the model attribute type_array, which means that #record.type_array now refers to that instance variable instead of the attribute.
You almost never use attr_accessor or it's siblings attr_reader and attr_writer in Rails because you want to deal with model attributes, not instance variables.
Edit: You're using type_array as a virtual attribute.
class Record < ActiveRecord::Base
validates :type_array, :presence => true
attr_accessible :type_array
def type_array=(val)
self.type_list = val.join ','
end
def type_array
self.type_list.split ','
end
def type_array_before_type_cast
type_array
end
end
For the reason why you need that last function definition, see this question.

Resources