San Francisco, USA

5214F Diamond Heights Blvd #553
San Francisco, CA 94131

Pune, India

203, Jewel Towers, 2nd Floor
Lane Number 5, Koregaon Park
Pune 411001, India

301 - 275 - 3997
hello@BigBinary.com

Rails 6.1 raises on db:rollback for multiple database applications

This blog is part of our Rails 6.1 series.

Rails 6.1 adds support to handle db:rollback in case of multiple database application.

Prior to this change, on executing db:rollback Rails used to rollback the latest migration from the primary database. If we passed on a [:NAME] option along with to specify the database, we used to get an error. Check out the issue for more details.

Rails 6.0.0

> rails db:rollback:secondary

rails aborted!
Don't know how to build task `db:rollback:secondary` (See the list of available tasks with `rails --tasks`)
Did you mean?  db:rollback

Staring with Rails 6.1, we need to pass the database name along with db:rollback:[NAME] otherwise a RuntimeError is raised.

Rails 6.1.0

> rails db:rollback

rails aborted!
You're using a multiple database application. To use `db:migrate:rollback` you must run the namespaced task with a VERSION. Available tasks are db:migrate:rollback:primary and db:migrate:rollback:secondary.

> rails db:rollback:primary

== 20200731130500 CreateTeams: reverting ======================================
-- drop_table(:teams)
   -> 0.0060s
== 20200731130500 CreateTeams: reverted (0.0104s) =============================

Check out the pull request for more details on this.


How to render liquid templates when the template refers to other liquid templates

Shopify’s Liquid Templates is a great way for templating in Ruby on Rails applications.

If the template is as simple as this one then there are no issues.

{% if user %}
  Hello {{ user.name }}
{% endif %}

However sometimes we have a liquid template which is using another liquid template. Here is an example.

home.liquid
<!DOCTYPE html>
<html>
  <head>
    <style>{% asset 'main.css' %}</style>
  </head>
  <body>
    {% partial 'header' %}
    <h1>Home Page</h1>
  </body>
</html>

In the above case home.liquid is using two other liquid templates main.css and header.liquid.

Let’ see what these templates look like.

main.css
* {
  color: {{ theme.text_color }};
}
a {
  color: {{ theme.link_color }};
}
header.liquid
<nav>
{{ organization.name }}
</nav>

In order to include the assets and the partials we need to create liquid tags.

Let’s create a tag which will handle assets.

# app/lib/liquid/tags/asset.rb

module Liquid
  module Tags
    class Asset < Liquid::Tag
      def initialize(tag_name, name, tokens)
        super
        @name = name.strip.remove("'")
      end

      def render(context)
        new_context = context.environments.first
        asset = Template.asset.find_by(filename: @name)

        Liquid::Template.parse(asset.content).render(new_context).html_safe
      end
    end
  end
end

Let’s create a tag that will handle partials.

# app/lib/liquid/tags/partial.rb

module Liquid
  module Tags
    class Partial < Liquid::Tag
      def initialize(tag_name, name, tokens)
        super
        @name = name.strip.remove("'")
      end

      def render(context)
        new_context = context.environments.first

        # Remember here we are not passing extension
        asset = Template.partial.find_by(filename: @name + ".liquid")

        Liquid::Template.parse(asset.content).render(new_context).html_safe
      end
    end
  end
end

Let’s create a new initializer and we need to register these tags in that initializer.

# config/initializers/liquid.rb

require 'liquid/tags/asset'
require 'liquid/tags/partial'

Liquid::Template.register_tag('asset', Liquid::Tags::Asset)
Liquid::Template.register_tag('partial', Liquid::Tags::Partial)

Restart the server and now we can render the home.liquid template like this.

template = Template.template.find_by(filename: "home.liquid")

attributes = {
  organization: {
    name: "Example"
  },
  theme: {
    text_color: "#000000",
    link_color: "#DBDBDB"
  }
}

Liquid::Template.parse(template.content).render(attributes).html_safe

Here we have a simple implementation of the tags. We can do much more, if needed, like looping over items to parse each item from the partial. That can be done by registering a separate tag for the item and passing in the id of the item so that the specific item can be found and parsed.


Rails 6.1 deprecates the use of return, break or throw to exit a transaction block

This blog is part of our Rails 6.1 series.

Rails 6.1 deprecates the use of return, break or throw to exit a transaction block.

return / break

>> Post.transaction do
>>   @post.update(post_params)
>>
>>   break # or return
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => DEPRECATION WARNING: Using `return`, `break` or `throw` to exit a transaction block is
# => deprecated without replacement. If the `throw` came from
# => `Timeout.timeout(duration)`, pass an exception class as a second
# => argument so it doesn't use `throw` to abort its block. This results
# => in the transaction being committed, but in the next release of Rails
# => it will rollback.
# => TRANSACTION (0.8ms)  commit transaction

throw

