🎧 Spotlights
Thoughts on ActiveRecord Callbacks
ActiveRecord callbacks provide a way to add actions to the lifecycle of models. Although this is a valuable feature, it can sometimes lead to problems.
They are an alternative to template methods, overriding or calling `super`. Generally, anything related to persistence should be put in a callback, while validations should not. Caching data can be put in a callback, but input sanitation is an abuse and can be dangerous when put in callbacks. Infrastructure details should be distinct from business logic.
Callbacks should only be used as a last resort in the development process.
It's better to start with other solutions and only move to callbacks if it makes the code easier to manage.
Our primary goal should be to ensure our code is clear and decoupled. This will help prevent future headaches and make it easier to maintain our code in the long run. ActiveRecord callbacks can be a powerful tool for prototyping, but we must be cautious about how we use them.
If you are enjoying the learning journal, please ❤️
Callbacks in Ruby on Rails can be convenient, but they can also be problematic.
Callbacks hide context around data operations that are important to a higher-level context. This can create a situation where the same data is needed in a different context, but a callback prevents that from happening. It is best to avoid callbacks when possible, as they can cause more problems than they solve.
It's important to ask yourself whether the model is responsible for performing the task in question.
# Bad
class Company < ActiveRecord::Base
after_commit :send_emails_after_onboarding
private
def send_emails_after_onboarding
if just_finished_onboarding?
EmailSender.send_emails_for_company(`self`)
end
end
end
# Good
class Company < ActiveRecord::Base
end
class OnboardCompany
def call(company)
# some other work probably happens up here, and then...
company.save!
EmailSender.send_emails_for_company!(company)
end
end
# Bad
class Company < ActiveRecord::Base
after_commit :create_welcome_notification, on: :create
private
def create_welcome_notification
# We're incurring an extra database request here, which
# is something we generally want to avoid.
notifications.create({ title: 'Welcome to Product!' })
end
end
# Good
class Company < ActiveRecord::Base
after_commit :create_welcome_notification, on: :create
private
def create_welcome_notification
WelcomeNotificationCreator.perform_async(id)
end
end
class WelcomeNotificationCreator
include Sidekiq::Worker
def perform(company_id)
@company = Company.find(company_id)
@company.notifications.create({ title: 'Welcome to Product!'}
end
end
If you think someone will benefit from this content, please share.
Generally, the code you're trying to execute belongs somewhere other than the model. For instance, sending an email after a model is saved is not the model's job. A Service Object can be a better option for such tasks.
Callbacks in your code may require more development time, resulting in more code. Conservative use of callbacks improves code modularity and testability.
💻 👓
Initial implementation of Roman Numbers kata, still a couple of tests fail and my Elixir is not idiomatic but getting the hand of it, a small program at a time.
defmodule RomanNumerals do
@doc """
Convert the number to a roman number.
"""
@spec numeral(pos_integer) :: String.t()
def numeral(number) do
if number < 1 or number > 3000 do
raise ArgumentError, "Number must be between 1 and 3000"
end
roman_numerals = [
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"}
]
roman_numerals
|> Enum.reduce({number, ""}, fn {arabic, roman}, {n, acc} ->
{div, rem} = div(n, arabic)
{rem, acc <> String.duplicate(roman, div)}
end)
|> elem(1)
end
end
🧪 Next Actions
I might keep going deep into Elixir, the language looks similar to Ruby enough that it deserve more time.
I was trying to solve every problem in for the challenge but I had to stop due to esoteric languages like Nim, the learning curve is too high for me to complete it on those languages, next stop will be C++.
Snippets of Code is an engineering journal documenting learnings for experiments and projects. I am learning about various programming languages with Exercism’s #48in21 challenge. Please share or like if the content is helpful.