Oauth2 on Rails: the client application

In part 1 we completed the Oauth server application. We now need to build the client app:


rails new oauth-client

Then add the omniauth-oauth2 gem, which allows us to use Oauth2 by just creating a simple configuration (strategy) file:


gem 'omniauth-oauth2'

And now we need to to build the strategy.

The doorkeeper Omniauth strategy

Omniauth is a versatile gem, allowing programmers to use it with variety of oauth providers, each one with its own different configuration.

This is achieved by using strategies, there are plenty already built for you, but in order to authenticate to our oauth-server application we need to write a custom one. We are naming it “doorkeeper” after the name of the gem used in the server app that handles Oauth authentication.

Let’s create the file lib/doorkeeper.rb and put the following code in it:


require 'omniauth-oauth2'

module OmniAuth
  module Strategies
    class Doorkeeper < OmniAuth::Strategies::OAuth2
      option :name, 'doorkeeper'
      option :client_options, {
        site:          'http://localhost:3000',
        authorize_url: 'http://localhost:3000/oauth/authorize'
      }

      uid {
        raw_info['id']
      }

      info do
        {
          email: raw_info['email'],
        }
      end

      extra do
        { raw_info: raw_info }
      end

      def raw_info
        @raw_info ||= access_token.get('/me').parsed
      end
    end
  end
end

Here we define a class, we give the strategy the name “doorkeeper”, we specify some urls where omniauth will go to manage the server authentication. The #raw_info method instead is used to fetch the information about the user logged on the server application (again, in the previous post we wrote the code, it’s inside application_controller.rb). The bulk of this class is just omniauth boilerplate, to see an example and some more details on building strategies you can visit the gem readme.

Now we need to generate the omniauth initializer:


touch config/initializers/omniauth.rb

and put this code in it:


require 'doorkeeper'

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :doorkeeper, <application_id>, <application_secret>
end