>> Timeout.timeout(1) do
>>   Post.transaction do
>>     @post.update(post_params)
>>
>>     sleep 3 # simulate slow request
>>   end
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => DEPRECATION WARNING: Using `return`, `break` or `throw` to exit a transaction block is
# => deprecated without replacement. If the `throw` came from
# => `Timeout.timeout(duration)`, pass an exception class as a second
# => argument so it doesn't use `throw` to abort its block. This results
# => in the transaction being committed, but in the next release of Rails
# => it will rollback.
# => TRANSACTION (1.6ms)  commit transaction
# => Completed 500 Internal Server Error in 1022ms (ActiveRecord: 3.2ms | Allocations: 9736)
# => Timeout::Error (execution expired)

Here, even when the error was thrown the transaction is committed. This is something which is going to change in the future versions.

This is done because currently, when a transaction block is wrapped in Timeout.timeout(duration) i.e. without second argument(an exception class) then it uses throw to exit the transaction.

Solution

>> Timeout.timeout(1, Timeout::Error) do
>>   Post.transaction do
>>     @post.update(post_params)
>>
>>     sleep 3 # simulate slow request
>>   end
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => TRANSACTION (0.7ms)  rollback transaction
# => Timeout::Error (execution expired)

Check out the pull request for more details on this.


Rails 6.1 automatically generates an abstract class when using multiple databases

This blog is part of our Rails 6.1 series.

Rails started supporting multiple databases from Rails 6.0. To use a specific database, we can specify the database connection in the model using connects_to. In the following case we want Person model to connect to crm database.

class Person < ApplicationRecord
  connects_to database: { writing: :crm }
end

As the application grows, more and more models start sharing the same database. Now a lot of models may contain connects_to call to the same database.

class Person < ApplicationRecord
  connects_to database: { writing: :crm }
end

class Order < ApplicationRecord
  connects_to database: { writing: :crm }
end

class Sale < ApplicationRecord
  connects_to database: { writing: :crm }
end

In order to avoid the duplication, we can create an abstract class connecting to a database and manually inherit all other models from that class. This could look like this.

class CrmRecord < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :crm }
end

class Person < CrmRecord
end

class Order < CrmRecord
end

class Sale < CrmRecord
end

Rails 6.1

Before Rails 6.1 we had no choice but to create that abstract class manually. Rails 6.1 allows us to generate an abstract class when we are generating a model using scaffold.

$ rails g scaffold Person name:string --database=crm

It creates an abstract class with the database’s name appended with Record. The generated model automatically inherits from the new abstract class.

# app/models/users_record.rb
class CrmRecord < ApplicationRecord
  self.abstract_class = true

  connects_to database: { writing: :crm }
end

# app/models/admin.rb
class Person < CrmRecord
end

If the abstract class already exists, it is not created again. We can also use an existing class as the abstract class by passing parent option to the scaffold command.

$ rails g scaffold Customer name:string --database=crm --parent=PrimaryRecord

This skips generating CrmRecord class as we have specified Rails to use PrimaryRecord abstract class as its parent.

Check out the pull request for more details on this.


Rails 6.1 deprecates the use of return, break or throw to exit a transaction block

This blog is part of our Rails 6.1 series.

Rails 6.1 deprecates the use of return, break or throw to exit a transaction block.

return / break

>> Post.transaction do
>>   @post.update(post_params)
>>
>>   break # or return
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => DEPRECATION WARNING: Using `return`, `break` or `throw` to exit a transaction block is
# => deprecated without replacement. If the `throw` came from
# => `Timeout.timeout(duration)`, pass an exception class as a second
# => argument so it doesn't use `throw` to abort its block. This results
# => in the transaction being committed, but in the next release of Rails
# => it will rollback.
# => TRANSACTION (0.8ms)  commit transaction

throw

>> Timeout.timeout(1) do
>>   Post.transaction do
>>     @post.update(post_params)
>>
>>     sleep 3 # simulate slow request
>>   end
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => DEPRECATION WARNING: Using `return`, `break` or `throw` to exit a transaction block is
# => deprecated without replacement. If the `throw` came from
# => `Timeout.timeout(duration)`, pass an exception class as a second
# => argument so it doesn't use `throw` to abort its block. This results
# => in the transaction being committed, but in the next release of Rails
# => it will rollback.
# => TRANSACTION (1.6ms)  commit transaction
# => Completed 500 Internal Server Error in 1022ms (ActiveRecord: 3.2ms | Allocations: 9736)
# => Timeout::Error (execution expired)

Here, even when the error was thrown the transaction is committed. This is something which is going to change in the future versions.

This is done because currently, when a transaction block is wrapped in Timeout.timeout(duration) i.e. without second argument(an exception class) then it uses throw to exit the transaction.

Solution

