window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster already exists') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Applying the Use Case Pattern with Rails - The Codest
The Codest
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Industries
    • Fintech & Banking
    • E-commerce
    • Adtech
    • Healthtech
    • Manufacturing
    • Logistics
    • Automotive
    • IOT
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
  • About us
  • Services
    • Software Development
      • Frontend Development
      • Backend Development
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Data Engineers
      • Cloud Engineers
      • QA Engineers
      • Other
    • It Advisory
      • Audit & Consulting
  • Value for
    • CEO
    • CTO
    • Delivery Manager
  • Our team
  • Case Studies
  • Know How
    • Blog
    • Meetups
    • Webinars
    • Resources
Careers Get in touch
Back arrow GO BACK
2022-04-05
Software Development

Applying the Use Case Pattern with Rails

Nicolas Nisoria

A common problem while working with Rails is to decide where to place the logic from our features.

The logic is often placed in the Controllers, Models, or if we are lucky in a Service Object. So if we have Service Objects then why do we need Use Cases?

Follow me in this article to discover the benefits of this pattern.

Use Case

Definition

A use case is a list of actions or event steps typically defining the interactions between a role and a system to achieve a goal.

It’s worth mentioning that this pattern is applied in many different ways and has alternative names. We can find it as Interactors, Operators or Commands, but in the Ruby community we stick with Use Case. Every implementation is different but with the same purpose: to serve a user’s use case of the system.

Even if in our project we are not defining the requirements using Use Cases and UML this pattern is still useful to structure the business logic in a practical way.

Rules

Our Use Cases must be:

  • Framework agnostic
  • Database agnostic
  • Responsible for only one thing (define the steps to achieve the user’s goal)

Benefits

  • Readability: Easy to read and understand since the steps are clearly defined.
  • Decoupling: Move the logic from Controllers and Models and create a new level of abstraction.
  • Visibility: The codebase reveals the features available in the system.

Into Practice

Let’s take the example of a user that wants to purchase something in our system.

module UseCases
  module Buyer
    class Purchase
      def initialize(buyer:, cart:)
        @buyer = buyer
        @cart = cart
      end
      def call
        return unless check_stock
        return unless create_purchase
notify end
private
      attr_reader :buyer, :cart
      def check_stock
        Services::CheckStock.call(cart: cart)
end
      def create_purchase
        Services::CreatePurchase.call(buyer: buyer, cart: cart).call
      end
      def notify

         Services::NotifyBuyer.call(buyer: buyer)
       end
     end
   end
 end

As you may see in this code example, we created a new Use Case called Purchase. We defined only one public method call. Inside the call method, we find pretty basic steps to make a purchase, and all the steps are defined as private methods. Every step is calling a Service Object, this way our Use Case is only defining the steps to make a purchase and not the logic itself. This gives us a clear picture of what can be done in our system (make a purchase) and the steps to achieve that.

Now we are ready to call our first Use Case from a controller.

class Controller
  def purchase
    UseCases::Buyer::Purchase.new(
      buyer: purchase_params[:buyer],
      cart: purchase_params[:cart]
    ).call

    ...
  end

  ...
end

From this perspective, the Use Case looks pretty much like a Service Object but the purpose is different. A Service Object accomplishes a low-level task and interacts with different parts of the system like the Database while the Use Case creates a new high-level abstraction and defines the logical steps.

Improvements

Our first Use Case works but could be better. How could we improve it? Let’s make use of the dry gems. In this case we are going to use dry-transaction.

First let’s define our base class.

class UseCase
  include Dry::Transaction

  class << self
    def call(**args)
      new.call(**args)
    end
  end
end

This will help us to pass attributes to the UseCase transaction and use them. Then we are ready to re-define our Purchase Use Case.

module UseCases
  module Buyer
    class Purchase
      def initialize(buyer:, cart:)
        @buyer = buyer
        @cart = cart
      end

      def call
        return unless check_stock
        return unless create_purchase
        notify
      end

      private

      attr_reader :buyer, :cart

      def check_stock
        Services::CheckStock.call(cart: cart)
      end

      def create_purchase
        Services::CreatePurchase.call(buyer: buyer, cart: cart).call
      end

      def notify
        Services::NotifyBuyer.call(buyer: buyer)
      end
    end
   end
 end

With the new changes, we can see in a clear way how our steps are defined and we can manage the result of every step with Success() and Failure().

We are ready to call our new Use Case in the controller and prepare our response depending on the final result.

class Controller
  def purchase
    UseCases::Buyer::Purchase.new.call(
      buyer: purchase_params[:buyer],
      cart: purchase_params[:cart]
    ) do |result|
      result.success do
        ...
      end
      result.failure do
        ...
      end
    end

    ...
  end

  ...
end

This example could be improved even more with validations but this is enough to show the power of this pattern.

Conclusions

Let’s be honest here, the Use Case pattern is pretty simple and looks a lot like a Service Object but this level of abstraction can make a big change in your application.

Imagine a new developer joining the project and opening the folder use_cases, as a first impression he will have a list of all the features available in the system and after opening one Use Case he will see all the necessary steps for that feature without going deep in the logic. This sense of order and control is the major benefit of this pattern.

Take this in your toolbox and maybe in the future you will make good use of it.

Related articles

Software Development

Ruby on Rails modularization with Packwerk Episode I

Humans find it difficult to see the big picture of a problem without devoting a lot of time and effort. This happens especially while working with large and complex applications....

Nicolas Nisoria
Software Development

Ruby on Rails modularization with Packwerk Episode II

In the second episode of our Ruby on Rails modularization with Packwerk we will take a close look at the concept of application as an package.

Nicolas Nisoria
Software Development

Rails and Other Means of Transport

Rails is a Rack-compatible framework focused on quick application development. Unfortunately, the "everything out of the box" approach and blind Rails-way behavior often cause the application code to lose quality,...

The Codest
Krzysztof Buszewicz Senior Software Engineer

Subscribe to our knowledge base and stay up to date on the expertise from the IT sector.

    About us

    The Codest – International software development company with tech hubs in Poland.

    United Kingdom - Headquarters

    • Office 303B, 182-184 High Street North E6 2JA
      London, England

    Poland - Local Tech Hubs

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warsaw, Poland

      The Codest

    • Home
    • About us
    • Services
    • Case Studies
    • Know How
    • Careers
    • Dictionary

      Services

    • It Advisory
    • Software Development
    • Backend Development
    • Frontend Development
    • Staff Augmentation
    • Backend Developers
    • Cloud Engineers
    • Data Engineers
    • Other
    • QA Engineers

      Resources

    • Facts and Myths about Cooperating with External Software Development Partner
    • From the USA to Europe: Why do American startups decide to relocate to Europe
    • Tech Offshore Development Hubs Comparison: Tech Offshore Europe (Poland), ASEAN (Philippines), Eurasia (Turkey)
    • What are the top CTOs and CIOs Challenges?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2025 by The Codest. All rights reserved.

    en_USEnglish
    de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek en_USEnglish