Support for left outer join in Rails 5

This blog is part of our Rails 5 series.

Suppose in a blog application there are authors and posts. A post belongs to an author, while author has many posts.

The app needs to show a list of all the authors along with a number of posts that they have written.

For this, we need to join author and posts table with “left outer join”. More about “left outer join” here, here and here .

In Rails 4.x, we need to write the SQL for left outer join manually as Active Record does not have support for outer joins.

authors = Author.join('LEFT OUTER JOIN "posts" ON "posts"."author_id" = "authors"."id"')
                .uniq
                .select("authors.*, COUNT(posts.*) as posts_count")
                .group("authors.id")

Rails 5 has added left_outer_joins method.

authors = Author.left_outer_joins(:posts)
                .uniq
                .select("authors.*, COUNT(posts.*) as posts_count")
                .group("authors.id")

It also allows to perform the left join on multiple tables at the same time.

>> Author.left_joins :posts, :comments
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" LEFT OUTER JOIN "posts" ON "posts"."author_id" = "authors"."id" LEFT OUTER JOIN "comments" ON "comments"."author_id" = "authors"."id"

If you feel left_outer_joins is too long to type, then Rails 5 also has an alias method left_joins.

has_secure_token to generate unique random token in Rails 5

This blog is part of our Rails 5 series.

We sometimes need unique and random tokens in our web apps. Here is how we typically build it.

class User < ActiveRecord::Base

  before_create :set_access_token

  private

  def set_access_token
    self.access_token = generate_token
  end

  def generate_token
    loop do
      token = SecureRandom.hex(10)
      break token unless User.where(access_token: token).exists?
    end
  end
end

has_secure_token in Rails 5

Rails 5 has added has_secure_token method to generate a random alphanumeric token for a given column.

class User < ApplicationRecord
  has_secure_token
end

By default, Rails assumes that the attribute name is token. We can provide a different name as a parameter to has_secure_token if the attribute name is not token.

class User < ApplicationRecord
  has_secure_token :password_reset_token
end

The above code assumes that we already have password_reset_token attribute in our model.

>> user = User.new
>> user.save
=> true

>> user.password_reset_token
=> 'qjCbex522DfVEVd5ysUWppWQ'

The generated tokens are URL safe and are of fixed length strings.

Migration helper for generating token

We can also generate migration for token similar to other data types.

$ rails g migration add_auth_token_to_user auth_token:token
class AddAuthTokenToUser < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :auth_token, :string
    add_index :users, :auth_token, unique: true
  end
end

Notice that migration automatically adds index on the generated column with unique constraint.

We can also generate a model with the token attribute.

$ rails g model Product access_token:token
class CreateProducts < ActiveRecord::Migration[5.0]
  def change
    create_table :products do |t|
      t.string :access_token

      t.timestamps
    end
    add_index :products, :access_token, unique: true
  end
end

Model generator also adds has_secure_token method to the model.

class Product < ApplicationRecord
  has_secure_token :access_token
end

Regenerating tokens

Sometimes we need to regenerate the tokens based on some expiration criteria.

In order to do that, we can simply call regenerate_#{token_attribute_name} which would regenerate the token and save it to its respective attribute.

>> user = User.first
=> <User id: 11, name: 'John', email: 'john@example.com',
         token: "jRMcN645BQyDr67yHR3qjsJF",
         password_reset_token: "qjCbex522DfVEVd5ysUWppWQ">

>> user.password_reset_token
=> "qjCbex522DfVEVd5ysUWppWQ"

>> user.regenerate_password_reset_token
=> true

>> user.password_reset_token
=> "tYYVjnCEd1LAXvmLCyyQFzbm"

Beware of race condition

It is possible to generate a race condition in the database while generating the tokens. So it is advisable to add a unique index in the database to deal with this unlikely scenario.

Suppress save events in Rails 5

This blog is part of our Rails 5 series.

Rails 5 added suppress method which is used to prevent the receiver from being saved during the given block.

Use case for suppress method

Let’s say, we have an E-commerce application, which has many products. Whenever new product is launched then subscribed customers are notified about it.

class Product < ApplicationRecord
  has_many :notifications
  belongs_to :seller

  after_save :send_notification

  def launch!
    update_attributes!(launched: true)
  end

  private

  def send_notification
    notifications.create(message: 'New product Launched', seller: seller)
  end
end

class Notification < ApplicationRecord
  belongs_to :product
  belongs_to :seller

  after_create :send_notifications

  private

  def send_notifications
    # Sends notification about product to customers.
  end
end

class Seller < ApplicationRecord
  has_many :products
end

This creates a notification record every time we launch a product.

>> Notification.count
=> 0

>> seller = Seller.last
=> <Seller id: 6, name: "John">

>> product = seller.products.create(name: 'baseball hat')
=> <Product id: 4, name: "baseball hat", seller_id: 6>

>> product.launch!

>> Notification.count
=> 1

Now, we have a situation where we need to launch a product but we don’t want to send notifications about it.

Before Rails 5, this was possible only by adding more conditions.

ActiveRecord::Base.Suppress in Rails 5

In Rails 5, we can use ActiveRecord::Base.suppress method to suppress creating of notifications as shown below.

class Product < ApplicationRecord
  def launch_without_notifications
    Notification.suppress do
      launch!
    end
  end
end

>> Notification.count
=> 0

>> product = Product.create!(name: 'tennis hat')
=> <Event id: 1, name: "tennis hat">

>> product.launch_without_notifications

>> Notification.count
=> 0

As we can see, no new notifications were created when product is launched inside Notification.suppress block.

Checkout the pull request to gain better understanding of how suppress works.

Rails 5 makes rendering partial from cache substantially faster

This blog is part of our Rails 5 series.

Let’s have a look at Rails view code that renders partial using a collection.

# index.html.erb
<%= render partial: 'todo', collection: @todos %>

# _todo.html.erb
<% cache todo do %>
  <%= todo.name %>
<% end %>

In the above case Rails will do one fetch from the cache for each todo.

Fetch is usually pretty fast with any caching solution, however, one fetch per todo can make the app slow.

Gem multi_fetch_fragments fixed this issue by using read_multi api provided by Rails.

In a single call to cache, this gem fetches all the cache fragments for a collection. The author of the gem saw 78% speed improvement by using this gem.

The features of this gem have been folded into Rails 5.

To get benefits of collection caching, just add cached: true as shown below.

# index.html.erb
<%= render partial: 'todo', collection: @todos, cached: true %>

# _todo.html.erb
<% cache todo do %>
  <%= todo.name %>
<% end %>

With cached: true present, Rails will use read_multi to the cache store instead of reading from it every partial.

Rails will also log cache hits in the logs as below.

  Rendered collection of todos/_todo.html.erb [100 / 100 cache hits] (339.5ms)

Checkout the pull request to gain better understanding about how collection caching works.

Rails 5 switches from strong etags to weak etags

This blog is part of our Rails 5 series.

ETag, short for entity tag, is a part of HTTP header and is used for web cache validation. ETag is a digest of the resource that uniquely identifies specific version of the resource. This helps browser and web servers determine if resource in the browser’s cache is exactly same as the resource on the server.

Strong v/s Weak ETags

ETag supports strong and weak validation of the resource.

Strong ETag indicates that resource content is same for response body and the response headers.

Weak ETag indicates that the two representations are semantically equivalent. It compares only the response body.

Weak ETags are prefixed with W\ and thus one can easily distinguish between Weak ETags and Strong ETags.

"543b39c23d8d34c232b457297d38ad99"    – Strong ETag
W/"543b39c23d8d34c232b457297d38ad99"  – Weak ETag