>> Timeout.timeout(1, Timeout::Error) do
>>   Post.transaction do
>>     @post.update(post_params)
>>
>>     sleep 3 # simulate slow request
>>   end
>> end

# => TRANSACTION (0.1ms)  begin transaction
# => TRANSACTION (0.7ms)  rollback transaction
# => Timeout::Error (execution expired)

Check out the pull request for more details on this.


Rails 6.1 adds annotate_rendered_view_with_filenames to annotate HTML output

This blog is part of our Rails 6.1 series.

Rails 6.1 makes it easier to debug rendered HTML by adding the name of each template used.

Rails 6.1

Add the following line in development.rb file to enable this feature.

config.action_view.annotate_rendered_view_with_filenames = true

Now the rendered HTML will contain comment indicating the begining and end of each template.

Here is an example.

Annotated HTML output

In the image we can see the begin and end for each of the templates. It helps a lot in debugging webpages to find out which template is rendered. Check out the pull request for more details on this.


Rails 6.1 allows enums attributes to configure the default value

Rails 6.1 makes it easier to configure a default value for Active Record enum attributes.

Let’s take an example of blog posts with status and category columns.

class Post < ApplicationRecord
  enum status: %i[draft reviewed published]
  enum category: { rails: "Rails", react: "React" }
end

Before Rails 6.1, defaults for enum attributes can be configured by applying default on the database level.

class AddColumnStatusToPosts < ActiveRecord::Migration[6.0]
  def change
    add_column :posts, :status, :integer, default: 0
    add_column :posts, :category, :string, default: "Rails"
  end
end

After Rails 6.1, defaults for enum attributes can be configured directly in the Post model using _default option.

class Post < ApplicationRecord
  enum status: %i[draft reviewed published], _default: "draft"
  enum category: { rails: "Rails", react: "React" }, _default: "Rails"
end

The new approach to set enum defaults has following advantages. Let’s understand keeping the context of Post model with category as an example.

  • When the category default value changes from Rails to React. We have to add a new migration in Rails 6 and previous versions to update the database column default.
  • Let say the default value for post category(i.e: Rails) is removed from the enum from Post model. Rails 6 and previous versions wouldn’t throw an exception and continue to work without setting any default value. Rails 6.1 with new syntax would raise an exception.

Check out the pull request for more details on this.


Rails 6.1 adds support for where with a comparison operator

Rails 6.1 adds support to comparison operator in the where clause. The four comparison operators supported are:

  • Greater than (>).
  • Greater than equal to (>=).
  • Less than (<).
  • Less than equal to (<=).

The comparison operator is also supported by the finder methods in ActiveRecord which internally uses where clause, for example: find_by, destroy_by, delete_by.

The new style for comparisons has to follow advantages:

  • The where clause with the comparison operator doesn’t raise an exception when ActiveRecord::Relation uses ambiguous column name.
  • The where clause with the comparison operator handle proper precision of the database columns.

Before Rails 6.1, to add a condition with comparison in where clause, we had to add raw SQL notation.

Rails 6.0.0

>> Post.where("DATE(published_at) > DATE(?)", Date.today)
# => <ActiveRecord::Relation [...]>

>> Post.find_by("likes < ?", 10)
# => <ActiveRecord::Relation [...]>

# Following query on execution would raise exception.
>> Post.joins(:comments).where("likes > 10")
# => ambiguous column name: id

Rails 6.1.0

>> Post.where("published_at >": Date.today)
# => <ActiveRecord::Relation [...]>

>> Post.find_by("likes <": 10)
# => <ActiveRecord::Relation [...]>

# Following query on execution would NOT raise exception.
>> Post.joins(:comments).where("likes >": 10)
# => <ActiveRecord::Relation [...]>

Check out the pull request for more details on this.


Rails 6.1 tracks Active Storage variant in the database

This blog is part of our Rails 6.1 series.

Active Storage variants are the transformation of the original image. These variants can be used as thumbnails, avatars, etc.

Active Storage generates variants on demand by downloading the original image. The image is transformed into a variant and is stored to the third party services like S3.

When a request to fetch a variant for an Active Storage object is made, Rails checks if the variant is already been processed and is already available on S3 or not. But to do so Rails has to make a call to find out if the variant is available on S3. This extra call adds to the latency.

Active Storage has to wait until the image variant check call is completed because S3 might not return the image when a GET request is made due to eventual consistency. This way Rails avoid downloading a broken image from S3 and uploading broken image variant to S3 in case the variant is not present.

In Rails 6.1, Active Storage tracks the presence of the variant in the database. This change avoids unnecessary variant presence remote request made to the S3 and directly fetches or generates a image variant.

In Rails 6.1, the configuration to allow variant tracking in the database is by default set to true.

config.active_storage.track_variants: true

Check out the pull request for more details on this.


Ruby 2.7 adds Enumerable#filter_map

This blog is part of our Ruby 2.7 series. Ruby 2.7.0 was released on Dec 25, 2019.

