Update: User MelissaLiberty from Reddit pointed out how they would improve the form object and some of it faults. The form object has been updated to reflect their excellent points.

Often, when we start a new Rails app we start with simple controllers, and we start by generating everything with scaffolding. There is nothing wrong with this and it is a great way to be able to build your basic models and perform CRUD actions on them but it breaks down a bit when the controllers get more complex. For instance, you might be building a Twitter clone where start with a User who publishes Tweets .

You start building this app by running:

rails g scaffold User username:string rails g scaffold Tweet body:string

So now you can create new tweets through the TweetsController and new users through the UsersController . The problem is what to do when the boss comes to you and says they want a new user to create their first tweet when they signup?

“This is high priority! We gotta juice the engagement metrics for the investors. The business is on the line! I don’t care if it is a crappy hack!” – Your boss

Your first thought might be to edit the UsersController so that the Tweet is created inline. It might look something like this:

class UsersController < ApplicationController #.... def create @user = User.new(user_params) @tweet = Tweet.new(tweet_params) if @user.save && @tweet.save redirect_to @user, notice: 'User and tweet was successfully created.' else render :new end end #.... end

It looks pretty much like a normal create action except for the additions of @tweet = Tweet.new(tweet_params) the && @tweet.save . Of course you would have additional changes to the form in the view and you would need to write the tweet_params method, but I see some bigger problems.

While this controller is still clearly understandable, it will grow with time and it doesn’t account for validation errors on the @tweet model. It also seems to already be incorrectly named. I would move this process to a new controller and leave the UsersController alone (or delete the create action if it will no longer be needed).

You can move the whole sign up process to a SignUpsController instead. Now you will be describing the process that is actually happening and if (when) the signup process changes in the future you will know where to put the changes. Lets start with the controller itself, it should look like this:

# app/controllers/sign_ups_controller.rb class SignUpsController < ApplicationController def new @sign_up = SignUp.new end def create @sign_up = SignUp.new(sign_up_params) if @sign_up.save redirect_to root_url, notice: 'Sign Up was a Success' else render :new end end private def sign_up_params params.permit(:username, :first_tweet) end end

This is the full controller and it is pretty simple, just like we like them. The new action renders a view with the @sign_up instance variable and the create action put the params on the @sign_up variable and saves it.

Note: You will also need to add resources :sign_ups and get "/signup", to: "sign_ups#new" to config/routes.rb to make the controller work.

So the magic must be in this SignUp object right? Let’s look at it.

# app/forms/sign_up.rb class SignUp include ActiveModel::Model attr_accessor :username, :first_tweet def save ActiveRecord::Base.transaction do user = User.create(username: username) add_errors(user.errors) if user.invalid? user.save! tweet = Tweet.create(body: first_tweet, user_id: user.id) add_errors(tweet.errors) if tweet.invalid? tweet.save! end rescue ActiveRecord::RecordInvalid => exception return false end private def add_errors(model_errors) model_errors.each do |attribute, message| errors.add(attribute, message) end end end

You can see that there isn’t too much that is special about this object but there are a few neat tricks that make it tick. The first is the line include ActiveModel::Model which is there to make the SignUp form object quack like an ActiveModel duck. From the Rails API about ActiveModel::Model:

That first, line along with attr_accessor :username, :first_tweet , is what lets it work with the form.

Now, we have the :username and :first_tweet on the form object so it is time to create the underlying objects it is composed of. In a previous version of this blog post we did this in the initialize but that created the objects on the database without ensuring that they were all valid and could have left us in a state where we created one valid object and didn’t create one invalid object. One out of two objects created isn’t what we are going for so now we are creating the objects in the def save method.

def save ActiveRecord::Base.transaction do @user = User.create(username: username) add_errors(@user.errors) if @user.invalid? @user.save! @tweet = Tweet.create(body: first_tweet, user_id: @user.id) add_errors(@tweet.errors) if @tweet.invalid? @tweet.save! end rescue ActiveRecord::RecordInvalid => exception return false end

We create both of the models that this signup is composed of in the save method. This gives us the instance variables @user and @tweet that we will can check if the are valid. The @user.invalid? checks the model to make sure that it passes it’s own internal validation and if it doesn’t then add_errors(@user.errors) will add those errors to the form object. As an example if the User has a validation for a unique username then we want that user model and and the sign_up form to have the validation error on them if the username is not unique.

Furthermore, we have wrapped the @user.save! and @tweet.save! in an ActiveRecord::Base.transaction to ensure that one won’t save without the other.

If anything goes wrong with our transaction or validations then the rescue will return false and let the controller know that the form object did not save.

We have gone through the controller and the form object so let’s look at the view.

# app/views/sign_ups/new.html.erb <p id="notice"><%= @sign_up.errors.messages if @sign_up.errors.any? %></p> <h1>Sign Up Here</h1> <%= form_for @sign_up do |f| %> <%= label_tag(:username, "username") %> <%= text_field_tag :username %> <%= label_tag(:first_tweet, "first_tweet") %> <%= text_field_tag :first_tweet %> <%= submit_tag("Create user & first tweet") %> <% end %>

The first thing to notice is the line where we show off any errors. If our validations added some errors then @sign_up.errors.any? will now get them to show up on the page after @sign_up.save fails in the controller and the controller sends the person attempting to sign up back through the render :new line to the new view. We didn’t want to forget about showing the user their errors right?

Other than that this appears to be a normal, Rails form_for that is passed the @sign_up object. That ability comes from the include ActiveModel::Model and attr_accessor :username, :first_tweet lines we set earlier in the SignUp form object. We put in some labels and text_field_tags and a submit button and our form object is complete.

Now that the controller looks better how about organizing the rest of the app?

Questions? Comments? Let me know below and I’ll do my best to answer them.

I also will be writing some more posts about the further uses for form objects in the future. If you want to learn how they can simplify and clean up your code then drop your email in the box below.