W3 has an example page to illustrate how ETag matching works.

When server receives a request, it returns an ETag header as part of HTTP response. This ETag represents state of the resource. For the subsequent HTTP requests, client sends this ETag via If-None-Match header to identify if the resource is changed or not. The server will compare the current ETag and the one sent by the client. If ETag matches, server responds with 304 Not modified. This means resource content in the client’s cache is up-to-date. If resource is changed, server will send updated resource along with the new ETag.

Let’s see it in action.

ETags in Rails 4.x

Rails 4.x generates strong ETags by default i.e without W/ prefix.

class ItemsController < ApplicationController
  def show
    @item = Item.find(params[:id])
    fresh_when @item
  end
end

We are making first request to the server.

$ curl -i http://localhost:3000/items/1

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Etag: "618bbc92e2d35ea1945008b42799b0e7"
Last-Modified: Sat, 30 Jan 2016 08:02:12 GMT
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 98359119-14ae-4e4e-8174-708abbc3fd4b
X-Runtime: 0.412232
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13)
Date: Fri, 04 Mar 2016 10:50:38 GMT
Content-Length: 1014
Connection: Keep-Alive

For the next request, we will send ETag that was sent by the sever. And notice that server returns 304 Not Modified.

$ curl -i -H 'If-None-Match: "618bbc92e2d35ea1945008b42799b0e7"' http://localhost:3000/items/1

HTTP/1.1 304 Not Modified
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Etag: "618bbc92e2d35ea1945008b42799b0e7"
Last-Modified: Sat, 30 Jan 2016 08:02:12 GMT
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: e4447f82-b96c-4482-a5ff-4f5003910c18
X-Runtime: 0.012878
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13)
Date: Fri, 04 Mar 2016 10:51:22 GMT
Connection: Keep-Alive

Rails 5 sets Weak ETags by default

In Rails 5, all ETags generated by Rails will be weak by default.

$ curl -i http://localhost:3000/items/1

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Etag: W/"b749c4dd1b20885128f9d9a1a8ba70b6"
Last-Modified: Sat, 05 Mar 2016 00:00:00 GMT
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: a24b986c-74f0-4e23-9b1d-0b52cb3ef906
X-Runtime: 0.038372
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Date: Fri, 04 Mar 2016 10:48:35 GMT
Content-Length: 1906
Connection: Keep-Alive

Now for the second request, server will return 304 Not Modified response as before, but the ETag is weak ETag.

$ curl -i -H 'If-None-Match: W/"b749c4dd1b20885128f9d9a1a8ba70b6"' http://localhost:3000/items/1

HTTP/1.1 304 Not Modified
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Etag: W/"b749c4dd1b20885128f9d9a1a8ba70b6"
Last-Modified: Sat, 05 Mar 2016 00:00:00 GMT
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 7fc8a8b9-c7ff-4600-bf9b-c847201973cc
X-Runtime: 0.005469
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Date: Fri, 04 Mar 2016 10:49:27 GMT
Connection: Keep-Alive

Why this change?

Rails does not perform strong validation of ETags as implied by strong ETags spec. Rails just checks whether the incoming ETag from the request headers matches with the ETag of the generated response. It does not do byte by byte comparison of the response.

This was true even before Rails 5. So this change is more of a course correction. Rack also generates weak ETags by default because of similar reasons.

Mark Notthingham is chair of HTTP Working Group and he has written about etags which has some useful links to other ETag resources.

How to use strong ETags in Rails 5

If we want to bypass default Rails 5 behavior to use strong ETags then we can do by following way.

class ItemsController < ApplicationController
  def show
    @item = Item.find(params[:id])
    fresh_when strong_etag: @item
  end
end

This will generate strong Etag i.e without W/ prefix.

Parameter filtering enhancement in Rails 5

This blog is part of our Rails 5 series.

For security reasons, we do not want sensitive data like passwords, credit card information, auth keys etc to appear in log files.

Rails makes it very easy to filter such data. Just add following line in application.rb to filter sensitive information.

config.filter_parameters += [:password]

Now the log file will show [FILTERED] instead of real password value.

This replacement of password with [FILTERED] is done recursively.

{user_name: "john", password: "123"}
{user: {name: "john", password: "123"}}
{user: {auth: {id: "john", password: "123"}}}

In all the above cases, “123” would be replaced by “[FILTERED]”.

Now think of a situation where we do not want to filter all the occurrence of a key. Here is an example.

{credit_card: {number: "123456789", code: "999"}}
{user_preference: {color: {name: "Grey", code: "999999"}}}

We definitely want to filter [:credit_card][:code] but we want [:color][:code] to show up in the log file.

This can be achieved in Rails 5.

The application.rb changes from

config.filter_parameters += ["code"]

to

config.filter_parameters += ["credit_card.code"]

In this case so long as parent of code is credit_card Rails will filter the data.

Rails 5 adds http_cache_forever to allow caching of response forever

This blog is part of our Rails 5 series.

Rails 5 allows to cache HTTP responses forever by introducing http_cache_forever method.

Sometimes, we have static pages that never/rarely change.

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    render
  end
end

# app/views/home/index.html.erb
<h1>Welcome</h1>

Let’s see log for the above action.

Processing by HomeController#index as HTML
  Rendered home/index.html.erb within layouts/application (1.3ms)
Completed 200 OK in 224ms (Views: 212.4ms | ActiveRecord: 0.0ms)

 And so on for every request for this action.

There is no change in the response and still we are rendering same thing again and again and again.

Rails 5 introduces http_cache_forever

When response does not change then we want browsers and proxies to cache it for a long time.

Method http_cache_forever allows us to set response headers to tell browsers and proxies that response has not modified.

# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    http_cache_forever(public: true) {}
  end
end

# OR
class HomeController < ApplicationController
  def index
    http_cache_forever(public: true) do
      render
    end
  end
end


# app/views/home/index.html.erb
<h1>Welcome</h1>

Now let’s look at the log for the modified code.

# When request is made for the first time.

Processing by HomeController#index as HTML
  Rendered home/index.html.erb within layouts/application (1.3ms)
Completed 200 OK in 224ms (Views: 212.4ms | ActiveRecord: 0.0ms)

# For consecutive requests for the same page

Processing by HomeController#index as HTML
Completed 304 Not Modified in 2ms (ActiveRecord: 0.0ms)

On first hit, we serve the request normally but, then on each subsequent request cache is revalidated and a “304 Not Modified” response is sent to the browser.

Options with http_cache_forever

By default, HTTP responses are cached only on the user’s web browser. To allow proxies to cache the response, we can set public to true to indicate that they can serve the cached response.

Use http_cache_forever with caution

By using this method, Cache-Control: max-age=3155760000 is set as response header and browser/proxy won’t revalidate the resource back to the server unless force reload is done.

In case force reload is done, Cache-Control: max-age=0 is set as request header.

In this case, browser will receive the changed resource whether ETag is changed or not.

http_cache_forever is literally going to set the headers to cache it for 100 years and developers would have to take extra steps to revalidate it. So, this should be used with extra care.

Better exception responses in Rails 5 API apps

This blog is part of our Rails 5 series.

Rails 4.x returns error information in HTML page whenever there is any exception, in the development environment.

This is fine for normal HTML requests. But traditionally, Rails always returned with HTML response for exceptions for all requests, including JSON on XML requests in development.

We can now generate API only apps in Rails 5. In case of such apps, it’s better to have the error message in the format in which request was made. Having an HTML response for a JSON endpoint like http://localhost:3000/posts.json is not going to help in debugging why the exception happened.

New config option debug_exception_response_format

Rails 5 has introduced new configuration to respond with proper format for exceptions.

