DRYing up Ruby code with Scopes

Software engineer transitioning into ML/AI Engineering
Search for a command to run...

Software engineer transitioning into ML/AI Engineering
💯
I recently set up Transmission on my Mac mini and wanted to access it remotely through Tailscale. In theory, it should’ve been easy: install Tailscale on both devices, open: http://mac-mini:9091/transmission/web …and call it a day. Instead, Transmis...
Don't outsource your boring work

Testing a hybrid approach on local and hosted LLM models
TLDR I've built a classifier for soccer team's uniforms, which you can check out here; It required some data cleaning to avoid overfitting but achieved about 83% accuracy; A little bit on my exciting journey exploring AI over the years; Hello al...

Before we start: Remember to sign up for the blog! It's free and you'll get awesome content directly to your inbox 😁 Last Saturday, my wife Hannah and I were having dinner with a couple of friends of ours who are both technical people. Inevitably,...

Many times I find myself or fellow team members in need of DRYing up their code or encapsulating logic to avoid redundancy but often times the solutions proposed introduce more complexity than what it's worth.
In the quest for cleaner and more maintainable code, we often turn to techniques like .send and super(), hoping to encapsulate logic and reduce redundancy. However, as our applications grow in size and complexity, these solutions can sometimes do more harm than good. The use of such techniques can increase cognitive load, making it challenging for developers to navigate and understand the codebase effectively.
Consider the case of super(), which might refer to a method potentially buried deep within Ruby's inheritance hierarchy. Imagine the mental gymnastics required to trace the execution flow back to its origin, all while trying to stay focused on the task at hand. It's no wonder that developers can find themselves lost in a sea of complexity.
Over time I found a good solution to that problem might be surprisingly simple: using Rails' scopes. For those of you who aren't familiar with scopes they encapsulate commonly used queries so they're easily accessible from a Model's instance. For example, say we have a Products model and we need to fetch the products that are available. A scope to achieve that might look like the following:
class Product < ApplicationRecord
scope :available, -> { where(available: true) }
end
Hence, if you need to chain it with other queries the code becomes super simple and highly legible:
Product.available.where(category: 'Electronics')
Compare that approach with the following example that uses inheritance to achieve the same functionality:
# Base model class
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.available
where(available: true)
end
end
# Subclass inheriting from ApplicationRecord
class Product < ApplicationRecord
def self.category(category)
where(category: category)
end
end
Those are at least two classes already and significantly more code to achieve the same functionality. So to summarize, a few advantages of scopes over super() and send are as follows:
Simplicity and Clarity: Scopes are defined directly within the model, serving as a single source of truth for data retrieval logic. This simplicity makes code more readable and easier to maintain over time.
Reduced Complexity: Scopes help avoid the introduction of unnecessary complexity, particularly in files that are already complex. By encapsulating logic within the model, scopes keep code clean and focused on its primary purpose.
Promotion of Composition over Inheritance: By embracing scopes, you set a precedent for favoring composition over inheritance in your codebase. This approach aligns with best practices in object-oriented design and encourages modular, reusable code.
In conclusion, when it comes to refactoring code in Ruby on Rails applications, simplicity is key. By leveraging Rails' scopes, we can streamline code, reduce complexity, and foster a more maintainable codebase.
Thank you for reading and I hope you've enjoyed it,
Teo.