Upgrade to Pro — share decks privately, control downloads, hide ads and more …

In Relentless Pursuit of Rest

In Relentless Pursuit of Rest

"That's not very RESTful." As a Rails developer you've probably heard or even spoken that proclamation before, but what does it really mean? What's so great about being RESTful anyway?

RESTful architecture can narrow the responsibilities of your Rails controllers and make follow-on refactorings more natural. In this talk, you'll learn to refactor code to follow RESTful principles and to identify the positive impact those changes have throughout your application stack.

Derek Prior

April 26, 2017
Tweet

More Decks by Derek Prior

Other Decks in Technology

Transcript

  1. class PasswordsController < ApplicationController def edit # what used to

    be `users#edit_password` end def update # what used to be `users#update_password` end end
  2. def update @photo = current_user.photos.find(params[:id]) if @photo.update(update_photo_params) redirect_to @photo else

    render :edit end end private def update_photo_params params.require(:photo).permit(:caption) end
  3. As a user, I want to mark a photo as

    featured so it is displayed prominently on my profile.
  4. As a user, I want to mark a photo as

    featured so it is displayed prominently on my profile. • Oh, and I should only be allowed to have one featured photo
  5. As a user, I want to mark a photo as

    featured so it is displayed prominently on my profile. • Oh, and I should only be allowed to have one featured photo • Oh, and if I un-feature a photo, my first photo should automatically be featured
  6. def update @photo = current_user.photos.find(params[:id]) @photo.assign_attributes(update_photo_params) if @photo.valid? && update_photo(photo)

    redirect_to @photo else render :edit end end private def update_photo(photo) ApplicationRecord.transaction do if photo.featured_changed? && photo.featured? current_user.photos.featured.update!(featured: false) elsif photo.featured_changed? && !photo.featured? current_user.photos.first.update!(featured: true) end photo.save! end end
  7. def update_photo(photo) ApplicationRecord.transaction do if photo.featured_changed? && photo.featured? current_user.photos.featured.update!(featured: false)

    elsif photo.featured_changed? && !photo.featured? current_user.photos.first.update!(featured: true) end photo.save! end end
  8. def update @photo = current_user.photos.find(params[:id]) @photo.assign_attributes(update_photo_params) if @photo.valid? && update_photo(photo)

    redirect_to @photo else render :edit end end private def update_photo(photo) ApplicationRecord.transaction do if photo.featured_changed? && photo.featured? current_user.photos.featured.update!(featured: false) elsif photo.featured_changed? && !photo.featured? current_user.photos.first.update!(featured: true) end photo.save! end end
  9. class FeaturedFlagsController < ApplicationController def create @photo = current_user.photos.find(params[:photo_id]) if

    AddFeaturedFlag.to(@photo) redirect_to photos_path else redirect_to photos_path, error: t('.error') end end def destroy @photo = current_user.photos.find(params[:photo_id]) if RemoveFeaturedFlag.from(@photo) redirect_to photos_path else redirect_to photos_path, error: t('.error') end end end
  10. class AddFeaturedFlag def self.to(photo) new(photo).call end def initialize(photo) @photo =

    photo end def call ApplicationRecord.transaction do @photo.user.photos.featured.update!(featured: false) @photo.update!(featured: true) end end end
  11. class OrdersController < ApplicationController before_action :find_order, except: [:index] before_action #...

    before_action #... before_action :ensure_processable, only: [:process] before_action #... # a bunch of actions def process # procedure for processing an order # potentially very complicated order.update!(process_params) end # more actions private # dozens of private methods useful to various actions end
  12. class ShipmentsController < ApplicationController before_action :ensure_processable def create # procedure

    for processing an order # potentially very complicated order.update!(shipment_params) end private # a subset of those dozens of private methods end
  13. state_machine initial: :pending do before_transition captured: :refunded, do: :ensure_refundable after_transition

    captured: :refunded, do: :process_refund after_transition authorized: :captured, do: :capture_payment after_transition pending: :rejected, do: :void_order after_transition pending: :authorized, do: :authorize_payment #... end
  14. state_machine initial: :pending do # callbacks... event :authorize do transition

    pending: :authorized end event :capture do transition authorized: :captured end event :refund do transition captured: :refunded end event :reject do transition all => :rejected end end
  15. state_machine initial: :pending do # callbacks... # events... state :captured

    do validates :reward_points, presence: true end state :rejected do validate :validates_tax_exemption end end
  16. state_machine initial: :pending do before_transition captured: :refunded, do: :ensure_refundable after_transition

    captured: :refunded, do: :process_refund after_transition authorized: :captured, do: :capture_payment after_transition pending: :rejected, do: :void_order after_transition pending: :authorized, do: :authorize_payment event :authorize do transition pending: :authorized end event :capture do transition authorized: :captured end event :refund do transition captured: :refunded end event :reject do transition all => :rejected end state :captured do validates :reward_points, presence: true end state :pending do validate :validates_tax_emption end end
  17. resources :orders, only: [:new, :create] do resource :authorization, only: [:create]

    resource :capture, only: [:create] resource :refund, only: [:create] resource :rejection, only: [:create] end
  18. class Authorization def initialize(order) @order = order end def process

    raise unless authorizable? ApplicationRecord.transaction do @order.update!(state: "authorized") PaymentGateway.authorize(@order.payment_method, @order.total) end end private def authorizable? # Is the order in a state where it can be authorized? end end