# config/environments/development.rb
config.debug_exception_response_format = :api

Let’s see an example of the response received with this configuration.

$ curl localhost:3000/posts.json
{"status":404,"error":"Not Found","exception":"#\u003cActionController::RoutingError: No route matches [GET] \"/posts.json\"\u003e","traces":{"Application Trace":[...],"Framework Trace":[...]}}

The status key will represent HTTP status code and error key will represent the corresponding Rack HTTP status.

exception will print the output of actual exception in inspect format.

traces will contain application and framework traces similar to how they are displayed in HTML error page.

By default, config.debug_exception_response_format is set to :api so as to render responses in the same format as requests.

If you want the original behavior of rendering HTML pages, you can configure this option as follows.

# config/environments/development.rb
config.debug_exception_response_format = :default

Use file_fixture to access test files in Rails 5

This blog is part of our Rails 5 series.

While writing tests sometimes we need to read files to compare the output. For example a test might want to compare the API response with pre determined data stored in a file.

Here is an example.

# In test helper
def file_data(name)
  File.read(Rails.root.to_s + "/tests/support/files/#{name}")
end

# In test
class PostsControllerTest < ActionDispatch::IntegrationTest
  setup do
    @post = posts(:one)
  end

  test "should get index" do
    get posts_url, format: :json

    assert_equal file_data('posts.json'), response.body
  end
end

File Fixtures in Rails 5

In Rails 5, we can now organize such test files as fixtures.

Newly generated Rails 5 applications, will have directory test/fixtures/files to store such test files.

These test files can be accessed using file_fixture helper method in tests.

require 'test_helper'

class PostsControllerTest < ActionDispatch::IntegrationTest
  setup do
    @post = posts(:one)
  end

  test "should get index" do
    get posts_url, format: :json

    assert_equal response.body, file_fixture('posts.json').read
  end
end

The file_fixture method returns Pathname object, so it’s easy to extract file specific information.

file_fixture('posts.json').read
file_fixture('song.mp3').size

Migrations are versioned in Rails 5

This blog is part of our Rails 5 series.

We will see how migrations in Rails 5 differ by looking at different cases.

Case I

In Rails 4.x command

rails g model User name:string

will generate migration as shown below.

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps null: false
    end
  end
end

In Rails 5 the same command will generate following migration.

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.timestamps
    end
  end
end

Let’s see the generated schema after running migration generated in Rails 5.

sqlite> .schema users
CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
sqlite>

Rails 5 added the NOT NULL constraints on the timestamps columns even though not null constraint was not specified in the migration.

Case II

Let’s look at another example.

In Rails 4.x command

rails g model Task user:references

would generate following migration.

class CreateTasks < ActiveRecord::Migration
  def change
    create_table :tasks do |t|
      t.references :user, index: true, foreign_key: true
      t.timestamps null: false
    end
  end
end

In Rails 5.0, same command will generate following migration.

class CreateTasks < ActiveRecord::Migration[5.0]
  def change
    create_table :tasks do |t|
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

There is no mention of index: true in the above migration. Let’s see the generated schema after running Rails 5 migration.

sqlite> .schema tasks
CREATE TABLE "tasks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);

CREATE INDEX "index_tasks_on_user_id" ON "tasks" ("user_id");

As you can see, an index on user_id column is added even though it’s not present in the migration.

Migration API has changed in Rails 5

Rails 5 has changed migration API because of which even though null: false options is not passed to timestamps when migrations are run then not null is automatically added for timestamps.

Similarly, we want indexes for referenced columns in almost all cases. So Rails 5 does not need references to have index: true. When migrations are run then index is automatically created.

Now let’s assume that an app was created in Rails 4.x. It has a bunch of migrations. Later the app was upgraded to Rails 5. Now when older migrations are run then those migrations will behave differently and will create a different schema file. This is a problem.

Solution is versioned migrations.

Versioned migrations in Rails 5

Let’s look at the migration generated in Rails 5 closely.

class CreateTasks < ActiveRecord::Migration[5.0]
  def change
    create_table :tasks do |t|
      t.references :user, index: true, foreign_key: true
      t.timestamps null: false
    end
  end
end

In this case CreateUsers class is now inheriting from ActiveRecord::Migration[5.0] instead of ActiveRecord::Migration.

Here [5.0] is Rails version that generated this migration.

Solving the issue with older migrations

Whenever Rails 5 runs migrations, it checks the class of the current migration file being run. If it’s 5.0, it uses the new migration API which has changes like automatically adding null: false to timestamps.

But whenever the class of migration file is other than ActiveRecord::Migration[5.0], Rails will use a compatibility layer of migrations API. Currently this compatibility layer is present for Rails 4.2. What it means is that all migration generated prior to usage of Rails 5 will be treated as if they were generate in Rails 4.2.

You will also see a deprecation warning asking user to add the version of the migration to the class name for older migrations.

So if you are migrating a Rails 4.2 app, all of your migrations will have class ActiveRecord::Migration. If you run those migrations in Rails 5, you will see a warning asking to add version name to the class name so that class name looks like ActiveRecord::Migration[4.2].

Rails 5 improves redirect_to :back with new redirect_back method

This blog is part of our Rails 5 series.

In Rails 4.x, for going back to previous page we use redirect_to :back.

However sometimes we get ActionController::RedirectBackError exception when HTTP_REFERER is not present.

class PostsController < ApplicationController
  def publish
    post = Post.find params[:id]
    post.publish!

    redirect_to :back
  end
end

This works well when HTTP_REFERER is present and it redirects to previous page.

Issue comes up when HTTP_REFERER is not present and which in turn throws exception.

To avoid this exception we can use rescue and redirect to root url.

class PostsController < ApplicationController
  rescue_from ActionController::RedirectBackError, with: :redirect_to_default

  def publish
    post = Post.find params[:id]
    post.publish!
    redirect_to :back
  end

  private

  def redirect_to_default
    redirect_to root_path
  end
end

Improvement in Rails 5

In Rails 5, redirect_to :back has been deprecated and instead a new method has been added called redirect_back.

To deal with the situation when HTTP_REFERER is not present, it takes required option fallback_location.

class PostsController < ApplicationController

  def publish
    post = Post.find params[:id]
    post.publish!

    redirect_back(fallback_location: root_path)
  end
end

This redirects to HTTP_REFERER when it is present and when HTTP_REFERER is not present then redirects to whatever is passed as fallback_location.

Rails 5 allows configuring queue name for mailers

This blog is part of our Rails 5 series.

In Rails 4.2, Active Job was integrated with Action Mailer to send emails asynchronously.

Rails provides deliver_later method to enqueue mailer jobs.

class UserMailer < ApplicationMailer
  def send_notification(user)
    @user = user
    mail(to: user.email)
  end
end

> UserMailer.send_notification(user).deliver_later
=> <ActionMailer::DeliveryJob:0x007ff602dd3128 @arguments=["UserMailer", "send_notification", "deliver_now", <User id: 1, name: "John", email: "john@bigbinary.com">], @job_id="d0171da0-86d3-49f4-ba03-37b37d4e8e2b", @queue_name="mailers", @priority=nil>

Note that the task of delivering email was put in queue called mailers.

In Rails 4.x, all background jobs are given queue named “default” except for mailers. All outgoing mails are given the queue named “mailers” and we do not have the option of changing this queue name from “mailers” to anything else.

Since Rails 4.x comes with minimum of two queues it makes difficult to use queuing services like que which relies on applications having only one queue.

Customizing queue name in Rails 5

In Rails 5, we can now change queue name for mailer jobs using following configuration.

config.action_mailer.deliver_later_queue_name = 'default'

