18
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
18