Slide 1

Slide 1 text

Keeping your Rails forms under control Grant Hutchins, Pivotal Labs Tuesday, July 17, 2012 Tuesday, July 17, 12

Slide 2

Slide 2 text

Tuesday, July 17, 12

Slide 3

Slide 3 text

1 = form_for product, html: {class: 'well form-horizontal'} do |f| 2 %fieldset 3 .control-group 4 = f.label :name, class: "control-label" 5 .controls 6 = f.text_field :name 7 .control-group 8 = f.label :photo_url, class: "control-label" 9 .controls 10 = f.text_field :photo_url 11 .control-group 12 = f.label :description, class: "control-label" 13 .controls 14 = f.text_area :description, rows: 8 15 .form-actions 16 %button.btn Save /app/views/product/_form.html.haml Tuesday, July 17, 12

Slide 4

Slide 4 text

Tuesday, July 17, 12

Slide 5

Slide 5 text

Tuesday, July 17, 12

Slide 6

Slide 6 text

config/locales/activerecord.en.yml 1 en: 2 activerecord: 3 attributes: 4 product: 5 photo_url: "Photo URL" Tuesday, July 17, 12

Slide 7

Slide 7 text

1 = form_for product, html: {class: 'well form-horizontal'} do |f| 2 %fieldset 3 .control-group 4 = f.label :name, class: "control-label" 5 .controls 6 = f.text_field :name 7 .control-group 8 = f.label :photo_url, Product.human_attribute_name(:photo_url), class: "control-label" 9 .controls 10 = f.text_field :photo_url 11 .control-group 12 = f.label :description, class: "control-label" 13 .controls 14 = f.text_area :description, rows: 8 15 .form-actions 16 %button.btn Save /app/views/product/_form.html.haml Tuesday, July 17, 12

Slide 8

Slide 8 text

Tuesday, July 17, 12

Slide 9

Slide 9 text

/app/views/product/_form.html.haml 1 = form_for product, html: {class: 'well form-horizontal'} do |f| 2 %fieldset 3 .control-group 4 = f.label :name, class: "control-label" 5 .controls 6 = f.text_field :name 7 .control-group 8 = f.label :photo_url, Product.human_attribute_name(:photo_url), class: "control-label" 9 .controls 10 = f.text_field :photo_url 11 .control-group 12 = f.label :description, class: "control-label" 13 .controls 14 = f.text_area :description, rows: 8 15 .form-actions 16 %button.btn Save Tuesday, July 17, 12

Slide 10

Slide 10 text

Simple Form Tuesday, July 17, 12

Slide 11

Slide 11 text

1 = simple_form_for product, html: {class: 'well form-horizontal'} do |f| 2 = f.input :name 3 = f.input :photo_url 4 = f.input :description, input_html: {rows: 8} 5 .form-actions 6 = f.button :submit /app/views/product/_form.html.haml Tuesday, July 17, 12

Slide 12

Slide 12 text

Tuesday, July 17, 12

Slide 13

Slide 13 text

/app/controllers/searches_controller.rb 1 class SearchesController < ApplicationController 2 def new 3 end 4 5 def show 6 @products = Product.where("name LIKE ?", "%#{params[:query]}%") 7 end 8 end Tuesday, July 17, 12

Slide 14

Slide 14 text

/app/views/searches/_form.html.haml 1 %form.form-search.well{action: search_path, method: :get} 2 = text_field_tag :query, params[:query], class: "search-query" 3 = button_tag "Search", class: "btn" Tuesday, July 17, 12

Slide 15

Slide 15 text

Tuesday, July 17, 12

Slide 16

Slide 16 text

Tuesday, July 17, 12

Slide 17

Slide 17 text

Tuesday, July 17, 12

Slide 18

Slide 18 text

/app/controllers/searches_controller.rb 1 class SearchesController < ApplicationController 2 def new 3 end 4 5 def show 6 if params[:query].blank? 7 @missing_query = true 8 @products = [] 9 else 10 @products = Product.where("name LIKE ?", "%#{params[:query]}%") 11 end 12 13 if params[:query] =~ /shoes/i 14 flash.now.alert = "Sorry, we don't sell shoes anymore" 15 end 16 end 17 end Tuesday, July 17, 12

Slide 19

Slide 19 text

/app/views/searches/_form.html.haml 1 %form.form-search.well{action: search_path, method: :get} 2 %fieldset.control-group{class: ("error" if @missing_query)} 3 .controls 4 = text_field_tag :query, params[:query], class: "search-query" 5 = button_tag "Search", class: "btn" 6 - if @missing_query 7 %span.help-inline Please fill out a search query Tuesday, July 17, 12

Slide 20

Slide 20 text