class UserMailer < ApplicationMailer
  def send_notification(user)
    @user = user
    mail(to: user.email)
  end
end

2.2.2 :003 > user = User.last
=> <User id: 6, name: "John", email: "john@bigbinary.com">

2.2.2 :004 > UserMailer.send_notification(user).deliver_later
=> <ActionMailer::DeliveryJob:0x007fea2182b2d0 @arguments=["UserMailer", "send_notification", "deliver_now", <User id: 1, name: "John", email: "john@bigbinary.com">], @job_id="316b00b2-64c8-4a2d-8153-4ce7abafb28d", @queue_name="default", @priority=nil>

Rails 5 handles DateTime with better precision

This blog is part of our Rails 5 series.

MySQL 5.6.4 and up has added fractional seconds support for TIME, DATETIME, and TIMESTAMP values, with up to microseconds (6 digits) precision.

Adding precision to migration

To add precision on datetime column we need to add limit option to it. By default it is set to 0.

def change
  add_column :users, :last_seen_at, :datetime, limit: 6
end

This adds precision(6) to last_seen_at column in users table.

Rails 4.x behavior

Let’s look at the some of the examples with different precision values.

The task here is to set end_of_day value to updated_at column.

With precision set to 6

user = User.first
user.updated_at
=> Mon, 18 Jan 2016 10:13:10 UTC +00:00

user.updated_at = user.updated_at.end_of_day
=> Mon, 18 Jan 2016 23:59:59 UTC +00:00

user.save
'UPDATE `users` SET `updated_at` = '2016-01-18 23:59:59.999999' WHERE `users`.`id` = 1'

user.updated_at
=> Mon, 18 Jan 2016 23:59:59 UTC +00:00

user.reload
user.updated_at
=> Mon, 18 Jan 2016 23:59:59 UTC +00:00

Everything looks good here.

But let’s look at what happens when precision is set to 0.

With precision set to 0

user = User.first
user.updated_at
=> Mon, 18 Jan 2016 10:13:10 UTC +00:00

user.updated_at = user.updated_at.end_of_day
=> Mon, 18 Jan 2016 23:59:59 UTC +00:00

user.save
'UPDATE `users` SET `updated_at` = '2016-01-18 23:59:59.999999' WHERE `users`.`id` = 1'

user.updated_at
=> Mon, 18 Jan 2016 23:59:59 UTC +00:00

So far everything looks good here too. Now let’s see what happens when we reload this object.

user.reload
user.updated_at
=> Tue, 19 Jan 2016 00:00:00 UTC +00:00

As we can clearly see after the reload updated_at value has been rounded off from 2016-01-18 23:59:59.999999 to 2016-01-19 00:00:00. It might seem like a small issue but notice that date has changed from 01/18 to 01/19 because of this rounding.

Improvement in Rails 5

Rails team fixed this issue by removing fractional part if mysql adapter does not support precision.

Here are the two relevant commits to this change.

With precision set to 0

user.updated_at
=> Tue, 19 Jan 2016 00:00:00 UTC +00:00

user.updated_at = user.updated_at.tomorrow.beginning_of_day - 1
=> Tue, 19 Jan 2016 23:59:59 UTC +00:00

user.save
'UPDATE `users` SET `updated_at` = '2016-01-19 23:59:59' WHERE `users`.`id` = 1'

user.reload

user.updated_at
=> Tue, 19 Jan 2016 23:59:59 UTC +00:00

If precision is not set then fractional part gets stripped and date is not changed.

Active Support Improvements in Rails 5

This blog is part of our Rails 5 series.

Rails 5 has added some nice enhancements to Active Support. This blog will go over some of those changes.

Improvements in Date, Time and Datetime

prev_day and next_day

As the name of the methods suggests, next_day returns next calendar date.

Similarly, prev_day returns previous calendar date.

Time.current
=> Fri, 12 Feb 2016 08:53:31 UTC +00:00

Time.current.next_day
=> Sat, 13 Feb 2016 08:53:31 UTC +00:00

Time.current.prev_day
=> Thu, 11 Feb 2016 08:53:31 UTC +00:00

Support for same_time option to next_week and prev_week

In Rails 4.x next_week returns beginning of next week and prev_week returns beginning of previous week.

In Rails 4.x these two methods also accept week day as a parameter.

Time.current
=> Fri, 12 Feb 2016 08:53:31 UTC +00:00

Time.current.next_week
=> Mon, 15 Feb 2016 00:00:00 UTC +00:00

Time.current.next_week(:tuesday)
=> Tue, 16 Feb 2016 00:00:00 UTC +00:00

Time.current.prev_week(:tuesday)
=> Tue, 02 Feb 2016 00:00:00 UTC +00:00

By using week day as parameter we can get the date one week from now but the returned date is still the beginning of that date. How do we get one week from the current time.

Rails 5 add an additional option same_time: true to solve this problem.

Using this option, we can now get next week date from the current time.

Time.current
=> Fri, 12 Feb 2016 09:15:10 UTC +00:00

Time.current.next_week
=> Mon, 15 Feb 2016 00:00:00 UTC +00:00

Time.current.next_week(same_time: true)
=> Mon, 15 Feb 2016 09:15:20 UTC +00:00

Time.current.prev_week
=> Mon, 01 Feb 2016 00:00:00 UTC +00:00

Time.current.prev_week(same_time: true)
=> Mon, 01 Feb 2016 09:16:50 UTC +00:00

on_weekend?

This method returns true if the receiving date/time is a Saturday or Sunday.

Time.current
=> Fri, 12 Feb 2016 09:47:40 UTC +00:00

Time.current.on_weekend?
=> false

Time.current.tomorrow
=> Sat, 13 Feb 2016 09:48:47 UTC +00:00

Time.current.tomorrow.on_weekend?
=> true

on_weekday?

This method returns true if the receiving date/time is not a Saturday or Sunday.

Time.current
=> Fri, 12 Feb 2016 09:47:40 UTC +00:00

Time.current.on_weekday?
=> true

Time.current.tomorrow
=> Sat, 13 Feb 2016 09:48:47 UTC +00:00

Time.current.tomorrow.on_weekday?
=> false

next_weekday and prev_weekday

next_weekday returns next day that is not a weekend.

Similarly, prev_weekday returns last day that is not a weekend.

Time.current
=> Fri, 12 Feb 2016 09:47:40 UTC +00:00

Time.current.next_weekday
=> Mon, 15 Feb 2016 09:55:14 UTC +00:00

Time.current.prev_weekday
=> Thu, 11 Feb 2016 09:55:33 UTC +00:00

Time.days_in_year

# Gives number of days in current year, if year is not passed.
Time.days_in_year
=> 366

# Gives number of days in specified year, if year is passed.
Time.days_in_year(2015)
=> 365

Improvements in Enumerable

pluck

pluck method is now added to Enumerable objects.

users = [{id: 1, name: 'Max'}, {id: 2, name: 'Mark'}, {id: 3, name: 'George'}]

users.pluck(:name)
=> ["Max", "Mark", "George"]

# Takes multiple arguments as well
users.pluck(:id, :name)
=> [[1, "Max"], [2, "Mark"], [3, "George"]]

one great improvement in ActiveRecord due to this method addition is that when relation is already loaded then instead of firing query with pluck, it uses Enumerable#pluck to get data.

# In Rails 4.x
users = User.all
SELECT `users`.* FROM `users`

users.pluck(:id, :name)
SELECT "users"."id", "users"."name" FROM "users"

=> [[2, "Max"], [3, "Mark"], [4, "George"]]


# In Rails 5
users = User.all
SELECT "users".* FROM "users"

