Ruby on Rails is no doubt very popular, the framework itself is highly-recognizable among the programmers and receives steady contributions from their community.
But did you know that there is more than just Ruby on Rails?
If you started your adventure with the framework later than four years ago, as I did, there's a good chance that you didn't.
I was super surprised, in a good way, to discover those!
Over the years, with one major update after another, the Rails team carefully extracted some of its less-known parts into their own gems.
That by any means does not imply that those are forgotten - it's just that their use cases are far-less often occurring.
Yet still, it is very nice to have them in our development toolkit, and same as the whole Ruby on Rails framework - they are very easy and straightforward to use.
Table of contents
Let us explore this hidden path of the Rails Way.
As you surely know by now, all the Ruby on Rails framework's code is available on Github, under the Ruby on Rails organization.
But there are 98 repositories in total, and some of them are truly golden solutions for a common, but not very often occurring problems.
If you care about implementing The Rails Way in your workflow and like to rely on stuff that simply works for certain scenarios, read on.
One of such scenarios that are not needed for every application is observing the changes occurring in your ActiveRecord
and ActionController
objects constantly, and acting on them accordingly.
Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
refactoring.guru
If you're interested in the full explanation of its implementation, do take a look at the Design Patterns: Observer by the Refactoring Guru Raccoon.
The Observer design pattern is also known as:
Let's see some scenarios for using the pattern:
There's more, but those three are pretty self-explanatory.
This functionality, once native to the Rails framework, was extracted from it in the 4.0 version and lives its own life ever since as a rails-observers
gem.
Its capabilities are pretty straightforward: you can hook up to any of the controllers and/or models callbacks, and execute the observer code whenever they occur.
Here's the full list of ActiveRecord
callbacks:
before_validation
after_validation
before_save
around_save
before_create
or before_update
around_create
or around_update
after_create
or after_update
after_save
after_commit
and after_rollback
before_destroy
around_destroy
after_destroy
after_commit
and after_rollback
And here are ActionController
callbacks:
after_action
append_after_action
append_around_action
append_before_action
around_action
before_action
prepend_after_action
prepend_around_action
prepend_before_action
skip_after_action
skip_around_action
skip_before_action
In order to observe them, first, we need to install the rails-observers
gem. Add the following line to your Gemfile.
gem 'rails-observers'
and then execute bundle install
from the command line.
Due to the gem's convention, observers are put next to the models and controllers they observe.
So, if you have app/models/user.rb, its observer will be in the app/models/user_observer.rb file.
That's one way to keep our models slim.
Let's now take a look at the Observer's DSL. Here's the simplest observer possible:
class UserObserver < ActiveRecord::Observer def after_create(user) user.logger.info("New user has been created: #{user.name}") end end
As you can see, the DSL really straightforward.
For every callback we want to observe and act on, we simply create a correlated method in our observer class.
It accepts the related ApplicationRecord
instance out of the box.
There's also one neat trick if you ever need it: we can create one observer for multiple models. Let's see:
class CleanupObserver < ActiveRecord::Observer observe :article, :review, :comment, :post def after_destroy(record) record.logger.info("A #{record.class.name} was just deleted") end end
With this in place, using the special observe
method, we instruct our Rails logger to inform as every time one of Article
, Review
, Comment
or Post
records are destroyed.
One last step for observers to work is registering them in the config/application.rb file.
config.active_record.observers = [:user_observer, :cleanup_observer]
Now that we've seen how to observe our models, let's take a look at the controllers.
Why would we even want to observe controllers in the first place?
In most cases, the answer is caching - and the Rails team knows that very well, so they've created ActionController::Caching::Sweeper
.
Let's see what happens in an exact example from the gem's documentation.
class ListSweeper < ActionController::Caching::Sweeper observe List, Item def after_save(record) list = record.is_a?(List) ? record : record.list expire_page(controller: 'lists', action: %w[show public feed], id: list.id) expire_action(controller: 'lists', action: 'all') list.shares.each { |share| expire_page(controller: 'lists', action: 'show', id: share.url_key) } end end
this code assumes a presence of two following models, List
and Item
class List < ApplicationRecord has_many :items end
and the second one
class Item < ApplicationRecord belongs_to :list end
With that in place, let's use the observer's methods in our ListsController
, as shown in the documentation example:
class ListsController < ApplicationController caches_action :index, :show, :public, :feed cache_sweeper :list_sweeper, only: [ :edit, :destroy, :share ] end
As we can see, the controller assumes four read-only actions for the List
resource, and instructs them to be cached. Those are index
, show
, public
and feed
.
Thanks to our previously defined sweeper, we can easily regenerate the cache anytime the cached Lists
is either edited, destroyed or shared.
This makes a perfect sense, assuming that our lists' shares are displayed on all of its views.
The next awesome gem that's available next to the mighty Ruby on Rails framework and integrates with it smoothly is activeresource
.
It allows us to fetch other REST APIs and map their resources to instances of classes very similar to those of ApplicationRecord
.
Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database tables. When a request is made to a remote resource, a REST JSON request is generated, transmitted, and the result received and serialized into a usable Ruby object.
ActiveResource README.md
So, let's assume for a second that there is a https://movies.com/api
that exposes the following endpoints:
GET https://movies.com/api/actors.json
GET https://movies.com/api/actors/:id.json
POST https://movies.com/api/actors.json
PUT https://movies.com/api/actors/:id.json
DELETE https://movies.com/api/actors/:id.json
Now, let's install the activeresource
gem by adding it to the Gemfile, and then create the app/resources directory.
With those in place, let's create our new ActiveResource
s, the Actor
in the corresponding app/resources/actor.rb file.
class Actor < ActiveResource::Base self.site = 'https://movies.com/api' end
With only these 3 lines of code in place, we are free to use the full power of the ActiveResource
magic, as long as the API we connect to follows the REST guidelines.
$ Actor.find(1) => {"first_name":"Edward","last_name":"Norton"} $ Actor.find(1).destroy $ Actor.find(1) => {}
The DSL is so similar to the ApplicationRecord
that using it takes no learning curve at all for any Rails developer.
This goes much, much further - we can fetch associated resources and they're returned as nested, own objects, and act on them as well.
On top of all that, there's also an easy way of implementing all types of API authentication, if it's not public.
One last little thing worth mentioning that got extracted from Ruby on Rails is a country_select
gem.
It does not belong to the Rails organization anymore and got archived some time ago, but one of its forks is still maintained to this day.
The idea of it is pretty simple
Rails – Country Select: Provides a simple helper to get an HTML select list of countries using the ISO 3166-1 standard.
Country Select README.md
In some cases, this is super-helpful: hardly anybody wants to maintain the full list of world's countries, so keeping them in a dedicated gem maintained by the community makes perfect sense.
Its DSL is very similar to Rails' default form elements available in its Views.
<%= form_for User.new, url: root_url do |f| %> <%= f.country_select :country_code %> <% end %>
On top of that, it exposes the ISO3166
module with a Country
class that enables easy access to all the data on the backend.
As you can see there's something more besides all the goodies available in Ruby on Rails by default.
It's not a plethora of stuff, but still - it is worth knowing a full scope of options when encountering some more-specific problems in our day-to-day Ruby on Rails development workflow.
What is your take on it? Maybe you already had a chance to work with any of those and want to share some better options instead?
Or maybe reading this you're thinking "I wish I knew sooner" recollecting some custom implementation of similar patterns all by yourself?
Please let me know in the comments!
Leave a Reply