# Reform ## How I use Form objects in my Rails applications -- ## Who am I? [![Ruby on Rails freelance](/assets/images/avatar-dccc6af7.png)](http://cecilitse.org/) Cecile Rails dev / coach
Teacher at [Le Wagon](https://www.lewagon.com/)
Remote worker --- ## The Rails Way (basic usage, we put everything in the controller :x) ``` class Artist < ActiveRecord::Base validates :name, presence: true end ``` ``` class ArtistsController < ApplicationController def create artist_params = params.require(:artist).permit(:name, :genre) # sick @artist = Artist.new(artist_params) if @artist.save flash[:notice] = 'Awesome band!' redirect_to artist_path(artist) else flash.now[:alert] = 'Whoops! Unable to add your favorite artist.' render :new end end end ``` -- ## User dependent validation ``` # app/models/artist.rb class Artist < ActiveRecord::Base attr_accessor :current_user # please don't do that validates :name, presence: true validates :genre, presence: true, if: :admin_user? private def admin_user? current_user.admin? end end ``` ``` # app/controller/admin/artists_controller.rb def create artist_params = params.require(:artist).permit(:name, :genre) # still sick @artist = Artist.new(artist_params) @artist.current_user = current_user if @artist.save # and so on ``` --- ## Why form objects? * Keep skinny models * Extract validations from models * Possibility to have a form per model, context, etc. -- ## Icing on the cake * No more strong parameters * No more `accepts_nested_attributes_for` --- I'm using Reform gem. https://github.com/apotonick/reform/ [![Nick Sutterer](/assets/images/talks/reform/apotonick-b063517b.jpeg)](http://nicksda.apotomo.de/) -- > “Reform gives you a form object with validations and nested setup of models. It is completely framework-agnostic and doesn't care about your database.” > ‒ Nick Sutterer, Reform author But also comes with Rails support -- ## Defining Form ``` # app/forms/artist_form.rb class ArtistForm < Reform::Form property :name property :genre validates :name, presence: true end ``` -- ## Using Form ``` class ArtistsController < ApplicationController def new @artist_form = ArtistForm.new(Artist.new) end end ``` -- ## Saving model *(basic usage, we put everything in the controller :x)* ``` class ArtistsController < ApplicationController def create artist = Artist.new @artist_form = ArtistForm.new(artist) if @artist_form.validate(params[:artist]) @artist_form.save # sync artist with params & saves it to db flash[:notice] = 'Awesome band!' redirect_to artist_path(artist) else flash.now[:alert] = 'Whoops! Unable to add your favorite artist.' render :new end end end ``` -- ## Other usages ``` artist_form.sync # writes form data back to the model artist_form.save # sync form + call a save on model artist_form.errors # returns validation messages ``` --- ## with Simple Form *(and Slim of course)* ``` = simple_form_for @artist_form, as: 'artist' do |form| = form.input :name = form.input :genre ``` -- Note the option ``` as: 'artist' ``` This will produce the following params ``` { artist: { name: 'Iron Maiden', genre: 'Heavy Metal' } } ``` --- ## What about TDD?! -- ## Testing Form *(with Rspec and Shoulda Matchers)* ``` # spec/forms/artist_form_spec.rb require 'rails_helper' RSpec.describe ArtistForm do subject(:form) { ArtistForm.new(artist) } let(:artist) { Artist.new } it { is_expected.to have_property(:name) } it { is_expected.to have_property(:genre) } it { is_expected.to validate_presence_of(:name) } end ``` --## Form matcher ``` # spec/support/matchers/form_matcher.rb require 'rspec/expectations' RSpec::Matchers.define :have_attr_accessor do |expected| match do |actual| actual.respond_to?(expected) && actual.respond_to?("#{expected}=") end end RSpec::Matchers.alias_matcher :have_property, :have_attr_accessor ``` --- ## Use Cases -- ## Passing Form to a Service *(simple usage)* ``` def update artist = Artist.find(params[:id]) @artist_form = ArtistForm.new(artist) service = Artists::UpdateService.new(@artist_form, params[:artist]) if service.call flash[:notice] = 'Got it!' redirect_to artist_path(artist) else flash.now[:alert] = 'Nope. Not saved.' render :edit end end ``` -- ## A Form per context ``` class ArtistForm < Reform::Form property :name property :genre validates :name, presence: true end module Admin class ArtistForm < Reform::Form property :name property :genre property :available validates :name, presence: true validates :genre, presence: true end end ``` No more validatation with `if: :admin_user?` guards -- ## Embedded models ``` class ClientForm < Reform::Form property :first_name property :last_name property :billing_address do property :street porperty :secondary property :city property :zipcode validates :street, presence: true validates :city, presence: true validates :zipcode, presence: true end validates :first_name, presence: true validates :last_name, presence: true end ``` --- ## Happy Hacking! [@cecilitse](http://twitter.com/cecilitse)