Reading through the Release Notes of Rails 5.2, things like ActiveStorage made me curious so that I wanted to give it a try. I went ahead and installed a pre-release version to build a simple app.

My goal was to create an application that allows a user to create questionnaires and then collect answers. I started out with a form object, that would take the title of a questionnaire and a list of questions.

# db/migrate/create_questionnaires.rb create_table "questionnaires" , force: :cascade do | t | t . string "title" t . string "questions" , default: [], null: false , array: true t . datetime "created_at" , null: false t . datetime "updated_at" , null: false end # app/forms/new_questionnaire_form.rb class NewQuestionnaireForm include ActiveModel :: Model attr_accessor :title , :questions validates :title , presence: true def save Questionnaire . new ( title: title , questions: questions ). save end end

Why would I wanna do that? I saw in Ecto how convenient it is to separate validations from your models (no more conditional validations!).

Our controller is super simple and not much different to a scaffolded controller. Here are the new and create actions:

# app/controllers/questionnaires_controller.rb class QuestionnairesController < ApplicationController def new @questionnaire = NewQuestionnaireForm . new end def create @questionnaire = NewQuestionnaireForm . new ( questionnaire_params ) if @questionnaire . save redirect_to @questionnaire , notice: 'Questionnaire was successfully created.' else render :new end end end

In our view we’re using the new form_with helper:

# app/views/questionnaires/_form.html.erb = form_with ( model: @questionnaire , local: true ) do | form | = form . label :title = form . text_field :title

If you start that up and visit the questionnaires/new page, you’re greeted with an error message: undefined method 'new_questionnaire_forms_path' for … .

What a bummer! Rails takes the class name of our form object we pass into form_with and automatically infers a path to the corresponding controller - only that our controller has a different name.

Now, we have several possibilities to fix that: We could overwrite the URL we’re posting to in the form_with helper. We could also override the #model_name of our form object and make it appear like we’re dealing with a Questionnaire (while this is valuable sometimes, I’d consider it a dirty hack for our situation).

To find a better solution, let’s have a look back at our form object. It only deals with a single object, a Questionnaire . It even creates one in the #save method. We might be able to express a conversion from our form object to the model it’s shadowing. Turns out, there’s ActiveRecord::Conversion#to_model . Let’s rewrite our form object to make use of that method:

class NewQuestionnaireForm include ActiveModel :: Model def to_model Questionnaire . new ( title: title , questions: questions ) end def save to_model . save end end