giant robots smashing into other giant robots

Written by thoughtbot

hrward

ActiveModel Form Objects

Submitting form data is a common feature of web applications — allowing users to submit their information and giving them feedback whether the information is valid or not.

ActiveRecord comes with a powerful set of validators for attributes on a persisted data model. When data is not persisted, or used for other non-active record purposes, Active Model Helper Modules reduce the complexity of validations on your plain old Ruby objects.

Routing

Create the routes needed for displaying the form object and posting the data

  • Restrict resources to the routes you need using only:
# config/routes.rb
resources :registration, only: [:new, :create]

Controller and Actions

Create a controller with new and create actions.

  • respond_with will re-render the new action if there are any validation errors on the model
  • If there are no errors on the model the visitor will be redirected to show the current resource. In this case the user will be redirected to some_other_success_path
# app/controllers/registration_controller.rb
class RegistrationsController < ApplicationController
  respond_to :html

  def new
    @registration = Registration.new
  end

   def create
    @registration = Registration.new(registration_params)
    @registration.register

    respond_with @registration, location: some_success_path
  end

  private

  def registration_params
    # ...
  end
end

View with Registration Form

The view renders a web form with fields to submit.

  • Use the ActiveModel object @registration in the form
  • Form generates the endpoint registration_path and method of delivery post
  • Validation errors will display inline within the form just like ActiveRecord
# app/views/registration/new.html.erb
<%= form_for @registration do |f| %>
  <%= f.label :first_name, 'First Name' %>:
  <%= f.text_field :first_name %>
  …
  <%= f.submit %>
<% end %>

Object with ActiveModel Conversion, Naming, and Validations

Use any of the ActiveRecord Validations in the model.

  • Command pattern used when calling register method.
  • ActiveRecord validation syntax on attributes.
  • ActiveModel::Model mixin includes modules, and includes an initialization method.
# app/models/registration.rb
class Registration
  include ActiveModel::Model

  attr_accessor(
    :company_name,
    :email,
    :first_name,
    :last_name,
    :terms_of_service
  )

  validates :company_name, presence: true
  validates :email, presence: true, email: true
  validates :first_name, presence: true
  validates :last_name, presence: true
  validates :terms_of_service, acceptance: true

  def register
    if valid?
      # Do something interesting here
      # - create user
      # - send notifications
      # - log events, etc.
    end
  end

  private
  
  def create_user
    # ...
  end
end

Takeaways

  • Keep business logic out of the Controller and Views
  • Add validation support to plain Ruby object using ActiveModel includes
  • Display data validation errors in the form
  • Use ActiveModel naming conventions for generating form endpoints

Written by Harlow Ward

dancroak

When, a Rails plugin

Ever write this?
before_filter :authorize

protected

def authorize
  unless logged_in?
    session[:return_to] = request.request_uri
    redirect_to login_url and return false
  end
end
You want your before_filter to authorize the current_user unless they are logged in. So why not say it like you mean it?
before_filter :authorize, :unless => logged_in?

protected

def authorize
  session[:return_to] = request.request_uri
  redirect_to login_url and return false
end

Paris Hilton is a fan of moving conditional logic into filters, callbacks, and validations.This is more expressive. The conditional logic is no longer hidden in the method.

The authorize method now has a single responsibility: to authorize. The before_filter, responsible for performing some action before your controller action is called, is now smarter about when it is supposed to run.


Introducing When

The When Rails plugin adds :if and :unless modifiers to before_filters, most ActiveRecord callbacks, and validations.

Get it:

piston import https://svn.thoughtbot.com/plugins/when/trunk vendor/plugins/when
Then start improving your controllers:
before_filter :deny_access, :unless => :admin?

protected

def deny_access
  flash[:failure] = "You do not have access to that page."
  redirect_to home_url
end

def admin?
  logged_in? and current_user.admin?
end
Improve your models, too:
before_create :encrypt_password,
  :unless => lambda {|user| user.password_confirmation.blank?}

after_save :send_alerts, :if => :alerts?

validate_on_create :add_unsupported_type_error, 
  :unless => lambda {|document| SUPPORTED_FILE_TYPES.include? document.file_type}

Coding without ifs?

Jared makes a strong argument that this is actually removing conditional logic from your program. I tend to agree.

It reminds me of defining constants in your environments so you don’t write conditional logic that checks the RAILS_ENV. That kind of control flow belongs in the framework.

Whether this is removing conditional logic is moot, though. It just feels right, and isn’t that why we write Ruby?

UpdateAfter releasing this, we found out this is already in Rails trunk. Awesome! It’s a great feature and should be in the framework. So, if you’re not running on Edge, use the when plugin to get this feature now. Then, remove it when you upgrade to the next version of Rails, when you’ll get :ifs and :unlesses “for free.” Happy coding!