We require the strategy we just defined, then we tell omniauth to add it to its current strategies (and eventually to the application middlewares).
Of course we must replace <application_id> and <application_secret> with the values we got when creating the client application configuration on the server (when in the previous post we visited http://localhost:3000/oauth/applications/new … remember?)

The omniauth configuration is not over yet: we need to add some omniauth routes in config/routes.rb:


Rails.application.routes.draw do
  root to: redirect('/auth/doorkeeper')

  get '/auth/:provider/callback' => 'application#authentication_callback'
end

The first line redirects to the path ’/auth/doorkeeper’, which will be handled by omniauth. The second is the callback url, used when returning from the server authentication process. Let’s write the code for its action, directly in application_controller.rb:


class ApplicationController < ActionController::Base
  auth = request.env['omniauth.auth']
  render json: auth.to_json
end

OmniAuth will always return a hash of information after authenticating with an external provider in the Rack environment under the key omniauth.auth. We access that object and render it as json.

Try it

It’s time to start both applications: oauth-server must run on port 3000, while oauth-client must run on 3001. When you visit localhost:3001 this is what should happen:

  • you are redirected to /auth/doorkeeper, a url that will start the Oauth authentication using the “doorkeeper” omniauth strategy
  • You are furher redirected to the server-app
  • If you aren’t authenticated on the server-app you will be redirected to localhost:3000/users/sign_in.
  • After successful sign in on the server, you will be redirected on the client app at /auth/doorkeeper/callback. Here you will see the information we got from the server app in json format.
image

At this point you can save the user information received from the server app in the client app database and build your user data and resources from there.

ECMAScript 6 (aka the new javascript) is coming soon! (hopefully…)

ECMAScript 6 (aka the new javascript) is coming soon! (hopefully…)

Oauth2 on Rails

Oauth2 has become the defacto standard for online authentication: Twitter, Facebook, Four Square, Amazon are some of the big players that rely on it for their APIs.

The Oauth authentication process requires an authentication server application where users login, and a client application that uses the server application for user authentication.

We’re going to create both the client and server application from scratch with rails using devise, doorkeeper and omniauth-oauth2 gems to speed up the process, make them work together and analyze how the authentication works.

In this first post we’re going to build the server application step by step.

The server application

Devise will provide authentication, while doorkeeper will allow the app to work as an Oauth2 server. Let’s generate a new app:


rails new oauth-server

then add devise and doorkeeper gems to the Gemfile:


gem "devise"
gem "doorkeeper"

generate the gems boilerplate and migrate the db:


rails g devise:install
rails g devise User

rails g doorkeeper:install
rails generate doorkeeper:migration

rake db:migrate

A bunch of files are generated in the process, among them there’s the config/initializers/doorkeeper.rb initializer. Let’s open this file and modify it to allow doorkeeper to use devise for authentication. Change the settings on the top of it as follows, remembering to comment out the “fail” instruction:


resource_owner_authenticator do
  current_user || begin
    session[:user_return_to] = request.fullpath
    redirect_to new_user_session_url
  end
end

admin_authenticator do
  current_user || redirect_to(new_user_session_url)
end

The resource_owner_authenticator block allows devise authenticated users to access their resources via doorkeeper. If the user is not authenticated we store the url where he came from in the session, and we redirect him to the login page.
The admin_authenticator bock limits the access to the doorkeeper admin area only to registered users. This admin area is used to manage (create, update, destroy) the client application tokens required for the Oauth authentication.

You may want to limit the access to restricted group of user, for example to admins. This code may work for you:


admin_authenticator do
  current_user && current_user.admin? || redirect_to(new_user_session_url)
end

Then let’s create a dummy user that we’ll use for experimenting in db/seed.rb:


User.create! email: 'test@test.com', password: 'password', password_confirmation: 'password'

rake db:seed

Let’s create the actual action that will provide the client app with the authenticated user information. We could build a separate controller, but let’s use application controller for the sake of brevity:


class ApplicationController < ActionController::Base
  before_action :doorkeeper_authorize!, only: :me

  def me
    render json: User.find(doorkeeper_token.resource_owner_id).as_json
  end
end

and update the routes as well:


Rails.application.routes.draw do
  use_doorkeeper
  devise_for :users

  get '/me' => 'application#me'
end

The directive before_action :doorkeeper_authorize!, only: :me applies only to the me action, and it basically looks for a doorkeeper_token, an object built starting from the oauth authorization code, if not found it will render with a 401 Unauthorized header. The same token object is then used in the me action body to retrieve the user details, which will be rendered as JSON.

Now we need to define root_path as Devise requires (please read the gem readme), then we can start the server and see what happens if we try to access the /me page:


rails s

curl -I localhost:3000/me
  HTTP/1.1 401 Unauthorized
  X-Frame-Options: SAMEORIGIN
  X-Xss-Protection: 1; mode=block
  X-Content-Type-Options: nosniff
  Cache-Control: no-store
  Pragma: no-cache
  WWW-Authenticate: Bearer realm="Doorkeeper", error="invalid_token", error_description="The access token is invalid"
  Content-Type: text/html
  X-Request-Id: 109af7db-5c09-4175-b6f3-cbd902f4caff
  X-Runtime: 0.003485
  Server: WEBrick/1.3.1 (Ruby/2.1.0/2013-12-25)
  Date: Fri, 30 Jan 2015 10:33:46 GMT
  Content-Length: 0
  Connection: Keep-Alive
  Set-Cookie: request_method=HEAD; path=/

As you can see, since we’re not authenticated and we didn’t provide a valid token the application responds with a 401 Unauthorized header.

Ideally the the client application will come to this url and get the user information, if authorized, and so the login process on the client app can complete.

So,everything is set for authentication, codewise, on the server application. The client application will need to be registered on the oauth-server app in order to be able to authenticate users. Let’s go and register it, heading your browser to http://localhost:3000/oauth/applications/new.
You will be redirected to the authentication page, fill in the form with the dummy user credentials in order to login (test@test.com, password) and you will be now presented a new form to create the new Oauth client application settings. Fill the form giving the name you prefer (I chose oauth-client) to the app and use this url as the redirect URI: http://localhost:3001/doorkeeper/callback.
This url must follow omniauth (used on the client app) conventions, where doorkeeper stands for the authentication strategy name (we’re going to build it later, the name could be anything meaningful for us) and callback is a path fixture for omniauth recognition. We will start the client app on localhost:3001, of course, and please consider switching to the more secure https protocol when you are production ready.
After pressing submit you will be presented with a success page showing the client app authentication settings, which include the application id, the secret key and the callback url. we’re going to use them later, so take a note.

image

Now let’s create the actual action that will provide the client app with the authenticated user information. We should build a separated controller, but let’s use application controller for brevity:


class ApplicationController < ActionController::Base
  before_action :doorkeeper_authorize!, only: :me

  def me
    render json: User.find(doorkeeper_token.resource_owner_id).as_json
  end
end

The line before_action :doorkeeper_authorize!, only: :me applies only to the me action, and it basically looks for a doorkeeper_token, an object built on the oauth authorization code, if not found it will render with a 401 Unauthorized header. The same token object is then used in the me action to retrieve the user information, which will be rendered in json format. Ideally the client application will come to this url and get the user information, if authorized, and so the login process on the client app can complete.
Let’s update the routes as well:


Rails.application.routes.draw do
  use_doorkeeper
  devise_for :users

  get '/me' => 'application#me'
end

The server application is now ready. Next time we will build the client application that will fetch the user data from the server. See you soon!

Ruby and self

What’s self?

Ruby newcomers usually are confused about the use of self, whether it’s a completely new concept for them or not.

First, let’s define self:

self is the current object

The definition may seem a little too dry, but that’s exactly what self is. In your code, at any time, there’s always a self object, and its value depends on the code context.

IRB and self

When practicing with ruby, irb is your best friend. For those who want to get fancy you can replace it with pry. Let’s see some examples, first start the irb session with the irb command, then let’s see what irb has to say about self:

$ irb

2.1.0 :001 > self
 => main 

> self.inspect
 => "main" 

self.class
 => Object 

There’s always a self when executing ruby code: at startup irb creates a top level object called main, which is actually just an instance of the Object class. That’s your default self.

self at class definition

Time to move on. Let’s see what self is when defining a class:

class Person
  self
end

 => Person

It’s the class itself, and it makes perfect sense. Do you know about class methods? When you’re out of the context of the class, you can define them like this:

def Person.show_self
  self.inspect
end

Person.show_self
 => "Person" 

But when you’re in the context of the class, you can define class methods in both these ways:

class Person
def Person.show_self
self.inspect
end
def self.show_self self.inspect end end Person.show_self => "Person"

They are equivalent, but the latter is preferable. Why? Because if you decide to change the name of the class, you don’t have to change the name in the signature method as well. 

self in instance methods

What about instance methods? Inside instance method definitions, self is the actual instance:

class Person
  def show_self
    self.inspect
  end
end

Person.new.show_self
 => #<Person:0x00000101939098>

Most of the times, inside method body definitions, self is redundant and we can avoid it:

class Person
  def self.show_self # class method, self is the class
    inspect
  end

  def show_self # instance method, self is the instance
    inspect
  end
end
Person.show_self
 => "Person"

Person.new.show_self
 => #<Person:0x00000101939098>

When self is required

There are some cases when you need an explicit self, namely when inside the method body there’s a local variable with the same name of the method you’re willing to call on self. If you omit self you’re going get some unexpected results for sure:

class Person
  def show_self
    inspect = 'boo!'
    inspect
  end
end

Person.new.show_self
 => "boo!"

class Person
  def show_self
    inspect = 'boo!'
    self.inspect
  end
end

Person.new.show_self
 => #<Person:0x00000101939098>

Another very common scenario when self is always required is when using writer methods:

class Person
  attr_accessor :name

  def set_name(person_name)
    self.name = person_name
  end
end

me = Person.new
me.set_name 'Andrea'

me.name
 => "Andrea"

If we had written

def set_name(person_name)
  name = person_name
end

then the code would have created a local variable named name.

The last important case when self is required is when you call the class method of an instance:

class Person
  def show_class
    self.class
  end
end

Person.new.show_class 
 => Person

In this case self is required because class is a reserved keyword, so we need to make sure the parser undestands we’re referencing to the instance class object.

When self cannot be used

Sometimes on the other hand you cannot use the explicit self: that’s the case with private methods:

class Person
  attr_accessor :name

  private :name

  def show_name
    name
  end

  def buggy_show_name
    self.name
  end
end

me.show_name 
 => "Andrea"

me.name
 NoMethodError: private method `name' called for #<Person:0x00000101939098& @name="Andrea">

me.buggy_show_name
 NoMethodError: private method `name' called for #<Person:0x00000101939098& @name="Andrea">

Wrapping up

In order to write idiomatic ruby, the rule of thumb is to avoid the explicit self whenever it’s not required.

Beginning Elasticsearch – mappings and analyzers

Elasticsearch is one of the most recent and promising full-text search engine. It’s built on Lucene, just like Solr, but it comes with nice RESTful JSON API and it was developed with scalability and the cloud in mind.

The documentation on the official website is complete and detailed, but for a newcomer it can be overwhelming, so I’ve decided to put together this introductory article.

First, you need to install Elasticsearch. It’s fairly easy on OSX: brew install elasticsearch will do the job. If you’re on linux there’s probably a package ready for you.

Once installation is done, if you don’t know where the executable file is located, you can find out with which elasticsearch. On my machine it’s in /usr/local/bin/elasticsearch and that’s exaclty what I have to type to start the server manually, but before that, let’s install Marvel, the official dashboard/development console. Sense is the name of the console, it’s a very nice web interface with some autocomplete features, so it’s highly recommended for getting familiar with Elasticsearch. From the Elasticsearch home directory, run bin/plugin -i elasticsearch/marvel/latest. Now you’re ready to rock.

In your browser go to http://localhost:9200/_plugin/marvel/sense/. That’s the development console. Let’s start by indexing some data. Since Elasticsearch can be schemaless, you can just throw data at it:image

Move the cursor on each code block and click the green arrow (next to the wrench) to execute the code. The right panel shows the result.

If you want to follow along with the example by typing the code in your machine you can find the code here.

While SQL databases have databases and tables, Elasticsearch has indexes and types. Just like a table belongs to a database, a type belongs to an index. In our example the index is “examples” and the type is “movies”

Looking at the right side of screenshot you can see that the first record was saved and there are some futher details about the operation outcome.

Mappings

I know you’re looking forward to make some queries, but your best option in order to get good matches out of your data is to properly index contents. That’s what mappings are about.

We didn’t need to write any schema upfront, but internally Elasticsearch generated one starting from our data. Let’s see it:

image

Elasticsearch can infer the field type from the data you insert. If we had numbers and dates they would have been mapped accordingly.

Analyzers

What You need to know is that the type “string” is analyzed before being inserted into the index. An analyzer manipulates the original text and spits it out in a new form. Records will be indexed and retrieved according to their tokenized form.

If no analyzer is explicitly set then Elasticsearch will use the default analyzer. If you’re curious you can see what your text will look like after analysis:

Using the standard analyzer, text is basically split into single words and lowercased. The first example analyzes the text out of the context of the index, using the explicitly named “standard” analyzer. The second one shows how text is analyzed in the context of the “examples” index and “title” field. Their results are exactly the same, confirming that the analysis process was identical.
Elasticsearch comes with many builtin analyzers. Some of the most useful ones are language analyzers that can stem words according to grammar rules. Try the third example in the above screenshot and see that “Saving” is stemmed to “save”.

image

Why is this useful? The first example of the next image looks for a title that contains the “saving” word. The record will be found.

image

The second example looks for a more generic “save”, but given the fact that the title field was analyzed with the standard analyzer, no record can be found.

If we want it to be found, then we need to update the mapping. Note that I’m going to add a new field “title.en” instead of changing the existing generic “title” field.

Changing existing fields requires reindexing (reinserting) all the data, while adding new fields doesn’t. You just need to update the existing records when necessary. Besides, if in the future we need to index German titles as well, we can just add a new “title.de” field and we will be able to index both English and German titles.

Let’s update the movies type mapping with a new field, “title.en”, that will be used for english titles:

image

Now we need to update the existing record so that we can search also within the “title.en” field:

image

You’re now ready to query the title.en field and get the expected result:

image

The second example uses the “multi_match” keyword to allow search within multiple fields. This comes very handy, as we now can query all title fields in one shot by adding an asterisk (“title*”), without the need to specify which language we’re interested in (if we added “title.de” it would have been searched as well, making our search cross language).

I hope this introduction to Elasticsearch mappings and analyzers was useful!

Funny bug fixing with ActiveRecord comparisons and Comparable

Today I was facing a very annoying bug in some old code that demonstrated to be quite hard to track down.

There’s a form where the user can set some dates, which have to be valid, meaning that each date must be greater than the previous one. Here’s a screenshot of the relevant part of the form:

image

Each date in the form is actually part of an Event ActiveRecord object. Each event belongs to a calendar, which is the actual record that gets saved when the form is submittted.

In order to validate the correct order of the dates the Comparable module was included into the Event model. Here it is some pseudocode, a simplified version of the original one:


class Event < ActiveRecord::Base
  include Comparable
  
  belongs_to :calendar

  def <=> other
    if calendar == other.calendar and calendar.present?
      date <=> other.date
    end
  end
  # ...
end

class Calendar
  has_many :events
  
  validate :validate_event_dates_sequence
  #....
end

The starship method (<=>) in the Event class is quite straightforward: it compares the records dates in order to decide which one is greater, given that they belong to the same calendar. Calendars have many events, and its the calendar responsibility to make sure all events have dates in the right order via the validate_event_dates_sequence method.

So, where’s the problem? When you edit an existing calendar and try to change the first date value, validations somehow fail and an extra date coming out of the blue is added to the form. We used to have 5 events, now we have 6:image

Why is that happening? I used pry in order to inspect the issue and I noticed that calendar.events contained an extra record, which had the same id of the first one:


calendar.events.map &:id
# => [16, 17, 18, 19, 20, 16]

For some reason ActiveRecord instantiated a new record instead of using the existing one. To confirm this oddity I did some more inspection. All records are saved in the database and that’s expected since I’m editing existing data:


events.map &:persisted?
=> [true, true, true, true, true, true]

The first record and the last one, while having the same id and referring to the same record in the database, are totally different objects for ruby:


events.first.object_id
=> 2162200580
events.last.object_id
=> 2214847000

Event dates are not ordered from smaller to greater (the last date, which should have replaced the first one, is in the wrong position as well):


[Thu, 06 Nov 2014 15:49:00 CET +01:00,
 Fri, 07 Nov 2014 15:49:00 CET +01:00,
 Sat, 08 Nov 2014 15:49:00 CET +01:00,
 Sun, 09 Nov 2014 15:49:00 CET +01:00,
 Mon, 10 Nov 2014 15:49:00 CET +01:00,
 Wed, 05 Nov 2014 17:49:00 CET +01:00]
 

Finally, let’s compare the first event to the last one, just to confirm that something is wrong:


 events.first <=> events.last
 # => 1
 

It was supposed to return 0, because they refer to the same record on the database.

So, the first problem is that the old implementation was too naive: it doesn’t check the records ID equality before comparing their dates. The first record has the same ID of the last one but its date is different, so the starship method <=> doesn’t return 0 as it should. Lack of complete rspec tests allowed for this bug to happen. 

But there’s more: even though ActiveRecord::Base doesn’t include the Comparable module, it definitely has the <=> method:


ActiveRecord::Base.methods.include? :<=>
# => true
ActiveRecord::Base.ancestors.include? Comparable
# => false

Here’s the default ActiveRecord implementation:


module ActiveRecord module Core def <=>(other_object) if other_object.is_a?(self.class) self.to_key <=> other_object.to_key else super end end end end

The main issues were:

  • The new implementation of the <=> method was not completely tested, leaving out one of the most important cases (record equality)
  • Comparable module was added but was useless
  • There’s always a good explaination behind odd behaviors, it’s just a matter of taking the time to find it out. 

By the way my final implementation was:


def <=> (other)
  unless super.zero? # they are not the same record
    if calendar == other.calendar and calendar.present?
      date <=> other.date
    end
  end
end

Analyze ruby execution stack with #caller

Sometimes you may need (or just want) to see the current execution stack of your ruby code for inspection: for this very purpose Ruby core library provides a method named caller. Its output is similar to the one of error backtraces, so you should be able to get very familiar with it quite soon.

caller provides some very useful debugging capability, is defined inside the Kernel module, which in turn is included into Object making it available anywhere. Let’s start with the simplest example, create a file and save it with the following content:

def a
  puts 'Your code execution stack:'
  puts caller(0)
end

def b
  a
end

b

When executed, the file output is:


$ ruby example.rb 

Your code execution stack:
example.rb:3:in `a'
example.rb:7:in `b'
example.rb:10:in `'

As you can see this is pretty informative, as it shows every step involved in the code execution, listing the filename, the method and the line number where execution passed to each next method. When you’re dealing with a complex application this can become a lifesaver. We’re seeing the result printed on the screen, but actually caller returns an array of strings (i.e. the lines).

What about that 0 param? It tells how many initial entries from the stack are to be omitted, it is optional and the default is 1. If you remove the zero then you will see that the first line example.rb:3:in `a' disappears from output.

caller accepts a second parameter, which represents how many lines are to be collected in the array. If you change the code to caller(0, 2) then the last line of the output will disappear, listing only the first 2 lines.

Now, let’s see a real use case when caller comes handy. I’ve created a simple rails app with the only purpose of listing all the rails source files involved in a simple request, plus the ability of showing the files content directly in the browser. The application can be downloaded here: read_rails.

Most of my code is in application_controller.rb, routes.rb and views/application/index.html.erb, you can look at those files if you’re interested in how it works, but most of the interesting code is in the erb view listed below:

<pre>
  <% caller(0).each_with_index do |line, i| %>
    <% filename, line_number = line.split(':') %>
    <%= i+1 %> <%= link_to line, "#{filename}#n#{line_number}", target: '_blank' %>
  <% end %>
</pre>

Bundle the gems, start the server and head to http://localhost:3000 to see the list of the files and methods involved in the request cycle. Each line, if clicked, will show the actual rails source code installed on your machine and used by the application.

image

What can we learn from this backtrace?

First, rails is a complex beast: about 110 different method calls (and about the same size in files) are involved to complete the request.

Second: we hit the whole rails MVC stack, plus routing, rack and webrick.

Third: this simple request hits only one single file inside the real app (the index view), the rest is library code. Of course, when you add database queries, assets and javascripts the count increases.

At this point, you may want to pry into rails guts and see what’s really happening at each point, so have fun!

Rails 4.2 new gems: active job and global id

A few days ago the new rails 4.2 beta release was announced and the usual bag of goodies is going to make our life as developers easier and more interesting.

The most relevant new feature is the Active Job framework and its integration with ActionMailer: rails has now its own queue interface and you can swap the queuing gem (resque, delayed job, sidekiq…) without changing your application code.
You can even send email asyncronously with the new deliver_later method, while if you want sync delivery you should use instead deliver_now because the old deliver method is now deprecated.
If you don’t add any queue gem to the Gemfile then the default rails system will be used, which means that everything will be sent immediately (no async functionality).

This brilliant feature depends on a couple of new gems: active job and global id. Active Job usage is well documented in the new official guide, so I will focus most of this article on Global Id.
If you want to follow along you can download a demo app from this repository, bundle the gems and start the server with rails s. The example is basically a rails 4.2 app with a regular ActiveRecord Friend scaffold and a NameCapitalizerJob job class.

How do the new gems interact exactly? Let’s enqueue a new job for the NameCapitalizerJob worker, which is located in the example application:

  NameCapitalizerJob.perform_later Friend.first
  => #<NameCapitalizerJob:0x007fb44acaff48 ...>

If you used rails queuing systems in the past you already know that it was necessary to pass your ActiveRecord objects to the worker in the form of their id and manually reload the record at job execution. This is no more required, so in the example above we’re simply passing the record itself, while in the job code the reload is automatic:

NameCapitalizerJob < ActiveJob::Base
  def perform(friend)
    name = friend.name.capitalize
    friend.update_attribute :name, name
  end
end

You can see the job has been correctly enqueued:

Delayed::Job.count
 => 1

with the following params:

YAML.load(Delayed::Job.first.handler).args
 => [NameCapitalizerJob, "4a33725b-35cf-4940-b1ca-d6fad84d410f", "gid://activejob-example/Friend/1"]

These params represent: the job class name, the job id, and the global id string representation for the Friend.first record.
The format of the global id string is gid://AppName/ModelClassName/record_id.
Given a gobal id, the worker will load the referenced record transparently. This is achieved by mixing into ActiveRecord::Base a module from the global id gem:

puts ActiveRecord::Base.ancestors
...
GlobalID::Identification
...

The GlobalID::Identification module defines only a couple of methods: #global_id, #signed_global_id and their aliases #gid and #sgid, where the first is the record’s globalid object, the second is the record’s signed(encrypted) version of the same object:

gid = Friend.first.gid
 => #<GlobalID:0x007fa9add041f8 ...>
gid.app
 => "activejob-example" 
gid.model_name
 => "Friend" 
gid.model_class
 => Friend(id: integer, name: string, email: string...)
gid.model_id
 => "1"   
gid.to_s
 => "gid://activejob-example/Friend/1"

sgid = Friend.first.sgid
 => #<SignedGlobalID:0x007fa9add15e58 ...>
sgid.to_s
 => "BAh7CEkiCGdpZAY6BkVUSSIlZ2lkOi8vYWN0aXZl..."

The most important thing here is the string representation of the gid, as it contains enough information to retrieve its original record:

GlobalID::Locator.locate "gid://activejob-example/Friend/1"
 => #<Friend id: 1, name: "John smith" ...>

The actual source code used for locating records is rather simple and self explanatory:

class ActiveRecordFinder
  def locate(gid)
    gid.model_class.find gid.model_id
  end
end  

Regarding the signed object, we can inspect the original data hidden into its string representation using the following code:

SignedGlobalID.verifier.verify sgid.to_s
 => {"gid"=>"gid://activejob-example/Friend/1", "purpose"=>"default", "expires_at"=>Mon, 29 Sep 2014 08:25:31 UTC +00:00}

That’s how the global id gem works inside rails. By the way, it’s rather easy to use it with your own classes. Let’s see how to do it.
Let’s start a new irb session with bundle exec irb from the app folder, and require it with require 'globalid'.
The needs an app namespace in order to generate ids, so we need to set it manually:

GlobalID.app = 'TestApp'

Now let’s build a PORO class that defines globalid required methods (::find and id) and includes the GlobalID::Identification module:

class Item
  include GlobalID::Identification
  
  @all = []
  def self.all; @all end
  
  def self.find(id)
    all.detect {|item| item.id.to_s == id.to_s }
  end
  
  def initialize
    self.class.all << self
  end
    
  def id
    object_id
  end
end

As you might guess, the ::find method retrieves an item from its id code, while the #id method is simply an identifier. It works like this:

  item = Item.new
   => #<Item:0x007fdb4b05da10>
  id = item.id
   => 70289916620040 
  Item.find(id)
   => #<Item:0x007fdb4b05da10>

Time to get the item global id:

  gid = item.gid
   => #<GlobalID:0x007fdb4b026358 ...>
  gid.app
   => "TestApp"
  gid.model_name
   => "Item"
  gid.model_id
   => "70289916620040"
  gid_uri = gid.to_s
   => "gid://TestApp/Item/70289916620040"

We can now retrieve the original Item object from the gid_uri:

found = GlobalID.locate gid_uri
 => #<Item:0x007fdb4b05da10>
found == item
 => true

That’s it! I encourage you to check out the global id gem code on github, it’s just a few files so it’s very readable.