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

A Case for Use Cases

A Case for Use Cases

If you cut your teeth on Ruby on Rails, at some point you've probably shuffled domain logic from views to controllers to models to POROs in pursuit of Separation of Concerns.

At Envato, we recently introduced the Use Case pattern as a contextual, well-defined interface between your Rails controllers and your domain entities. This presentation walks through a real example and explains how these nifty little classes can reveal your user stories while also encapsulating complex business logic.

Presented at RubyConf AU 2015.

Accompanying blog post can be found here: http://webuild.envato.com/blog/a-case-for-use-cases/

Shevaun Coker

February 06, 2015
Tweet

Other Decks in Programming

Transcript

  1. $ rails g controller Purchases class PurchasesController < ApplicationController def

    create Purchase.create!(buyer: buyer, photo: photo) redirect_to purchase_completed_path end end
  2. $ rails g controller Purchases class PurchasesController < ApplicationController def

    create Purchase.create!(buyer: buyer, photo: photo) redirect_to purchase_completed_path end end
  3. Sole Requirement: A buyer can purchase a photo s A

    buyer receives a purchase invoice Record the sales count of each photo A buyer can review a purchased photo Updated
  4. class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMail.invoice.deliver(buyer, purchase)

    photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end
  5. class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase)

    photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end
  6. class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase)

    photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end
  7. class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase)

    photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end
  8. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  9. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  10. class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase)

    photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end
  11. class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase)

    photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end
  12. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  13. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  14. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  15. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  16. class Purchase < ActiveRecord::Base; end def self.create_for_buyer(buyer, photo) purchase =

    self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  17. class Purchase < ActiveRecord::Base; end def self.create_for_buyer(buyer, photo) purchase =

    self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end > Purchase.instance_methods.size - Object.instance_methods.size => 214
  18. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  19. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  20. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  21. class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer,

    photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end
  22. A use case is a written description of how users

    will perform tasks on your website. usability.gov
  23. It outlines, from a user's point of view, a system's

    behaviour as it responds to a request. usability.gov
  24. Each use case is represented as a sequence of simple

    steps, beginning with a user's goal and ending when that goal is fulfilled. usability.gov
  25. module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap {

    |use_case| use_case.perform } end end def perform raise NotImplementedError end end
  26. module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap {

    |use_case| use_case.perform } end end def perform raise NotImplementedError end end
  27. module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap {

    |use_case| use_case.perform } end end def perform raise NotImplementedError end end
  28. module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap {

    |use_case| use_case.perform } end end def perform raise NotImplementedError end end
  29. module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap {

    |use_case| use_case.perform } end end def perform raise NotImplementedError end end
  30. class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer

    @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end
  31. class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer

    @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end
  32. class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer

    @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end
  33. class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer

    @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end
  34. class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer

    @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end
  35. class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer

    @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end
  36. private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice

    Buyer::InvoiceMailer.enqueue(purchase) end def increment_sales_count Photo::SalesCount.increment(photo) end def allow_buyer_to_review_photo Photo::ReviewPermissions.add_buyer(buyer, photo) end
  37. class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to

    purchase_completed_path else redirect_to purchase_failed_path end end end
  38. class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to

    purchase_completed_path else redirect_to purchase_failed_path end end end
  39. class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to

    purchase_completed_path else redirect_to purchase_failed_path end end end
  40. class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to

    purchase_completed_path else redirect_to purchase_failed_path end end end
  41. RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo)

    } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end
  42. RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo)

    } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end
  43. RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo)

    } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end
  44. RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo)

    } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end
  45. private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice

    Buyer::InvoiceMailer.enqueue(purchase) end def increment_sales_count Photo::SalesCount.increment(photo) end def allow_buyer_to_review_photo Photo::ReviewPermissions.add_buyer(buyer, photo) end
  46. private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice

    Buyer::InvoiceMailer.enqueue(purchase) end def increment_sales_count Photo::SalesCount.increment(photo) end def allow_buyer_to_review_photo Photo::ReviewPermissions.add_buyer(buyer, photo) end
  47. private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice

    Buyer::InvoiceMailer.enqueue(purchase) end # ... def send_purchase_confirmation Purchase::ConfirmationMailer.enqueue(purchase) end def allow_buyer_to_review_photo
  48. private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice

    Buyer::InvoiceMailer.enqueue(purchase) end # ... def send_confirmation_email Purchase::ConfirmationMailer.enqueue(purchase) end def allow_buyer_to_review_photo