Rails design pattern

If you are a Rails-developer you know at least MVC pattern. Most of us know of course that it has some limitations in terms of structuring your code (fat controllers/models) and in some point of your developer's path you start thinking about code quality and here I mean not code style and other minor things but more general - single responsibility principal (SRP) and how we can organize code in order to make it real, Liskov’s Substitution Principle etc.
Ok, we have spotted a problem and what can we do with it?
As you can have noticed - SOLID principles and general OO design are our tools. But they are too general. Are there something more specific? Googling will provide you at least next possible patterns:
  • Builder
  • Decorator
  • Form
  • Interactor
  • Observer
  • Policy
  • Presenter
  • Query
  • Service
  • Value
  • I am not going to retell them, you can easily find definitions. But I want to light on next moment: how many from them you are using in yours projects? Is it straightforward which pattern you need to use?
    Let take for example Service and Interactor:

    The service design pattern breaks down a program into its functional constituents. Each service object completes one task, such as running payment for a customer, completing a bank transaction, etc.

    You can use the interactor design pattern to split a large task into a series of small, inter-dependent steps. If any of the steps fail, the flow stops, and a relevant message appears about why the execution failed.

    The definitions are different but the aim is the same - breaks down something big into small blocks. From my point of view, the interactor pattern is a special case of service.
    class AuthenticateUser
      include Interactor
    
      def call
        if user = User.authenticate(context.email, context.password)
          context.user = user
          context.token = user.secret_token
        else
          context.fail!(message: "authenticate_user.failure")
        end
      end
    end
    This excerpt was written in context of interactor gem.
    and
    class AuthenticateUserService < BaseService
      def perform
        if (user = User.authenticate(context.email, context.password))
          success_result(status: :success, message: nil, data: user) # this method as and error_result are implemented in BaseService
        else
          error_result(status: :error, message: user.errors, data: {})
        end
      end
    end
    Does it look similar? From my perspective, they are not different patterns but one pattern with slightly different implementation.
    Let's take anther one confusing couple: Decorator vs Presenter:

    The Decorator pattern adds new functionality to an existing object by wrapping it with additional objects containing the new features. This helps to modify an instance of a class for extra features and not define new static subclasses in the code.

    The Presenter design pattern aims to isolate view rendering logic from the controller and model and encapsulate it into a reusable component. It makes the controller and models leaner and enhances code readability.

    Let's take a look into code snippets:
    class PostPresenter
      def initialize(post)
        @post = post
      end
    
      def image
        @post.image? ? @post.image.url : 'no-image.png'
      end
    
      # similary define other methods with their logic
    
    end
    class Car
      def roof
        "fixed"
      end
    end
    
    class ConvertibleDecorator < SimpleDelegator
      def roof
        "collapsible"
      end
    
      def self.decorate(car)
            new(car)
      end
    
     private
    
     def model
       __getobj__
     end
    end
    
    #using 
    basicCar1 = Car.new()
    basicCar2 = Car.new()
    
    basicCar1 = ConvertibleDecorator.decorate(basicCar1)
    
    puts(basicCar1.roof) # => collapsible
    puts(basicCar2.roof) # => fixed
    You can argue that is different approach... but I would like to say - different implementation but the aim is the same. Both examples are trying to separate some additional(auxiliary) logic in separate place. And there are other way for do it, for example:
    class Car
      def roof
        "fixed"
      end
    end
    
    module ConvertibleDecorator
      def roof
        "collapsible"
      end
    end
    
    #using 
    basicCar1 = Car.new()
    basicCar2 = Car.new()
    
    basicCar1 = basicCar1.extend ConvertibleDecorator
    
    puts(basicCar1.roof) # => collapsible
    puts(basicCar2.roof) # => fixed
    Some other patterns are more straightforward and not so confusing. But the main idea of the post is:
  • try to use general principle when you structure the code (SOLID);
  • not use patterns because it is just the best practice but when you can see that it makes sense; if you cannot match you class with one of this patterns it is ok if you use something new what related to you problem (often developers see Services in a project and try to put all new classes there however it can be QueryObject or ValueObject).
  • The definitions of the patterns and part of examples were taken from here

    23

    This website collects cookies to deliver better user experience

    Rails design pattern