# does not fire any query
users.pluck(:id, :name)
=> [[1, "Max"], [2, "Mark"], [3, "George"]]

without

This method returns a copy of enumerable without the elements passed to the method.

vehicles = ['Car', 'Bike', 'Truck', 'Bus']

vehicles.without("Car", "Bike")
=> ["Truck", "Bus"]

vehicles = {car: 'Hyundai', bike: 'Honda', bus: 'Mercedes', truck: 'Tata'}

vehicles.without(:bike, :bus)
=> {:car=>"Hyundai", :truck=>"Tata"}

Array#second_to_last and Array#third_to_last

['a', 'b', 'c', 'd', 'e'].second_to_last
=> "d"

['a', 'b', 'c', 'd', 'e'].third_to_last
=> "c"

PR for these methods can be found here.

Integer#positive? and Integer#negative?

positive? returns true if integer is positive.

negative? returns true if integer is negative.

4.positive?
=> true

4.negative?
=> false

-4.0.positive?
=> false

-4.0.negative?
=> true

Commit for these methods can be found here.

These changes have now been added to Ruby 2.3 also.

Array#inquiry

Rails team has added ArrayInquirer to ActiveSupport which gives a friendlier way to check its contents.

Array#inquiry is a shortcut for wrapping the receiving array in an ArrayInquirer

users = [:mark, :max, :david]

array_inquirer1 = ActiveSupport::ArrayInquirer.new(users)

# creates ArrayInquirer object which is same as array_inquirer1 above
array_inquirer2 = users.inquiry

array_inquirer2.class
=> ActiveSupport::ArrayInquirer

# provides methods like:

array_inquirer2.mark?
=> true

array_inquirer2.john?
=> false

array_inquirer2.any?(:john, :mark)
=> true

array_inquirer2.any?(:mark, :david)
=> true

array_inquirer2.any?(:john, :louis)
=> false

Rails 5 improves searching for routes with advanced options

This blog is part of our Rails 5 series.

rails routes shows all the routes in the application.

$ rake routes

Prefix       Verb   URI Pattern                   Controller#Action
wishlist_user GET    /users/:id/wishlist(.:format) users#wishlist
        users GET    /users(.:format)              users#index
              POST   /users(.:format)              users#create
     new_user GET    /users/new(.:format)          users#new
    edit_user GET    /users/:id/edit(.:format)     users#edit
         user GET    /users/:id(.:format)          users#show
              PATCH  /users/:id(.:format)          users#update
              PUT    /users/:id(.:format)          users#update
              DELETE /users/:id(.:format)          users#destroy
     products GET    /products(.:format)           products#index
              POST   /products(.:format)           products#create
and so on ......

This list can be lengthy and it could be difficult to locate exactly what user is looking for.

Ways to search specific routes prior to Rails 5

To see only specific routes we can use commands like grep.

$ rake routes | grep products
Prefix       Verb   URI Pattern                   Controller#Action
products      GET    /products(.:format)           products#index
              POST   /products(.:format)           products#create

Options with Rails 5

Rails 5 has added options in rails routes to perform pattern matching on routes.

Use option -c to search for routes related to controller. Also remember that Rails does case insensitive search. So rails routes -c users is same as rails routes -c Users.

# Search for Controller name
$ rails routes -c users
       Prefix Verb   URI Pattern                   Controller#Action
wishlist_user GET    /users/:id/wishlist(.:format) users#wishlist
        users GET    /users(.:format)              users#index
              POST   /users(.:format)              users#create

# Search for namespaced Controller name.
$ rails routes -c admin/users
         Prefix Verb   URI Pattern                     Controller#Action
    admin_users GET    /admin/users(.:format)          admin/users#index
                POST   /admin/users(.:format)          admin/users#create

# Search for namespaced Controller name.
$ rails routes -c Admin::UsersController
         Prefix Verb   URI Pattern                     Controller#Action
    admin_users GET    /admin/users(.:format)          admin/users#index
                POST   /admin/users(.:format)          admin/users#create

Use -g option to do general purpose pattern matching. This results in any routes that partially matches Prefix, Controller#Action or the URI pattern.

# Search with pattern
$ rails routes -g wishlist
       Prefix Verb URI Pattern                   Controller#Action
wishlist_user GET  /users/:id/wishlist(.:format) users#wishlist

# Search with HTTP Verb
$ rails routes -g POST
    Prefix Verb URI Pattern            Controller#Action
           POST /users(.:format)       users#create
           POST /admin/users(.:format) admin/users#create
           POST /products(.:format)    products#create

# Search with URI pattern
$ rails routes -g admin
       Prefix Verb   URI Pattern                     Controller#Action
  admin_users GET    /admin/users(.:format)          admin/users#index
              POST   /admin/users(.:format)          admin/users#create

Note that using CONTROLLER=some_controller has now been deprecated. This had the same effect as searching for a controller specific route.

Rails 5 makes belongs_to association required by default

This blog is part of our Rails 5 series.

In Rails 5, whenever we define a belongs_to association, it is required to have the associated record present by default after this change.

It triggers validation error if associated record is not present.

class User < ApplicationRecord
end

class Post < ApplicationRecord
  belongs_to :user
end

post = Post.create(title: 'Hi')
=> <Post id: nil, title: "Hi", user_id: nil, created_at: nil, updated_at: nil>

post.errors.full_messages.to_sentence
=> "User must exist"

As we can see, we can’t create any post record without having an associated user record.

How to achieve this behavior before Rails 5

In Rails 4.x world To add validation on belongs_to association, we need to add option required: true .

class User < ApplicationRecord
end

class Post < ApplicationRecord
  belongs_to :user, required: true
end

post = Post.create(title: 'Hi')
=> <Post id: nil, title: "Hi", user_id: nil, created_at: nil, updated_at: nil>

post.errors.full_messages.to_sentence
=> "User must exist"

By default, required option is set to false.

Opting out of this default behavior in Rails 5

We can pass optional: true to the belongs_to association which would remove this validation check.

class Post < ApplicationRecord
  belongs_to :user, optional: true
end

post = Post.create(title: 'Hi')
=> <Post id: 2, title: "Hi", user_id: nil>

But, what if we do not need this behavior anywhere in our entire application and not just a single model?

Opting out of this default behavior for the entire application

New Rails 5 application comes with an initializer named new_framework_defaults.rb.

When upgrading from older version of Rails to Rails 5, we can add this initializer by running bin/rails app:update task.

This initializer has config named Rails.application.config.active_record.belongs_to_required_by_default = true

For new Rails 5 application the value is set to true but for old applications, this is set to false by default.

We can turn off this behavior by keeping the value to false.

Rails.application.config.active_record.belongs_to_required_by_default = false

class Post < ApplicationRecord
  belongs_to :user
end

post = Post.create(title: 'Hi')
=> <Post id: 3, title: "Hi", user_id: nil, created_at: "2016-02-11 12:36:05", updated_at: "2016-02-11 12:36:05">

Rails 5 does not halt callback chain when false is returned

This blog is part of our Rails 5 series.

Before Rails 5, returning false from any before_ callback in ActiveModel or ActiveModel::Validations, ActiveRecord and ActiveSupport resulted in halting of callback chain.

class Order < ActiveRecord::Base

  before_save :set_eligibility_for_rebate
  before_save :ensure_credit_card_is_on_file

  def set_eligibility_for_rebate
    self.eligibility_for_rebate ||= false
  end

  def ensure_credit_card_is_on_file
    puts "check if credit card is on file"
  end
end

Order.create!
=> ActiveRecord::RecordNotSaved: ActiveRecord::RecordNotSaved