Ruby 2.7 adds Enumerable#filter_map which is a combination of filter + map as the name indicates. The ‘filter_map’ method filters and map the enumerable elements within a single iteration.

Before Ruby 2.7, we could have achieved the same with 2 iterations using select & map combination or map & compact combination.

irb> numbers = [3, 6, 7, 9, 5, 4]

# we can use select & map to find square of odd numbers

irb> numbers.select { |x| x.odd? }.map { |x| x**2 }
=> [9, 49, 81, 25]

# or we can use map & compact to find square of odd numbers

irb> numbers.map { |x| x**2 if x.odd? }.compact
=> [9, 49, 81, 25]

Ruby 2.7

Ruby 2.7 adds Enumerable#filter_map which can be used to filter & map the elements in a single iteration and which is more faster when compared to other options described above.

irb> numbers = [3, 6, 7, 9, 5, 4]
irb> numbers.filter_map { |x| x**2 if x.odd? }
=> [9, 49, 81, 25]

The original discussion had started 8 years back. Here is the latest thread and github commit for reference.


Ruby 2.7 deprecates conversion of keyword arguments

A notable change has been announced for Ruby 3 for which deprecation warning has been added in Ruby 2.7. Ruby 2.7 deprecated automatic conversion of keyword arguments and positional arguments. This conversion will be completely removed in Ruby 3.

Ruby 2.7 NEWS has listed the spec of keyword arguments for Ruby 3.0. We will take the examples mentioned there and for each scenario we will look into how we can fix them in the existing codebase.

Scenario 1

When method definition accepts keyword arguments as the last argument.

def sum(a: 0, b: 0)
  a + b
end

Passing exact keyword arguments in a method call is acceptable, but in applications we usually pass a hash to a method call.

sum(a: 2, b: 4) # OK

sum({ a: 2, b: 4 }) # Warned

In this case, we can add a double splat operator to the hash to avoid deprecation warning.

sum(**{ a: 2, b: 4 }) # OK

Scenario 2

When method call passes keyword arguments but does not pass enough required positional arguments.

If the number of positional arguments doesn’t match with method definition, then keyword arguments passed in method call will be considered as the last positional argument to the method.

def sum(num, x: 0)
  num.values.sum + x
end
sum(a: 2, b: 4) # Warned

sum(a: 2, b: 4, x: 6) # Warned

To avoid deprecation warning and for code to be compatible with Ruby 3, we should pass hash instead of keyword arguments in method call.

sum({ a: 2, b: 4 }) # OK

sum({ a: 2, b: 4}, x: 6) # OK

Scenario 3

When a method accepts a hash and keyword arguments but method call passes only hash or keyword arguments.

If a method arguments are a mix of symbol keys and non-symbol keys, and the method definition accepts either one of them then Ruby splits the keyword arguments but also raises a warning.

def sum(num={}, x: 0)
  num.values.sum + x
end
sum("x" => 2, x: 4) # Warned

sum(x: 2, "x" => 4) # Warned

To fix this warning, we should pass hash separately as defined in the method definition.

sum({ "x" => 4 }, x: 2) # OK

Scenario 4

When an empty hash with double splat operator is passed to a method that doesn’t accept keyword arguments.

Passing keyword arguments using double splat operator to a method that doesn’t accept keyword argument will send empty hash similar to earlier version of Ruby but will raise a warning.

def sum(num)
  num.values.sum
end
numbers = {}
sum(**numbers) # Warned

To avoid this warning, we should change method call to pass hash instead of using double splat operator.

numbers = {}
sum(numbers) # OK

Added support for non-symbol keys

In Ruby 2.6.0, support for non-symbol keys in method call was removed. It is added back in Ruby 2.7. When method accepts arbitrary keyword arguments using double splat operator then non-symbol keys can also be passed.

def sum(**num)
  num.values.sum
end
ruby 2.6.5
sum("x" => 4, "y" => 3)
=> ArgumentError (wrong number of arguments (given 1, expected 0))

sum(x: 4, y: 3)
=> 7
ruby 2.7.0
sum("x" => 4, "y" => 3)
=> 7

sum(x: 4, y: 3)
=> 7

Added support for **nil

Ruby 2.7 added support for **nil to explicitly mention if a method doesn’t accept any keyword arguments in method call.

def sum(a, b, **nil)
  a + b
end

sum(2, 3, x: 4)
=> ArgumentError (no keywords accepted)

To suppress above deprecation warnings we can use -W:no-deprecated option.

In conclusion, Ruby 2.7 has worked big steps towards changing specification of keyword arguments which will be completely changed in Ruby 3.

For more information on discussion, code changes and official documentation, please head to Feature #14183 discussion, pull request and NEWS release.


Ruby 2.7 adds Enumerator::Lazy#eager

This blog is part of our Ruby 2.7 series. Ruby 2.7.0 was released on Dec 25, 2019.

