Upload
brianauton
View
101
Download
0
Tags:
Embed Size (px)
DESCRIPTION
A step-by-step process for simplifying controller code in Ruby on Rails applications, with code examples. First presented at Philly.rb September 2014.
Citation preview
SimplifyYour Rails Controllers
VengeanceWith a
Brian Auton
[email protected]/brianautongithub.com/brianauton
I know what's wrongwith your Rails app...
The controllers(they're too complex)
1. Less responsibilities
2. Shorter methods
3. Less duplication
1. Less responsibilities
2. Shorter methods
3. Less duplication
Controllers are just code.
1. Less responsibilities
User's Intent <=> Business Logic
1. Less responsibilities
User's Intent <=> Business Logic
How? REST
routes.rbresources :clients do get :download_pdf, on: :memberend
clients_controller.rbdef download_pdf client = Client.find params[:id] send_data client.to_pdf, type: :pdfend
def show @client = Client.find params[:id]end
routes.rbresources :clients
clients_controller.rbdef show @client = Client.find params[:id] respond_to do |format| format.html {} format.pdf do send_data @client.to_pdf, type: :pdf end endend
routes.rbresources :orders do post :submit, on: :memberend
orders_controller.rbdef submit order = Order.find params[:id] PaymentGateway.process order flash[:notice] = “Payment successful” order.update_attribute :status, :completerescue PaymentGateway::Error => e order.update_attribute :status, :failed redirect_to order, alert: “Error: #{e}”end
routes.rbresources :ordersresources :payment_attempts, only: :create
payment_attempts_controller.rbdef create attempt = PaymentAttempt.create payment_attempt_params PaymentGateway.process attempt.order flash[:notice] = “Payment successful” attempt.update_attribute :status, :completerescue PaymentGateway::Error => e attempt.update_attribute :error, e.message redirect_to attempt.order, alert: “Error: #{e.message}”end
routes.rbresources :regions do put :sort, on: :collectionend
regions_controller.rbdef sort params[:region].each do |id, position| Region.find(id).update_attribute :position, position endend
routes.rbresources :regionsresources :region_collections, only: :update
region_collections_controller.rbdef update region_collection_params.each do |attributes| Region.find(attributes[:id)].update_attributes attributes endend
private
def region_collection_params params.require(:region_collection).permit [:id, :position]end
2. Shorter methods
2. Shorter methods
How? Delegate to models
payment_attempts_controller.rbdef create attempt = PaymentAttempt.create payment_attempt_params PaymentGateway.process attempt.order flash[:notice] = “Payment successful” attempt.update_attribute :status, :completerescue PaymentGateway::Error => e attempt.update_attribute :error, e.message redirect_to attempt.order, alert: “Error: #{e.message}”end
payment_attempt.rbclass PaymentAttempt < ActiveRecord::Base before_save do PaymentGateway.process order rescue PaymentGateway::Error => e update_attribute :error, e.message end
def successful? error.present? endend
payment_attempts_controller.rbdef create @attempt = PaymentAttempt.create payment_attempt_params if @attempt.successful? flash[:notice] = “Payment successful.” else flash[:alert] = “Error: #{@attempt.error}” redirect_to @attempt.order endend
users_controller.rbdef update @user = User.find params[:id] @user.update_attributes user_params @user.address.update_attributes address_params ...end
def user_params params.require(:user).permit :name, :emailend
def address_params params.require(:address).permit :city, :state, :zipend
users_controller.rbdef update @user = User.find params[:id] @user.update_attributes user_params ...end
def user_params params.require(:user).permit :name, :email, { address_attributes: [:city, :state, :zip] }end
user.rbhas_one :addressaccepts_nested_attributes_for :address
3. Reduce Duplication
3. Reduce Duplication
How? It's just code.
widgets_controller.rbdef new @widget = Widget.newend
def show @widget = Widget.find params[:id]end
def update @widget = Widget.find params[:id] ...end
widgets_controller.rbbefore_action :build_widget, only: [:new]before_action :find_widget, only: [:show, :update]
private
def build_widget @widget = Widget.newend
def find_widget @widget = Widget.find params[:id]end
application_controller.rbprotected
def build_member set_member_instance_variable collection.newend
def find_member set_member_instance_variable collection.find(params[:id])end
def set_member_instance_variable(value) variable_name = “@#{controller_name.singularize}” instance_variable_set variable_name, (@member = value)end
def collection controller_name.classify.constantizeend
assets_controller.rbdef update if @asset.update_attributes asset_params redirect_to @asset, notice: “Asset updated” else flash[:alert] = @asset.errors.full_messages.join(', ') render :edit endend
surveys_controller.rbdef update if @survey.update_attributes survey_params redirect_to @survey, notice: “Survey updated” else flash[:alert] = @survey.errors.full_messages.join(', ') render :edit endend
assets_controller.rbdef update @asset.update_attributes asset_params respond_to_update @assetend
surveys_controller.rbdef update @survey.update_attributes survey_params respond_to_update @surveyend
application_controller.rbdef respond_to_update(model) if model.valid? type = model.class.name.humanize redirect_to model, notice: “#{type} updated” else flash[:alert] = model.errors.full_messages.join(', ') render :edit endend
shared_rest_actions.rbmodule SharedRestActions def self.included(base) base.before_action :build_member, only: [:new, :create] base.before_action :find_collection, only: :index base.before_action :find_member, except: [:index, :new, :create] end
def index end
def update member_params = send “#{controller_name.singularize}_params” @member.update_attribute member_params respond_to_update @member end
...end
assets_controller.rbclass AssetsController < ApplicationController include SharedRestActions before_action :paginate, only: :index before_action :sort_by_date, only: :indexend
surveys_controller.rbclass SurveysController < ApplicationController include SharedRestActions before_action :build_member, only: :indexend
1. Less responsibilities (REST)
2. Shorter methods (Models)
3. Less duplication
Recap:
Go try it out!
[email protected]/brianautongithub.com/brianauton