In this case the code is attempting to set the value of eligibility_for_rebate to false. However the side effect of the way Rails callbacks work is that the callback chain will be halted simply because one of the callbacks returned false.

Right now, to fix this we need to return true from before_ callbacks, so that callbacks are not halted.

Improvements in Rails 5

Rails 5 fixed this issue by adding throw(:abort) to explicitly halt callbacks.

Now, if any before_ callback returns false then callback chain is not halted.

class Order < ActiveRecord::Base

  before_save :set_eligibility_for_rebate
  before_save :ensure_credit_card_is_on_file

  def set_eligibility_for_rebate
    self.eligibility_for_rebate ||= false
  end

  def ensure_credit_card_is_on_file
    puts "check if credit card is on file"
  end

end

Order.create!
=> check if credit card is on file
=> <Order id: 4, eligibility_for_rebate: false>

To explicitly halt the callback chain, we need to use throw(:abort).

class Order < ActiveRecord::Base

  before_save :set_eligibility_for_rebate
  before_save :ensure_credit_card_is_on_file

  def set_eligibility_for_rebate
    self.eligibility_for_rebate ||= false
    throw(:abort)
  end

  def ensure_credit_card_is_on_file
    puts "check if credit card is on file"
  end

end

Order.create!
=> ActiveRecord::RecordNotSaved: Failed to save the record

Opting out of this behavior

The new Rails 5 application comes up with initializer named callback_terminator.rb.

ActiveSupport.halt_callback_chains_on_return_false = false

By default the value is to set to false.

We can turn off this default behavior by changing this configuration to true. However then Rails shows deprecation warning when false is returned from callback.

ActiveSupport.halt_callback_chains_on_return_false = true

class Order < ApplicationRecord
  before_save :set_eligibility_for_rebate
  before_save :ensure_credit_card_is_on_file

  def set_eligibility_for_rebate
    self.eligibility_for_rebate ||= false
  end

  def ensure_credit_card_is_on_file
    puts "check if credit card is on file"
  end
end

=> DEPRECATION WARNING: Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails. To explicitly halt the callback chain, please use `throw :abort` instead.
ActiveRecord::RecordNotSaved: Failed to save the record

How older applications will work with this change?

The initializer configuration will be present only in newly generated Rails 5 apps.

If you are upgrading from an older version of Rails, you can add this initializer yourself to enable this change for entire application.

This is a welcome change in Rails 5 which will help prevent accidental halting of the callbacks.

Configuring bundler using bundle config

Bundler helps in managing gem dependencies of ruby projects. You can specify which gems and versions you need, bundler will install them and load them at runtime. Bundler ensures that gems you need are present in the environment you need.

Bundle configurations

Bundler gets its configurations from local application (app/.bundle/config), environment variables and user’s home directory (~/.bundle/config) in the order of priority.

To list all bundler configurations for the current bundle, run bundle config without any parameters. You will also get the location where the value is set.

$ bundle config
Settings are listed in order of priority. The top value will be used.

You might see different result based on configuration of bundler on your machine.

To get value for the specific configuration setting, run bundle config with name.

$ bundle config disable_multisource
Settings for `disable_multisource` in order of priority. The top value will be used
You have not configured a value for `disable_multistore`

Setting Configuration

To set the value of the configuration setting, use bundle config with name and value. Configuration will be stored in ~/.bundle/config.

$ bundle config build.pg --with-pg-config=/opt/local/lib/postgresql91/bin/pg_config

$ bundle config
Settings are listed in order of priority. The top value will be used.
build.pg
Set for the current user (/Users/username/.bundle/config): "--with-pg-config=/opt/local/lib/postgresql91/bin/pg_config"

If any config already has a value, it will be overwritten directly and user will be warned.

$ bundle config build.pg --with-pg-config=/usr/pgsql-9.1/bin/pg_config
You are replacing the current global value of build.pg, which is currently "--with-pg-config=/opt/local/lib/postgresql91/bin/pg_config"

Application level configuration

By default setting configuration value will set it for all projects on the machine. You can set configurations specific to local application with --local option. This will store value in app/.bundle/config.

$ bundle config --local auto_install false
You are replacing the current local value of auto_install, which is currently nil

$ bundle config auto_install
Settings for `auto_install` in order of priority. The top value will be used
Set for your local app (/Users/username/Documents/Workspace/app-name/.bundle/config): "false"
Set for the current user (/Users/username/.bundle/config): "true"

You can run bundle config with --global. This will set values at global level i.e. across all applications on the machine. It will be similar to running bundle config without any options.

Deleting configuration

You can delete configuration with --delete option.

$ bundle config --delete auto_install
$ bundle config
Settings are listed in order of priority. The top value will be used.
disable_multisource
Set for the current user (/Users/username/.bundle/config): "true"

build.pg
Set for the current user (/Users/username/.bundle/config): "--with-pg-config=/usr/pgsql-9.1/bin/pg_config"

This is not compatible with --local and --global. This will delete configuration from local and global resources.

Build Options

You can pass the flags required for installing particular gem to bundler with bundle config.

Many El Capitan users face an issue while installing eventmachine gem. The issue can be resolved by providing path to OpenSSL include directory while installing eventmachine.

$ gem install eventmachine -v '1.0.8' -- --with-cppflags=-I/usr/local/opt/openssl/include

As location of configuration will vary from machine to machine, we can set this with bundle config.

$ bundle config build.eventmachine --with-cppflags=-I/usr/local/opt/openssl/include

Now bundler will pick this configuration while installing eventmachine gem.

Configuration keys

Various configuration keys are available with bundler. These keys are available in two forms. You can specify them in canonical form with bundle config or set them in environment variable form. Following are canonical forms and their usage. Corresponding environment variables are specified in bracket.

auto_install

Setting auto_install config will enable automatic installing of gems instead of raising an error. This applies to show, binstubs, outdated, exec, open, console, license, clean commands.

Example, When you try to run bundle show for the gem which is not yet installed you will get an error.

$ bundle show pg
Could not find gem 'pg'.

You can set auto_install to remove this error and install gem.

$ bundle config auto_install true

$ bundle show pg
# Gem will get installed
/Users/username/.rvm/gems/ruby-2.2.2@gemset-auto/gems/pg-0.17.1

path (BUNDLE_PATH)

You can specify location to install your gems. Default path is $GEM_HOME for development and vendor/bundle when –deployment is used.

$ bundle config path NEW_PATH
$ bundle install
# Gems will get installed
#
#
Bundle complete! 4 Gemfile dependencies, 35 gems now installed.
Bundled gems are installed into NEW_PATH.

frozen (BUNDLE_FROZEN)

You can freeze changes to your Gemfile.

$ bundle config frozen true

If frozen is set and you try to run bundle install with changed Gemfile, you will get following warning.

You are trying to install in deployment mode after changing
your Gemfile. Run `bundle install` elsewhere and add the
updated Gemfile.lock to version control.

If this is a development machine, remove the /Users/username/Documents/Workspace/app-name/Gemfile freeze
by running `bundle install --no-deployment`.

You have added to the Gemfile:
* minitest-reporters

without (BUNDLE_WITHOUT)

You can skip installing groups of gems with bundle install. Specify : separated group names whose gems bundler should not install.

$ bundle config --local without development:test

$ bundle install
# This will install gems skipping development and test group gems.

bin (BUNDLE_BIN)

You can set the directory to install executables from gems in the bundle.

$ bundle config bin NEW_PATH

$ bundle install
# This will install executables in NEW_PATH

gemfile (BUNDLE_GEMFILE)

You can set the file which bundler should use as theGemfile. By default, bundler will use Gemfile. The location of this file also sets the root of the project, which is used to resolve relative paths in the Gemfile.