Ruby 2.0 introduced Enumerator::Lazy, a special type of enumerator which helps us in processing chains of operations on a collection without actually executing it instantly.

By applying Enumerable#lazy method on any enumerable object, we can convert that object into Enumerator::Lazy object. The chains of actions on this lazy enumerator will be evaluated only when it is needed. It helps us in processing operations on large collections, files and infinite sequences seamlessly.

# This line of code will hang and you will have to quit the console by Ctrl+C.
irb> list = (1..Float::INFINITY).select { |i| i%3 == 0 }.reject(&:even?)

# Just adding `lazy`, the above line of code now executes properly
# and returns result without going to infinite loop. Here the chains of
# operations are performed as and when it is needed.
irb> lazy_list = (1..Float::INFINITY).lazy.select { |i| i%3 == 0 }.reject(&:even?)
=> #<Enumerator::Lazy: ...>

irb> lazy_list.first(5)
=> [3, 9, 15, 21, 27]

When we chain more operations on Enumerable#lazy object, it again returns lazy object without executing it. So, when we pass lazy objects to any method which expects a normal enumerable object as an argument, we have to force evaluation on lazy object by calling to_a method or it’s alias force.

# Define a lazy enumerator object.
irb> list = (1..30).lazy.select { |i| i%3 == 0 }.reject(&:even?)
=> #<Enumerator::Lazy: #<Enumerator::Lazy: ... 1..30>:select>:reject>

# The chains of operations will return again a lazy enumerator.
irb> result = list.select { |x| x if x <= 15 }
=> #<Enumerator::Lazy: #<Enumerator::Lazy: ... 1..30>:select>:reject>:map>

