Reputation: 8158
I am still very new to Rails. Thus far all of my form elements have mapped directly to their underlying Rails model counterparts. In my current situation though that isn't possible.
My model has an event_duration
field which stores a duration in seconds (integer
type).
In my form though I want to have two text fields: hours
and minutes
. When the form is submitted I want to convert those values into seconds and store the resulting value in the model.
What is the best way to accomplish this? (Thank you in advance)
Upvotes: 1
Views: 409
Reputation: 8787
Another way to accomplish this is what's called a Form Object, which is essentially a "mock model" that you pass to your controller in lieu of your model, which then handles form rendering and persistence.
The benefits of this is you don't need any logic in your controllers, and you can eliminate pesky callbacks from your models. They also work exceptionally well for getting rid of those accepts_nested_attributes_for
traps when dealing with multiple models.
It can look something like this:
# app/forms/event_form.rb
class EventForm
include ActiveModel::Model
include ActiveModel::Validations
attr_accessor :duration_hours, :duration_minutes, ... # Event parameters
validates ... # Your validations
def save
if valid?
persist!
else
false
end
end
private
def persist!
event = Event.new(...) # Event parameters
event.event_duration = ((duration_hours * 60) + duration_minutes) * 60
event.save
end
end
By including some of the ActiveModel
modules, you can borrow some functionality, like initializing from a Hash
, and validations.
And in your controller:
# app/controllers/events_controller.rb
def new
@event = EventForm.new
end
def create
@event = EventForm.new(event_params).save
end
There are also Gems that you can use to create form objects, like reform, if you don't want to roll your own.
Upvotes: 1
Reputation: 865
You can create virtual attributes in your model:
class Model < ActiveRecord::Base
attr_reader :hours_event_duration, :minutes_event_duration, :use_diff_event_duration_variables
before_save :correct_save_event_duration
def initialize(*args)
@hours_event_duration = @minutes_event_duration = 0
@use_diff_event_duration_variables = false
super
end
...
protected
def correct_save_event_duration
self.event_duration = @hours_event_duration * 1.hour + @minutes_event_duration * 1.minute if @use_diff_event_duration_variables
end
end
And in your form:
f.hidden_field :use_diff_event_duration_variables, true
f.text_field :hours_event_duration
f.text_field :minutes_event_duration
Upvotes: 1
Reputation: 54902
You can create a 'hook' in your controller to update the params
right before the record is created/updated:
class EventsController < ApplicationController
before_filter :sanitize_durations_in_params, only: [:create, :update]
# ...
def sanitize_durations_in_params
hours = params[:event].delete(:hours).to_i
minutes = params[:event].delete(:minutes).to_i
minutes = minutes + hours * 60
seconds = minutes * 60
params[:event][:event_duration] = seconds
end
end
This code implies that you have an input with name event[:hours]
and another one with event[:minutes]
in your form. If you already have an input with name event[:event_duration]
, it will be overwriten.
Upvotes: 2