$ bundle config gemfile Gemfile-rails4

$ bundle install
# This will install gems from Gemfile-rails4 file.

ssl_ca_cert (BUNDLE_SSL_CA_CERT)

This specifies path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. You can specify your own https sources in Gemfile with corresponding certificates specified via bundle config.

$ bundle config ssl_ca_cert NEW_CERTIFICATE_PATH

ssl_client_cert (BUNDLE_SSL_CLIENT_CERT)

This specifies path to a designated file containing a X.509 client certificate and key in PEM format.

$ bundle config ssl_client_cert NEW_CERTIFICATE_PATH

cache_path (BUNDLE_CACHE_PATH)

You can set the path to place cached gems while running bundle package.

$ bundle config cache_path vendor/new-cache-path

$ bundle package
Using colorize 0.7.7
Using pg 0.17.1
Using bundler 1.11.2
Updating files in vendor/new-cache-path
  * colorize-0.7.7.gem
  * pg-0.17.1.gem
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
Updating files in vendor/new-cache-path

disable_multisource (BUNDLE_DISABLE_MULTISOURCE)

When set, Gemfiles containing multiple sources will produce an error instead of a warning.

With Gemfile,

source 'https://rubygems.org'
source 'http://gems.github.com'

ruby '2.2.2'

When you try to run bundle install, you will get warning.

$ bundle install
Warning: this Gemfile contains multiple primary sources. Using `source` more than once without a block is a security risk, and may result in installing unexpected gems. To resolve this warning, use a block to indicate which gems should come from the secondary source. To upgrade this warning to an error, run `bundle config disable_multisource true`.
$ bundle config --local disable_multisource true

$ bundle install
[!] There was an error parsing `Gemfile`: Warning: this Gemfile contains multiple primary sources. Each source after the first must include a block to indicate which gems should come from that source. To downgrade this error to a warning, run `bundle config --delete disable_multisource`. Bundler cannot continue.

 #  from /Users/username/Documents/Workspace//Gemfile:2
 #  -------------------------------------------
 #  source 'https://rubygems.org'
 >  source 'http://gems.github.com'
 #
 #  -------------------------------------------

To ignore all bundle config on the machine and run bundle install, set BUNDLE_IGNORE_CONFIG environment variable.

Using D3 JS with React JS

In this blog, we will see how to plot a simple line chart using ReactJS and D3JS.

If you are not familiar with ReactJS then please take a look at official ReactJS webpage. You can also look at our Learn ReactJS in steps video series.

What is D3.js

D3.js is a Javascript library used to create interactive, dynamic visualizations.

Let’s take a step by step look at how we can integrate ReactJS with D3JS to plot some interactive visualizations.

Step 1 - Get ReactJS example working

We will be using JSFiddle example from ReactJS Docs to begin with. Fork the JSFiddle example and you should be good to go.

Step 2 - Add D3.js as an external resource

We will be using D3.js from Cloudflare CDN. Add D3.js as an external resource as shown in the image given below and type the following URL as an external resource.

https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.js

Add D3 js as an external resource

Step 3 - Build ReactJS components to create visualizations with D3.js

Now let’s try to draw a Line Chart using D3.js.

Let’s create a Line component that renders line path for the data points provided.

const Line = React.createClass({

  propTypes: {
    path:         React.PropTypes.string.isRequired,
    stroke:       React.PropTypes.string,
    fill:         React.PropTypes.string,
    strokeWidth:  React.PropTypes.number
  },

  getDefaultProps() {
    return {
      stroke:       'blue',
      fill:         'none',
      strokeWidth:  3
    };
  },

  render() {
    let { path, stroke, fill, strokeWidth } = this.props;
    return (
      <path
        d={path}
        fill={fill}
        stroke={stroke}
        strokeWidth={strokeWidth}
        />
    );
  }

});

Here in above code, Line component renders an SVG path. Path data d is generated using D3 path functions.

Let’s create another component DataSeries that will render Line component for each series of data provided. This generates path based on xScale and yScale generated for plotting a line chart.

const DataSeries = React.createClass({

  propTypes: {
    colors:             React.PropTypes.func,
    data:               React.PropTypes.object,
    interpolationType:  React.PropTypes.string,
    xScale:             React.PropTypes.func,
    yScale:             React.PropTypes.func
  },

  getDefaultProps() {
    return {
      data:               [],
      interpolationType:  'cardinal',
      colors:             d3.scale.category10()
    };
  },

  render() {
    let { data, colors, xScale, yScale, interpolationType } = this.props;

    let line = d3.svg.line()
      .interpolate(interpolationType)
      .x((d) => { return xScale(d.x); })
      .y((d) => { return yScale(d.y); });

    let lines = data.points.map((series, id) => {
      return (
        <Line
          path={line(series)}
          stroke={colors(id)}
          key={id}
          />
      );
    });

    return (
      <g>
        <g>{lines}</g>
      </g>
    );
  }

});

Here in above code d3.svg.line creates a new line generator which expects input as a two-element array of numbers.

Now we will create LineChart component that will calculate xScale, yScale based on data and will render DataSeries by passing xScale, yScale, data (input x,y values), width, height for the chart.

const LineChart = React.createClass({

  propTypes: {
    width:  React.PropTypes.number,
    height: React.PropTypes.number,
    data:   React.PropTypes.object.isRequired
  },

  getDefaultProps(){
    return {
      width:  600,
      height: 300
    }
  },

  render() {
    let { width, height, data } = this.props;

    let xScale = d3.scale.ordinal()
                   .domain(data.xValues)
                   .rangePoints([0, width]);

    let yScale = d3.scale.linear()
                   .range([height, 10])
                   .domain([data.yMin, data.yMax]);

    return (
      <svg width={width} height={height}>
          <DataSeries
            xScale={xScale}
            yScale={yScale}
            data={data}
            width={width}
            height={height}
            />
      </svg>
    );
  }

});

Here d3.scale.ordinal constructs an ordinal scale that can have discrete domain while d3.scale.linear constructs a linear quantitative scale.

You can learn more about D3 Quantitative scales here.

Now we need to call LineDataSeries component with the data.

let data = {
  points: [
    [ { x: 0, y: 20 }, { x: 1, y: 30 }, { x: 2, y: 10 }, { x: 3, y: 5 },
      { x: 4, y: 8 }, { x: 5, y: 15 }, { x: 6, y: 10 } ]
    ,
    [ { x: 0, y: 8 }, { x: 1, y: 5 }, { x: 2, y: 20 }, { x: 3, y: 12 },
      { x: 4, y: 4 }, { x: 5, y: 6 }, { x: 6, y: 2 } ]
    ,
    [ { x: 0, y: 0 }, { x: 1, y: 5 }, { x: 2, y: 8 }, { x: 3, y: 2 },
      { x: 4, y: 6 }, { x: 5, y: 4 }, { x: 6, y: 2 } ]
    ],
  xValues: [0,1,2,3,4,5,6],
  yMin: 0,
  yMax: 30
};

ReactDOM.render(
  <LineChart
    data={data}
    width={600}
    height={300}
    />,
  document.getElementById('container')
);

An element with id container is replaced with content rendered by LineChart.

If we take a look at the output now, we see how the Line Chart gets plotted.

ReactJS + D3.js Line Chart example

To build complex visualizations in a modularized fashion, we can use one of the open source libraries mentioned below based on their advantages and disadvantages.

ReactJS + D3.js Open Source Projects

Here are two popular open source ReactJS + D3.JS projects.

react-d3

Pros

  • Supports Bar chart, Line chart, Area chart, Pie chart, Candlestick chart, Scattered chart and Treemap.
  • Legend support.
  • Tooltips support.