# It returns error when we call usual array methods on result.
irb> result.sample
irb> NoMethodError (undefined method `sample'
irb> for #<Enumerator::Lazy:0x00007faab182a5d8>)

irb> result.length
irb> NoMethodError (undefined method `length'
irb> for #<Enumerator::Lazy:0x00007faab182a5d8>)

# We can call the normal array methods on lazy object after forcing
# its actual execution with methods as mentioned above.
irb> result.force.sample
=> 9

irb> result.to_a.length
=> 3

The Enumerable#eager method returns a normal enumerator from a lazy enumerator, so that lazy enumerator object can be passed to any methods which expects a normal enumerable object as an argument. Also, we can call other usual array methods on the collection to get desired results.

# By adding eager on lazy object, the chains of operations would return
# actual result here. If lazy object is passed to any method, the
# processed result will be received as an argument.
irb> eager_list = (1..30).lazy.select { |i| i%3 == 0 }.reject(&:even?).eager
=> #<Enumerator: #<Enumerator::Lazy: ... 1..30>:select>:reject>:each>

irb> result = eager_list.select { |x| x if x <= 15 }
irb> result.sample
=> 9

irb> result.length
=> 3

The same way, we can use eager method when we pass lazy enumerator as an argument to any method which expects a normal enumerator.

irb> list = (1..10).lazy.select { |i| i%3 == 0 }.reject(&:even?)
irb> def display(enum)
irb>   enum.map { |x| p x }
irb> end

irb>  display(list)
=> #<Enumerator::Lazy: #<Enumerator::Lazy: ... 1..30>:select>:reject>:map>

irb> eager_list = (1..10).lazy.select { |i| i%3 == 0 }.reject(&:even?).eager
irb> display(eager_list)
=> 3
=> 9

Here’s the relevant commit and feature discussion for this change.


Ruby 2.7 introduces numbered parameters as default block parameters

This blog is part of our Ruby 2.7 series. Ruby 2.7.0 was released on Dec 25, 2019.

At some point, all of us have used names like a, n, i etc for block parameters. Below are few examples where numbered parameters can come in handy.

> (1..10).each { |n| p n * 3 }

> { a: [1, 2, 3], b: [2, 4, 6], c: [3, 6, 9] }.each { |_k, v| p v }

> [10, 100, 1000].each_with_index { |n, i| p n, i }

Ruby 2.7 introduces a new way to access block parameters. Ruby 2.7 onwards, if block parameters are obvious and we wish to not use absurd names like n or i etc, we can use numbered parameters which are available inside a block by default.

We can use _1 for first parameter, _2 for second parameter and so on.

Here’s how Ruby 2.7 provides numbered parameters inside a block. Below shown are the examples from above, only this time using numbered parameters.

> (1..10).each { p _1 * 3 }

> { a: [1, 2, 3], b: [2, 4, 6], c: [3, 6, 9] }.each { p _2 }

> [10, 100, 1000].each_with_index { p _1, _2 }

Like mentioned in News-2.7.0 docs, Ruby now raises a warning if we try to define local variable in the format _1. Local variable will have precedence over numbered parameter inside the block.

> _1 = 0
> => warning: `_1' is reserved for numbered parameter; consider another name

> [10].each { p _1 }
> => 0

Numbered parameters are not accessible inside the block if we define ordinary parameters. If we try to access _1 when ordinary parameters are defined, then ruby raises SyntaxError like shown below.

> ["a", "b", "c"].each_with_index { |alphabet, index| p _1, _2}

=> SyntaxError ((irb):1: ordinary parameter is defined)

This feature was suggested 9 years back and came back in discussion last year. After many suggestions community agreed to use _1 syntax.

Head to following links to read the discussion behind numbered parameters, Feature #4475 and Discussion #15723.

Here’s relevant commit for this feature.


Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block

This blog is part of our Rails 6 series. Rails 6.0 was recently released.

Rails 6 fixes a bug where after_commit callbacks are called on failed update in a transaction block.

Let’s checkout the bug in Rails 5.2 and the fix in Rails 6.

Rails 5.2

Let’s define an after_commit callback in User model and try updating an invalid user object in a transaction block.

>> class User < ApplicationRecord
>>   validates :name, :email, presence: true
>>
>>   after_commit :show_success_message
>>
>>   private
>>
>>     def show_success_message
>>       p 'User has been successfully saved into the database.'
>>     end
>> end

=> :show_success_message

>> user = User.create(name: 'Jon Snow', email: 'jon@bigbinary.com')
begin transaction
User Create (0.8ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Jon Snow"], ["email", "jon@bigbinary.com"], ["created_at", "2019-07-14 15:35:33.517694"], ["updated_at", "2019-07-14 15:35:33.517694"]]
commit transaction
"User has been successfully saved into the database."

=> #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:35:33", updated_at: "2019-07-14 15:35:33">

>> User.transaction do
>>   user.email = nil
>>   p user.valid?
>>   user.save
>> end
begin transaction
false
commit transaction
"User has been successfully saved into the database."

=> false

As we can see here, that that the after_commit callback show_success_message was called even if object was never saved in the transaction.

Rails 6.0.0.rc1

Now, let’s try the same thing in Rails 6.

>> class User < ApplicationRecord
>>   validates :name, :email, presence: true
>>
>>   after_commit :show_success_message
>>
>>   private
>>
>>     def show_success_message
>>       p 'User has been successfully saved into the database.'
>>     end
>> end

=> :show_success_message

>> user = User.create(name: 'Jon Snow', email: 'jon@bigbinary.com')
SELECT sqlite_version(*)
begin transaction
User Create (1.0ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "Jon Snow"], ["email", "jon@bigbinary.com"], ["created_at", "2019-07-14 15:40:54.022045"], ["updated_at", "2019-07-14 15:40:54.022045"]]
commit transaction
"User has been successfully saved into the database."

=> #<User id: 1, name: "Jon Snow", email: "jon@bigbinary.com", created_at: "2019-07-14 15:40:54", updated_at: "2019-07-14 15:40:54">

>> User.transaction do
>>   user.email = nil
>>   p user.valid?
>>   user.save
>>   end
false

=> false

Now, we can see that after_commit callback was never called if the object was not saved.

Here is the relevant issue and the pull request.


Ruby 2.7 adds Enumerable#tally

This blog is part of our Ruby 2.7 series. Ruby 2.7.0 was released on Dec 25, 2019.

Let’s say that we have to find the frequency of each element of an array.

Before Ruby 2.7, we could have achieved it using group_by or inject.

irb> scores = [100, 35, 70, 100, 70, 30, 35, 100, 45, 30]

# we can use group_by to group the scores

irb> scores.group_by { |v| v }.map { |k, v| [k, v.size] }.to_h
=> {100=>3, 35=>2, 70=>2, 30=>2, 45=>1}

# or we can use inject to group the scores

irb> scores.inject(Hash.new(0)) {|hash, score| hash[score] += 1; hash }
=> {100=>3, 35=>2, 70=>2, 30=>2, 45=>1}

Ruby 2.7

Ruby 2.7 adds Enumerable#tally which can be used to find the frequency. Tally makes the code more readable and intuitive. It returns a hash where keys are the unique elements and values are its corresponding frequency.

irb> scores = [100, 35, 70, 100, 70, 30, 35, 100, 45, 30]
irb> scores.tally
=> {100=>3, 35=>2, 70=>2, 30=>2, 45=>1}

Check out the github commit for more details on this.


Rails 6.1 introduces class_names helper

This blog is part of our Rails 6.1 series.

Rails 6.1 adds class_names view helper method to conditionally add CSS classes. class_names helper accepts String, Hash and Array as arguments and returns string of class names built from arguments.

Before Rails 6.1, conditional classes were added by using conditional statements. Let’s take an example of adding an active class to navigation link based on the current page.

Rails 6.0.0

<li class="<%= current_page?(dashboards_path) ? 'active' : '' %>">
  <%= link_to "Home", dashboards_path %>
</li>

Rails 6.1.0

>> class_names(active: current_page?(dashboards_path))
=> "active"

# Default classes can be added with conditional classes
>> class_names('navbar', { active: current_page?(dashboards_path) })
=> "navbar active"

# class_names helper rejects empty strings, nil, false arguments.
>> class_names(nil, '', false, 'navbar' {active: current_page?(dashboards_path)})
=> "navbar active"
<li class="<%= class_names(active: current_page?(dashboards_path)) %>">
  <%= link_to "Home", dashboards_path %>
</li>

Check out the pull request for more details on this.


Rails Multiple Polymorphic Joins

Having polymorphic associations in Rails can be a hard nut to crack. It enforces restriction on joins association which makes it difficult to write complex queries.

Consider following architecture where Defects can be of InspectedTruck or InspectedTrailer associated polymorphically.

  class InspectedTruck
    has_many :defects, as: :associated_object
  end

  class InspectedTrailer
    has_many :defects, as: :associated_object
  end

  class Defect
    belongs_to :associated_object, polymorphic: true
  end

Finding defects for inspected trucks using joins will raise error.

  => Defect.joins(:associated_object).load
  ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :associated_object

We need to write a raw sql INNER JOIN to fetch trucks with defects. Following query runs perfectly fine.

  sql = "INNER JOIN inspected_trucks ON inspected_trucks.id = defects.associated_object_id"
  Defect.joins(sql).load

We faced a scenario in one of our applications with multiple polymorphic joins. We needed to build a single query which lists vehicle inspection time, truck or trailer number and defect name (if available on the inspected item).

  class Truck
    # attributes :number
    has_many :inspected_trucks
  end

  class Trailer
    # attributes :number
    has_many :inspected_trailers
  end

  class VehicleInspectionReport
    # attribites :inspection_time
    has_one :inspected_truck, class_name: "InspectedTruck"
    has_many :inspected_trailers, class_name: "InspectedTrailer"
  end

  class InspectedTruck
    belongs_to :truck
    has_many :defects, as: :associated_object
  end

  class InspectedTrailer
    belongs_to :trailer
    has_many :defects, as: :associated_object
  end

  class Defect
    # attributes :name
    belongs_to :associated_object, polymorphic: true
  end

The task here was to query VehicleInspectionReport joining other five different tables and select required attributes to show. But the challenge here was posed by polymorphic association.

We had to come up with a way to query InspectedTruck and InspectedTrailer as a single dataset. We identified the dataset a kind of Single Table Inheritance (STI) dataset. And came up with following subquery.

  SELECT id AS associated_object_id, 'InspectedTruck' AS associated_object_type, vehicle_inspection_report_id, truck_id, NULL trailer_id
  FROM inspected_trucks
    UNION
  SELECT id AS associated_object_id, 'InspectedTrailer' AS associated_object_type, vehicle_inspection_report_id, NULL truck_id, trailer_id
  FROM inspected_trailers

This subquery gave us all inspected items in a single dataset and we could refer this dataset in a form of STI.

We were then able to build the final query using above subquery.

Add a scope in VehicleInspectionReport to join inspected items.

  class VehicleInspectionReport
    # attribites :inspection_time

    INSPECTED_ITEMS_RAW_SQL = "(
                              SELECT id, 'InspectedTruck' AS object_type, vehicle_inspection_report_id, truck_id, NULL trailer_id
                                FROM inspected_trucks
                              UNION
                              SELECT id, 'InspectedTrailer' AS object_type, vehicle_inspection_report_id, NULL truck_id, trailer_id
                                FROM inspected_trailers
                            ) AS inspected_items"

    has_one :inspected_truck, class_name: "InspectedTruck"
    has_many :inspected_trailers, class_name: "InspectedTrailer"

    scope :joins_with_inspected_items, -> { joins("INNER JOIN #{INSPECTED_ITEMS_RAW_SQL} ON vehicle_inspection_reports.id = inspected_items.vehicle_inspection_report_id") }
  end

joins_with_inspected_items scope on VehicleInspectionReport will work in a way of joining a STI table (inspected_items) on VehicleInspectionReport. We can now chain any query which require inspected items. Example:

  VehicleInspectionReport.select("defects.id AS defect_id,
                                  defects.name AS description,
                                  trucks.truck_number AS truck_number,
                                  trailers.number AS trailer_number,
                                  vehicle_inspection_reports.inspection_time AS inspection_time")
        .joins_with_inspected_items
        .joins("LEFT JOIN defects ON inspected_items.id = defects.associated_object_id
                  AND defects.associated_object_type = inspected_items.object_type")
        .joins("LEFT JOIN trucks ON inspected_items.truck_id = trucks.id")
        .joins("LEFT JOIN trailers ON inspected_items.trailer_id = trailers.id")
        .where("inspected_items.id IS NOT NULL")
        .order('truck_number, trailer_number, inspection_time DESC')

The underlying concept here is to structure STI dataset from polymorphic architecture. Notice the use of inspected_items dataset in a form of STI using inspected_items.associated_object_id AND inspected_items.associated_object_type.


Rails 6 adds rails db:prepare to migrate or setup a database

This blog is part of our Rails 6 series. Rails 6.0 was recently released.

Rails 6 adds rails db:prepare to migrate or setup a database if it doesn’t exist.

Before Rails 6, we had to run the following tasks to set up the database.

# create the database
rails db:create

# run the migrations
rails db:migrate

# prepopulate the database with initial/default data
rails db:seed  

Rails 6

Rails 6, adds rails db:prepare to get rid of running all the above tasks individually. rails db:prepare first calls the migrate to run the migrations, but if the database doesn’t exist, migrate throws an ActiveRecord::NoDatabaseError. Once it is catched, it performs the following operations:

  • Creates the database.
  • Loads the schema.
  • Seeds the database.

Thus, rails db:prepare saves a lot of time spent on running database tasks individually while setting up an application and finishes it with just one command.

Here is the relevant pull request.


Rails 6.1 adds *_previously_was attribute methods

This blog is part of our Rails 6.1 series.

Rails 6.1 adds *_previously_was attribute methods for dirty tracking the previous attribute value after the model is saved or reset. *_previously_was returns the previous attribute value that was changed before the model was saved

Before Rails 6.1, to retrieve the previous attribute value, we used *_previous_change or previous_changes.

Here is how it can be used.

Rails 6.0.0

>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

>> user.name = "Sam"

# *_was returns the original value. In this example, the name was initially nil.
>> user.name_was
=> nil
>> user.save!

# After save, the original value is set to "Sam". To retrieve the
# previous value, we had to use `previous_changes`.
>> user.previous_changes[:name]
=> [nil, "Sam"]

Rails 6.1.0

>> user = User.find_by(name: "Sam")
=> #<User id: 1, name: "Sam", email: nil, created_at: "2019-10-14 17:53:06", updated_at: "2019-10-14 17:53:06">

>> user.name = "Nick"
>> user.name_was
=> "Sam"

>> user.save!

>> user.previous_changes[:name]
=> ["Sam", "Nick"]

# *_previously_was returns the previous value.
>> user.name_previously_was
=> "Sam"

# After reload, all the dirty tracking
# attributes is reset.
>> user.reload
>> user.name_previously_was
=> nil

Check out the pull request for more details on this.


Rails 6 adds guard against DNS Rebinding attacks

This blog is part of our Rails 6 series. Rails 6.0 was recently released.

In a DNS Rebinding attack, a malicious webpage runs client-side script when it is loaded, to attack endpoints within a given network.

What is DNS Rebinding attack?

DNS Rebinding can be summarized as follows.

  • An unsuspecting victim is tricked into loading rebinding.network which is resolved by a DNS server controlled by a malicious entity.
  • Victims web browser sends a DNS query and gets the real IP address, say 24.56.78.99 of http://rebinding.network. This DNS server also sets a very short TTL value ( say 1 second ) on the response so that the client won’t cache this response for long.
  • The script on this webpage cannot attack services running in local network due to CORS restrictions imposed by victims web browser. Instead it starts sending a suspicious POST request to http://rebinding.network/setup/reboot with a JSON payload {params: factory-reset}.
  • First few requests are indeed sent to 24.56.78.99 (real IP address), with the DNS info from the cache, but then the browser sends out a DNS query for rebinding.network when it observes that the cache has gone stale.
  • When the malicious DNS server gets the request for a second time, instead of responding with 24.56.78.99 (which is the real IP address of rebinding.network), it responds with 192.168.1.90, an address at which, a poorly secured smart device runs.

Using this exploit, an attacker is able to factory-reset a device which relied on security provided by local network.

This attack is explained in much more detail in this blog post.

How does it affect Rails?

Rails’s web console was particularly vulnerable to a Remote Code Execution (RCE) via a DNS Rebinding.

In this blog post, Ben Murphy goes into technical details of exploiting this vulnerability to open Calculator app (only works in OS X).

How does Rails 6 mitigate DNS Rebinding?

Rails mitigates DNS Rebinding attack by maintaining a whitelist of domains from which it can receive requests. This is achieved with a new HostAuthorization middleware. This middleware leverages the fact that HOST request header is a forbidden header.

# taken from Rails documentation

# Allow requests from subdomains like `www.product.com` and
# `beta1.product.com`.
Rails.application.config.hosts << ".*\.product\.com/"

In the above example, Rails would render a blocked host template, if it receives requests from domains outside of above whitelist.

In development environment, default whitelist includes 0.0.0.0/0, ::0 (CIDR notations for IPv4 and IPv6 default routes) and localhost. For all other environments, config.hosts is empty and host header checks are not done.