Simple Form without ActiveRecord
by Paweł Świątkowski
28 Jan 2015
Simple Form is a great gem I’ve been using extensively for most of my Rails projects. It’s one of those that go to Gemfile at the very beginning. However, I only used that for “real” models - derived from ActiveRecord and persisted in the database. Until yesterday. This might seem obvious, but here is a short guide on how to use Simple Form with other objects.
Use case was simple: a message and personal details that user enters on “Contact Us” page. There is, obviously, no point in storing that in database, however I though it would be nice to have a working prototype with Simple Form. Model goes as such:
class ContactMessage < Struct.new(:name, :company, :email, :phone, :send_to_me, :message)
include ActiveModel::Model
end
As you see, it’s nothing fancy. I wanted it to be Struct
, not OpenStruct
to have a well-defined API. Including include ActiveModel::Model
is necessary for Simple Form to have methods such as model_name
, which one could define yourself, but ActiveModel is a dependency of Simple Form, so you will have it available anyway.
And that’s it. Simple and neat, and I have everything right away in my views:
= simple_form_for @message, wrapper: :horizontal_form do |f|
- if @user.nil?
= f.input :name, required: true
= f.input :company
= f.input :email, required: true
= f.input :phone
= f.input :message, as: :text, required: true
= f.input :send_to_me, as: :boolean, wrapper: :horizontal_boolean
= f.button :submit
This goes further. You can (and you should) treat this object like any other in controller. Here we go:
class ContactMessagesController < ApplicationController
def create
@message = ContactMessage.from_hash(safe_params)
# sending actual email etc.
end
private
def safe_params
params.require(:contact_message).permit(:name, :company, :email, :phone, :message, :send_to_me)
end
end
There is only one trick here. Classes formed from Struct
cannot be initialized like ActiveRecord (or OpenStruct) ones - with a hash. Therefore you need to define this little from_hash
method to properly initialize your object. In my case it is as simple as:
def self.from_hash(hash)
obj = self.new
hash.each {|key,value| obj.send("#{key}=", value)}
obj
end