Cons

  • No support for Animations. You can implement animations using D3 Transitions.
  • Only stacked Bar chart support.
react-d3-components

Pros

  • Custom scales support.
  • Supports Bar chart (Stacked, Grouped), Line chart, Area chart, Pie chart, Scattered chart.
  • Tooltips support.

Cons

  • No Legend support.
  • No support for Animations.

Summary

Below is final working example of JSFiddle built in the post.

Caching result sets and collection in Rails 5

This blog is part of our Rails 5 series.

Often while developing a Rails application you may look to have one of these caching techniques to boost the performance. Along with these, Rails 5 now provides a way of caching a collection of records, thanks to the introduction of the following method:

ActiveRecord::Relation#cache_key

What is collection caching?

Consider the following example where we are fetching a collection of all users belonging to city of Miami.

@users = User.where(city: 'miami')

Here @users is a collection of records and is an object of class ActiveRecord::Relation.

Whether the result of the above query would be same depends on following conditions.

  • The query statement doesn’t change. If we change city name from “Miami” to “Boston” then result might change.
  • No record is deleted. The count of records in the collection should be same.
  • No record is added. The count of records in the collection should be same.

Rails community implemented caching for a collection of records . Method cache_key was added to ActiveRecord::Relation which takes into account many factors including query statement, updated_at column value and the count of the records in collection.

Understanding ActiveRecord::Relation#cache_key

We have object @users of class ActiveRecord::Relation. Now let’s execute cache_key method on it.

 @users.cache_key
 => "users/query-67ed32b36805c4b1ec1948b4eef8d58f-3-20160116111659084027"

Let’s try to understand each piece of the output.

users represents what kind of records we are holding. In this example we have collection of records of class User. Hence users is to illustrate that we are holding users records.

query- is hardcoded value and it will be same in all cases.

67ed32b36805c4b1ec1948b4eef8d58f is a digest of the query statement that will be executed. In our example it is MD5( "SELECT "users".* FROM "users" WHERE "users"."city" = 'Miami'")

3 is the size of collection.

20160116111659084027 is timestamp of the most recently updated record in the collection. By default, the timestamp column considered is updated_at and hence the value will be the most recent updated_at value in the collection.

Using ActiveRecord::Relation#cache_key

Let’s see how to use cache_key to actually cache data.

In our Rails application, if we want to cache records of users belonging to “Miami” then we can take following approach.

# app/controllers/users_controller.rb

class UsersController < ApplicationController

  def index
    @users = User.where(city: 'Miami')
  end
end

# users/index.html.erb

<% cache(@users) do %>
  <% @users.each do |user| %>
    <p> <%= user.city %> </p>
  <% end %>
<% end %>

# 1st Hit
Processing by UsersController#index as HTML
  Rendering users/index.html.erb within layouts/application
   (0.2ms)  SELECT COUNT(*) AS "size", MAX("users"."updated_at") AS timestamp FROM "users" WHERE "users"."city" = ?  [["city", "Miami"]]
Read fragment views/users/query-37a3d8c65b3f0f9ece7f66edcdcb10ab-4-20160704131424063322/30033e62b28c83f26351dc4ccd6c8451 (0.0ms)
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."city" = ?  [["city", "Miami"]]
Write fragment views/users/query-37a3d8c65b3f0f9ece7f66edcdcb10ab-4-20160704131424063322/30033e62b28c83f26351dc4ccd6c8451 (0.0ms)
Rendered users/index.html.erb within layouts/application (3.7ms)

# 2nd Hit
Processing by UsersController#index as HTML
  Rendering users/index.html.erb within layouts/application
   (0.2ms)  SELECT COUNT(*) AS "size", MAX("users"."updated_at") AS timestamp FROM "users" WHERE "users"."city" = ?  [["city", "Miami"]]
Read fragment views/users/query-37a3d8c65b3f0f9ece7f66edcdcb10ab-4-20160704131424063322/30033e62b28c83f26351dc4ccd6c8451 (0.0ms)
  Rendered users/index.html.erb within layouts/application (3.0ms)

From above, we can see that for the first hit, a count query is fired to get the the latest updated_at and size from the users collection.

Rails will write a new cache entry with a cache_key generated from above count query.

Now on second hit, it again fires count query and checks if cache_key for this query exists or not.

If cache_key is found, it loads data without firing SQL query.

What if your table doesn’t have updated_at column?

Previously we mentioned that cache_key method uses updated_at column. cache_key also provides an option of passing custom column as a parameter and then the highest value of that column among the records in the collection will be considered.

For example if your business logic considers a column named last_bought_at in products table as a factor to decide caching, then you can use the following code.

 products = Product.where(category: 'cars')
 products.cache_key(:last_bought_at)
 => "products/query-211ae6b96ec456b8d7a24ad5fa2f8ad4-4-20160118080134697603"

Edge cases to watch out for

Before you start using cache_key there are some edge cases to watch out for.

Consider you have an application where there are 5 entries in users table with city Miami.

Using limit puts incorrect size in cache key if collection is not loaded.

If you want to fetch three users belonging to city “Miami” then you would execute following query.

 users = User.where(city: 'Miami').limit(3)
 users.cache_key
 => "users/query-67ed32b36805c4b1ec1948b4eef8d58f-3-20160116144936949365"

Here users contains only three records and hence the cache_key has 3 for size of collection.

Now let’s try to execute same query without fetching the records first.

 User.where(name: 'Sam').limit(3).cache_key
 => "users/query-8dc512b1408302d7a51cf1177e478463-5-20160116144936949365"

You can see that the count in the cache is 5 this time even though we have set a limit to 3. This is because the implementation of ActiveRecord::Base#collection_cache_key executes query without limit to fetch the size of the collection.

Cache key doesn’t change when an existing record from a collection is replaced

I want 3 users in the descending order of ids.

 users1 = User.where(city: 'Miami').order('id desc').limit(3)
 users1.cache_key
 => "users/query-57ee9977bb0b04c84711702600aaa24b-3-20160116144936949365"

Above statement will give us users with ids [5, 4, 3].

Now let’s remove the user with id = 3.

 User.find(3).destroy

 users2 = User.where(first_name: 'Sam').order('id desc').limit(3)
 users2.cache_key
 => "users/query-57ee9977bb0b04c84711702600aaa24b-3-20160116144936949365"

Note that cache_key both users1 and users2 is exactly same. This is because none of the parameters that affect the cache key is changed i.e., neither the number of records, nor the query statement, nor the timestamp of the latest record.

There is a discussion undergoing about adding ids of the collection records as part of the cache key. This might help solve the problems discussed above.

Using group query gives incorrect size in the cache key

Just like limit case discussed above cache_key behaves differently when data is loaded and when data is not loaded in memory.

Let’s say that we have two users with first_name “Sam”.

First let’s see a case where collection is not loaded in memory.

 User.select(:first_name).group(:first_name).cache_key
 => "users/query-92270644d1ec90f5962523ed8dd7a795-1-20160118080134697603"

In the above case, the size is 1 in cache_key. For the system mentioned above, the sizes that you will get shall either be 1 or 5. That is, it is size of an arbitrary group.

Now let’s see when collection is first loaded.

 users = User.select(:first_name).group(:first_name)
 users.cache_key
 => "users/query-92270644d1ec90f5962523ed8dd7a795-2-20160118080134697603"

In the above case, the size is 2 in cache_key. You can see that the count in the cache key here is different compared to that where the collection was unloaded even though the query output in both the cases will be exactly same.

In case where the collection is loaded, the size that you get is equal to the total number of groups. So irrespective of what the records in each group are, we may have possibility of having the same cache key value.