https://github.com/mconf/mconf-web/blob/627390660a980574c4bd0ba90bec9b00e296f3bc/app/controllers/search_controller.rb Tuesday, July 17, 12

Slide 21

Slide 21 text

/app/controllers/searches_controller.rb 1 class SearchesController < ApplicationController 2 def new 3 end 4 5 def show 6 @search = Search.new(params) 7 end 8 end Tuesday, July 17, 12

Slide 22

Slide 22 text

/app/views/searches/show.html.haml 1 - unless @search.missing_query? 2 %h1 Search results for #{@search.query} 3 4 %p= pluralize @search.count, "result" 5 6 %ul.products 7 - @search.results.each do |result| 8 %li= render result Tuesday, July 17, 12

Slide 23

Slide 23 text

/app/models/search.rb 1 class Search 2 attr_reader :query 3 4 def initialize(params) 5 @query = params[:query] 6 end 7 8 def results 9 return [] if missing_query? 10 Product.where("name LIKE ?", "%#{@query}%") 11 end 12 13 def count 14 results.count 15 end 16 17 def missing_query? 18 @query.blank? 19 end 20 end Tuesday, July 17, 12

Slide 24

Slide 24 text

/app/views/searches/_form.html.haml 1 %form.form-search.well{action: search_path, method: :get} 2 %fieldset.control-group{class: ("error" if @missing_query)} 3 .controls 4 = text_field_tag :query, params[:query], class: "search-query" 5 = button_tag "Search", class: "btn" 6 - if @missing_query 7 %span.help-inline Please fill out a search query Tuesday, July 17, 12

Slide 25

Slide 25 text

/app/views/searches/_form.html.haml 1 = simple_form_for @search, url: search_path, method: :get, html: {class: "form-inline well"} do |f| 2 = f.input :query, class: "search-query" 3 .form-actions 4 = f.button :submit, class: "help-inline" Tuesday, July 17, 12

Slide 26

Slide 26 text

Before filter goes here Tuesday, July 17, 12

Slide 27

Slide 27 text

Tuesday, July 17, 12

Slide 28

Slide 28 text

/app/models/search.rb 1 class Search 2 attr_reader :query 3 4 def initialize(params = {}) 5 @query = params[:query] 6 end 7 8 def self.model_name 9 "Search" 10 end 11 12 def results 13 return [] if missing_query? 14 Product.where("name LIKE ?", "%#{@query}%") 15 end 16 17 def count 18 results.count 19 end 20 21 def missing_query? 22 @query.blank? Tuesday, July 17, 12

Slide 29

Slide 29 text

Tuesday, July 17, 12

Slide 30

Slide 30 text

Docs for ActiveModel::Naming Tuesday, July 17, 12

Slide 31

Slide 31 text

Active Model Tuesday, July 17, 12

Slide 32

Slide 32 text

Tuesday, July 17, 12

Slide 33

Slide 33 text

/app/models/search.rb 1 class Search 2 extend ActiveModel::Naming 3 4 attr_reader :query 5 6 def initialize(params = {}) 7 @query = params[:query] 8 end 9 10 def results 11 return [] if missing_query? 12 Product.where("name LIKE ?", "%#{@query}%") 13 end 14 15 def count 16 results.count 17 end 18 19 def missing_query? 20 @query.blank? 21 end 22 end Tuesday, July 17, 12

Slide 34

Slide 34 text

Tuesday, July 17, 12

Slide 35

Slide 35 text

Tuesday, July 17, 12

Slide 36

Slide 36 text

Tuesday, July 17, 12

Slide 37

Slide 37 text

/app/models/search.rb 1 class Search 2 extend ActiveModel::Naming 3 include ActiveModel::Conversion 4 5 attr_reader :query 6 7 def initialize(params = {}) 8 @query = params[:query] 9 end 10 11 def persisted? 12 false 13 end 14 15 def results 16 return [] if missing_query? 17 Product.where("name LIKE ?", "%#{@query}%") 18 end 19 20 def count 21 results.count 22 end 23 Tuesday, July 17, 12

Slide 38

Slide 38 text

Tuesday, July 17, 12

Slide 39

Slide 39 text

/app/views/searches/_form.html.haml 1 = simple_form_for @search, url: search_path, method: :get, html: {class: "form-inline well"} do |f| 2 = f.input :query, class: "search-query" 3 .form-actions 4 = f.button :submit Tuesday, July 17, 12

Slide 40

Slide 40 text

Tuesday, July 17, 12

Slide 41

Slide 41 text

/app/views/searches/_form.html.haml 1 = simple_form_for @search, url: search_path, method: :get, html: {class: "form-inline well"} do |f| 2 = f.input :query, label: false do 3 = f.input_field :query 4 = f.button :submit Tuesday, July 17, 12

Slide 42

Slide 42 text

