Patterns in ruby and rails
Some patterns here for reference:
Value Object
Probably the simplest pattern is Value Object - PORO(plain old ruby object) that provides methods that return values:
class User
def initialize (full_name:, email:)
@full_name = full_name
@email = email
end
attr_reader :full_name, :email
def first_name
full_name.split(" ").first
end
def last_name
full_name.split(" ").last
end
end
Service
Service object is responsible for doing only one thing:
class Greeter
def self.call(name, title)
"Welcome back #{title}. #{name}!"
end
end
Of course, in reallity, you will have more complicated logic, but you want to make your service responsible for one thing only (single responsibility rule).
Decorator
Honestly, I am confusing decorators
and presenters
. I found this explanation and example here:
The decorator pattern is similar to the presenter, but instead of adding additional logic, it alters the original class’s behavior.
We have the Post model that provides a content attribute that contains the post’s content. On the single post page, we would like to render the full content, but on the list, we would like to render just a few words of it:
class PostListDecorator < SimpleDelegator
def content
model.content.truncate(50)
end
def self.decorate(posts)
posts.map { |post| new(post) }
end
private
def model
__getobj__
end
end
@posts = Post.all
@posts = PostListDecorator.decorate(@posts)
Presenter
This pattern is used when you need to isolate some logic and is usually (not always) used in views.
class UserPresenter
def initialize(user)
@user = user
end
def role
if @user.admin?
'Administrator'
elsif @user.signed_in
'User'
else
'Guest'
end
end
end
Policy Objects
These objects are responsible for permissions, policies etc. Examples of gems will be cancan
, pundit
class UserAccoutnPolicy
def initialize(user, account)
@user = user
@account = account
end
def access?
admin? && @account.user == @user
end
def close?
access? && @account.active? && @user.balance.positive?
end
private
def admin?
@user.role == 'admin'
end
end
or another example
class BankTransferPolicy
def self.allowed?(user, recipient, amount)
user.account_balance >= amount &&
user.transfers_enabled &&
user != recipient &&
amount.positive?
end
end
Builder
Again, nice example with file parsers from long live ruby
The builder pattern is often also called an adapter. The pattern’s main purpose is to provide a simple way of returning a given class or instance depending on the case. If you are parsing files to get their contents you can create the following builder:
class FileParser
def self.build(file_path)
case File.extname(file_path)
when '.csv' then CsvFileParser.new(file_path)
when '.xls' then XlsFileParser.new(file_path)
else
raise(UnknownFileFormat)
end
end
end
class BaseParser
def initialize(file_path)
@file_path = file_path
end
end
class CsvFileParser < BaseParser
def rows
# parse rows
end
end
class XlsFileParser < BaseParser
def rows
# parse rows
end
end
And usage: you can access the rows without worrying about selecting a good class that will be able to parse the given format:
parser = FileParser.build(file_path)
rows = parser.rows
Null object
Nobody likes to deal with nil
s and having those extra guards and defensive style.
If you expect a user in your code, and user is not created (for example guest), rather than check for nil
create GuestUser
class GuestUser
def name
'Guest'
end
def posts
[]
end
end
Now if you need to perform something with user you don’t need to check for nil
s and can treat user as it always exists.
user = current_user || GuestUser.new
puts "Welcome #{user.name}"
puts "Number of posts created #{user.posts.size}"
Afterword
Patterns solve problems but when used incorrectly they bring unneeded complexity to your codebase.