Simple Form without ActiveRecord

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

Related posts