hidden-goodies

Ruby on Rails Hidden Goodies You Gotta Know About

wiktor-plagaWiktor Plaga
March 25, 20238 min reading time

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.

undefined "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.

undefined "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 or before_update
  • around_create or around_update
  • after_create or after_update
  • after_save
  • after_commit and after_rollback
  • Destroy callbacks: - 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.

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.

undefined "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 ActiveResources, 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.

undefined "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!

Hix logoHix Software Project Starter

Automate your project configuration with the Hix project starter.

Skip all the mundane tasks and start delivering.

Subscribe

Like what you're reading?

 

Get new articles straight to your inbox.

We use cookies, please read and accept our Cookie Policy.