/config/locales/helpers.en.yml.html 1 en: 2 helpers: 3 submit: 4 search: 5 create: "Search" Tuesday, July 17, 12

Slide 43

Slide 43 text

Tuesday, July 17, 12

Slide 44

Slide 44 text

/app/models/search.rb 1 class Search 2 extend ActiveModel::Naming 3 include ActiveModel::Conversion 4 include ActiveModel::Validations 5 6 validates :query, presence: {message: "Please fill out a search query"} 7 8 attr_reader :query 9 10 def initialize(params = {}) 11 @query = params[:query] 12 end 13 14 def persisted? 15 false 16 end 17 18 def results 19 return [] if missing_query? 20 Product.where("name LIKE ?", "%#{@query}%") 21 end 22 23 def count 24 results.count 25 end 26 27 def missing_query? 28 @query.blank? 29 end 30 end Tuesday, July 17, 12

Slide 45

Slide 45 text

/app/controllers/searches_controller.rb 1 class SearchesController < ApplicationController 2 def new 3 end 4 5 def show 6 @search = Search.new(params).tap(&:valid?) 7 end 8 end Tuesday, July 17, 12

Slide 46

Slide 46 text

Tuesday, July 17, 12

Slide 47

Slide 47 text

/app/controllers/searches_controller.rb 1 class SearchesController < ApplicationController 2 def new 3 end 4 5 def show 6 @search = Search.new(params[:search]).tap(&:valid?) 7 end 8 end Tuesday, July 17, 12

Slide 48

Slide 48 text

/app/models/search.rb 1 class Search 2 extend ActiveModel::Naming 3 include ActiveModel::Conversion 4 include ActiveModel::Validations 5 6 validates :query, presence: {message: "Please fill out a search query"} 7 8 attr_reader :query 9 10 def initialize(params = {}) 11 @query = params[:query] 12 end 13 14 def persisted? 15 false 16 end 17 18 def results 19 return [] if missing_query? 20 Product.where("name LIKE ?", "%#{@query}%") 21 end 22 23 def count 24 results.count 25 end 26 27 def missing_query? 28 @query.blank? 29 end 30 end Tuesday, July 17, 12

Slide 49

Slide 49 text

Informal by Josh Susser 1 class Search 2 include Informal::Model 3 4 validates :query, presence: {message: "Please fill out a search query"} 5 6 attr_accessor :query 7 8 def results 9 return [] if missing_query? 10 Product.where("name LIKE ?", "%#{@query}%") 11 end 12 13 def count 14 results.count 15 end 16 17 def missing_query? 18 @query.blank? 19 end 20 end Tuesday, July 17, 12

Slide 50

Slide 50 text

Rails 4 1 class Search 2 include ActiveModel::Model 3 4 validates :query, presence: {message: "Please fill out a search query"} 5 6 attr_accessor :query 7 8 def results 9 return [] if missing_query? 10 Product.where("name LIKE ?", "%#{@query}%") 11 end 12 13 def count 14 results.count 15 end 16 17 def missing_query? 18 @query.blank? 19 end 20 end Tuesday, July 17, 12

Slide 51

Slide 51 text

/app/controllers/products_controller.rb 1 class ProductsController < ApplicationController 2 def index 3 @products = Product.scoped 4 end 5 6 def new 7 @product = Product.new 8 end 9 10 def create 11 @product = Product.new(params[:product]) 12 13 if @product.save 14 flash.notice = "Thanks, your new product has been saved!" 15 redirect_to @product 16 else 17 flash.now.alert = "Sorry, unable to save your product, see errors below." 18 render :new 19 end 20 end 21 22 def show 23 @product = Product.find(params[:id]) 24 end 25 26 def edit 27 @product = Product.find(params[:id]) 28 end 29 30 def update 31 @product = Product.find(params[:id]) Tuesday, July 17, 12

Slide 52

Slide 52 text

8 end 9 10 def create 11 @product = Product.new(params[:product]) 12 13 if @product.save 14 flash.notice = "Thanks, your new product has been saved!" 15 redirect_to @product 16 else 17 flash.now.alert = "Sorry, unable to save your product, see errors below." 18 render :new 19 end 20 end 21 22 def show 23 @product = Product.find(params[:id]) 24 end 25 26 def edit 27 @product = Product.find(params[:id]) 28 end 29 30 def update 31 @product = Product.find(params[:id]) 32 33 if @product.update_attributes(params[:product]) 34 flash.notice = "Thanks, your new product has been updated!" 35 redirect_to @product 36 else 37 flash.now.alert = "Sorry, unable to save your product, see errors below." 38 render :new 39 end 40 end 41 end Tuesday, July 17, 12

Slide 53

Slide 53 text

