Ruby on Rails Hidden Goodies You Gotta Know About
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.
Let us explore this hidden path of the Rails Way.
A quick glance at the Ruby on Rails organization on Github
As you surely know by now, all the Ruby on Rails framework's code is available on Github, under the Ruby on Rails organization.
Ruby on Rails on Github
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.
Ruby on Rails Observers
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.
The Observer design pattern is also known as:
- Listener
- Event-Subscriber.
Let's see some scenarios for using the pattern:
- Synchronizing your users with 3rd-party CRM
- Notifying users on products availability
- Generating an invoice after an order was placed
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.
Ruby on Rails Rails Observers repository
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: - Create and update
callbacks:
before_validation
after_validation
before_save
around_save
before_create
orbefore_update
around_create
oraround_update
after_create
orafter_update
after_save
after_commit
andafter_rollback
- Destroy callbacks: -
before_destroy
around_destroy
after_destroy
after_commit
andafter_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.
Ruby on Rails: ActiveResource
The next awesome gem that's available next to the mighty Ruby on Rails
framework and integrates with it smoothly is activeresource
gem.
Ruby on Rails Active Resource repository
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`</pre>
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.
Ruby on Rails Country Select
One last little thing worth mentioning that got extracted from Ruby on Rails
is a country_select
gem.
Ruby on Rails forked Country Select repository
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.
Conclusion
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!