1 class ProductsController < ApplicationController 2 respond_to :html 3 4 def index 5 @products = Product.scoped 6 end 7 8 def new 9 @product = Product.new 10 end 11 12 def create 13 @product = Product.create(params[:product]) 14 respond_with @product 15 end 16 17 def show 18 @product = Product.find(params[:id]) 19 end 20 21 def edit 22 @product = Product.find(params[:id]) 23 end 24 25 def update 26 @product = Product.update(params[:id], params[:product]) 27 respond_with @product 28 end 29 end Tuesday, July 17, 12

Slide 54

Slide 54 text

Responders Tuesday, July 17, 12

Slide 55

Slide 55 text

1 class ApplicationResponder < ActionController::Responder 2 include Responders::FlashResponder 3 include Responders::HttpCacheResponder 4 5 # Uncomment this responder if you want your resources to redirect to the collection 6 # path (index action) instead of the resource path for POST/PUT/DELETE requests. 7 # include Responders::CollectionResponder 8 end /lib/application_responder.rb Tuesday, July 17, 12

Slide 56

Slide 56 text

/app/controllers/application_controller.rb 1 require "application_responder" 2 3 class ApplicationController < ActionController::Base 4 self.responder = ApplicationResponder 5 respond_to :html 6 7 protect_from_forgery 8 9 before_filter { @search ||= Search.new } 10 end Tuesday, July 17, 12

Slide 57

Slide 57 text

/config/locales/responders.en.yml 1 en: 2 flash: 3 actions: 4 create: 5 notice: '%{resource_name} was successfully created.' 6 update: 7 notice: '%{resource_name} was successfully updated.' 8 destroy: 9 notice: '%{resource_name} was successfully destroyed.' 10 alert: '%{resource_name} could not be destroyed.' Tuesday, July 17, 12

Slide 58

Slide 58 text

Tuesday, July 17, 12

Slide 59

Slide 59 text

/app/controllers/products_controller.rb 1 class ProductsController < ApplicationController 2 respond_to :html 3 4 def index 5 @products = Product.scoped 6 end 7 8 def new 9 @product = Product.new 10 end 11 12 def create 13 @product = Product.create(params[:product]) 14 respond_with @product, alert: "Please try again!", notice: "Good work!" 15 end 16 17 def show 18 @product = Product.find(params[:id]) 19 end 20 21 def edit 22 @product = Product.find(params[:id]) 23 end 24 25 def update 26 @product = Product.update(params[:id], params[:product]) 27 respond_with @product 28 end 29 end Tuesday, July 17, 12

Slide 60

Slide 60 text

Tuesday, July 17, 12

Slide 61

Slide 61 text

/app/controllers/products_controller.rb 1 class ProductsController < ApplicationController 2 respond_to :html 3 4 def index 5 @products = Product.scoped 6 end 7 8 def new 9 @product = Product.new 10 end 11 12 def create 13 @product = Product.create(params[:product]) 14 respond_with @product, location: root_path 15 end 16 17 def show 18 @product = Product.find(params[:id]) 19 end 20 21 def edit 22 @product = Product.find(params[:id]) 23 end 24 25 def update 26 @product = Product.update(params[:id], params[:product]) 27 respond_with @product, location: root_path 28 end 29 end Tuesday, July 17, 12

Slide 62

Slide 62 text

/app/controllers/products_controller.rb 1 class ProductsController < ApplicationController 2 respond_to :html, :json, :xml 3 4 def index 5 @products = Product.scoped 6 end 7 8 def new 9 @product = Product.new 10 end 11 12 def create 13 @product = Product.create(params[:product]) 14 respond_with @product, location: root_path, only: [:name, :description] 15 end 16 17 def show 18 @product = Product.find(params[:id]) 19 end 20 21 def edit 22 @product = Product.find(params[:id]) 23 end 24 25 def update 26 @product = Product.update(params[:id], params[:product]) 27 respond_with @product, location: root_path, only: [:name, :description] 28 end 29 end Tuesday, July 17, 12

Slide 63

Slide 63 text

action/lib/action_controller/metal/responder.rb (ActionPack) 186 # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth. 187 def navigation_behavior(error) 188 if get? 189 raise error 190 elsif has_errors? && default_action 191 render :action => default_action 192 else 193 redirect_to navigation_location 194 end 195 end 196 197 # This is the common behavior for formats associated with APIs, such as :xml and :json. 198 def api_behavior(error) 199 raise error unless resourceful? 200 201 if get? 202 display resource 203 elsif post? 204 display resource, :status => :created, :location => api_location 205 else 206 head :no_content 207 end 208 end Tuesday, July 17, 12

Slide 64

Slide 64 text

Thanks! Tuesday, July 17, 12

Slide 65

Slide 65 text

Tuesday, July 17, 12