Rails 5 supports logging errors with tagged logging

This blog is part of our Rails 5 series.

We use tagged logging to better extract information from logs generated by Rails applications.

Consider a Rails 4.x application where the request id is used as a log tag by adding following in config/environments/production.rb.

config.log_tags = [:uuid]

The log generated for that application would look like:

[df88dbaa-50fd-4178-85d7-d66279ea33b6] Started GET "/posts" for ::1 at 2016-06-03 17:19:32 +0530
[df88dbaa-50fd-4178-85d7-d66279ea33b6] Processing by PostsController#index as HTML
[df88dbaa-50fd-4178-85d7-d66279ea33b6]   Post Load (0.2ms)  SELECT "posts".* FROM "posts"
[df88dbaa-50fd-4178-85d7-d66279ea33b6]   Rendered posts/index.html.erb within layouts/application (4.8ms)
[df88dbaa-50fd-4178-85d7-d66279ea33b6] Completed 500 Internal Server Error in 10ms (ActiveRecord: 0.2ms)
[df88dbaa-50fd-4178-85d7-d66279ea33b6]
ActionView::Template::Error (divided by 0):
    29: <br>
    30:
    31: <%= link_to 'New Post', new_post_path %>
    32: <%= 1/0 %>
  app/views/posts/index.html.erb:32:in `/'
  app/views/posts/index.html.erb:32:in `_app_views_posts_index_html_erb___110320845380431566_70214104632140'

As we can see the request id tag is not prepended to the lines containing error details. If we search the log file by the request id then the error details would not be shown.

In Rails 5 errors in logs show log tags as well to overcome the problem we saw above.

Please note that the log tag name for request id has changed in Rails 5. The setting would thus look like as shown below.

config.log_tags = [:request_id]

This is how the same log will look like in a Rails 5 application.

[7efb4d18-8e55-4d51-b31e-119f49f5a410] Started GET "/" for ::1 at 2016-06-03 17:24:59 +0530
[7efb4d18-8e55-4d51-b31e-119f49f5a410] Processing by PostsController#index as HTML
[7efb4d18-8e55-4d51-b31e-119f49f5a410]   Rendering posts/index.html.erb within layouts/application
[7efb4d18-8e55-4d51-b31e-119f49f5a410]   Post Load (0.9ms)  SELECT "posts".* FROM "posts"
[7efb4d18-8e55-4d51-b31e-119f49f5a410]   Rendered posts/index.html.erb within layouts/application (13.2ms)
[7efb4d18-8e55-4d51-b31e-119f49f5a410] Completed 500 Internal Server Error in 30ms (ActiveRecord: 0.9ms)
[7efb4d18-8e55-4d51-b31e-119f49f5a410]
[7efb4d18-8e55-4d51-b31e-119f49f5a410] ActionView::Template::Error (divided by 0):
[7efb4d18-8e55-4d51-b31e-119f49f5a410]     29: <br>
[7efb4d18-8e55-4d51-b31e-119f49f5a410]     30:
[7efb4d18-8e55-4d51-b31e-119f49f5a410]     31: <%= link_to 'New Post', new_post_path %>
[7efb4d18-8e55-4d51-b31e-119f49f5a410]     32: <%= 1/0 %>
[7efb4d18-8e55-4d51-b31e-119f49f5a410]
[7efb4d18-8e55-4d51-b31e-119f49f5a410] app/views/posts/index.html.erb:32:in `/'
[7efb4d18-8e55-4d51-b31e-119f49f5a410] app/views/posts/index.html.erb:32:in `_app_views_posts_index_html_erb___1136362343261984150_70232665530320'

Rails 5 makes sql statements even more colorful

This blog is part of our Rails 5 series.

In Rails 5, SQL statements have much more granular level of colortion .

INSERT statement

Font color for INSERT command is green

insert statement in green color

UPDATE and SELECT statement

Font color for UPDATE command is yellow and for SELECT it is blue.

update statement in yellow color

DELETE statement

Font color for DELETE command is red.

delete statement in red color

As you might have noticed from above that font color for transaction is cyan.

Rollback statement

Font color for Rollback transaction is red.

rollback statement in red color

Other statements

For custom SQL statements color is magenta and Model Load/exists color is cyan.

magenta

Rails 5 adds helpers method in controllers to ease usage of helper modules in controllers

This blog is part of our Rails 5 series.

Before Rails 5, when we wanted to use any of the helper methods in controllers we used to do the following.

module UsersHelper
  def full_name(user)
    user.first_name + user.last_name
  end
end

class UsersController < ApplicationController
  include UsersHelper

  def update
    @user = User.find params[:id]
    if @user.update_attributes(user_params)
      redirect_to user_path(@user), notice: "#{full_name(@user) is successfully updated}"
    else
      render :edit
    end
  end
end

Though this works, it adds all public methods of the included helper module in the controller.

This can lead to some of the methods in the helper module conflict with the methods in controllers.

Also if our helper module has dependency on other helpers, then we need to include all of the dependencies in our controller, otherwise it won’t work.

New way to call helper methods in Rails 5

In Rails 5, by using the new instance level helpers method in the controller, we can access helper methods in controllers.

module UsersHelper
  def full_name(user)
    user.first_name + user.last_name
  end
end

class UsersController < ApplicationController

  def update
    @user = User.find params[:id]
    if @user.update_attributes(user_params)
      notice = "#{helpers.full_name(@user) is successfully updated}"
      redirect_to user_path(@user), notice: notice
    else
      render :edit
    end
  end
end

This removes some of the drawbacks of including helper modules and is much cleaner solution.

Rails 5 supports adding comments in migrations

This blog is part of our Rails 5 series.

Database schemas change rapidly as project progresses. And it can be difficult to track purpose of each table and each column in a large project with multiple team members.

The solution for this problem is to document data models right from Rails migrations.

Solution in Rails 4

You can add comments in Rails 4.x migrations using gems like migration_comments and pg_comment.

Solution in Rails 5

Rails 5 allows to specify comments for tables, column and indexes in migrations.

These comments are stored in database itself.

Currently only MySQL and PostgreSQL supports adding comments.

We can add comments in migration as shown below.

class CreateProducts < ActiveRecord::Migration[5.0]
  def change
    create_table :products, comment: 'Products table' do |t|
      t.string :name, comment: 'Name of the product'
      t.string :barcode, comment: 'Barcode of the product'
      t.string :description, comment: 'Product details'
      t.float :msrp, comment: 'Maximum Retail Price'
      t.float :our_price, comment: 'Selling price'

      t.timestamps
    end

    add_index :products, :name,
              name: 'index_products_on_name',
              unique: true,
              comment: 'Index used to lookup product by name.'
  end
end

When we run above migration output will look as shown below.

  rails_5_app rake db:migrate:up VERSION=20160429081156
== 20160429081156 CreateProducts: migrating ===================================
-- create_table(:products, {:comment=>"Products table"})
   -> 0.0119s
-- add_index(:products, :name, {:name=>"index_products_on_name", :unique=>true, :comment=>"Index used to lookup product by name."})
   -> 0.0038s
== 20160429081156 CreateProducts: migrated (0.0159s) ==========================

The comments are also dumped in db/schema.rb file for PostgreSQL and MySQL.

db/schema.rb of application will have following content after running products table migration .

ActiveRecord::Schema.define(version: 20160429081156) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "products", force: :cascade, comment: "Products table" do |t|
      t.string   "name",                     comment: "Name of the product"
      t.string   "barcode",                  comment: "Barcode of the product"
      t.string   "description",              comment: "Product details"
      t.float    "msrp",                     comment: "Maximum Retail Price"
      t.float    "our_price",                comment: "Selling price"
      t.datetime "created_at",  null: false
      t.datetime "updated_at",  null: false
      t.index ["name"], name: "index_products_on_name", unique: true, using: :btree, comment: "Index used to lookup product by name."
    end
end

We can view these comments with Database Administration Tools such as MySQL Workbench or PgAdmin III.

PgAdmin III will show database structure with comments as shown below.

-- Table: products

-- DROP TABLE products;

CREATE TABLE products
(
  id serial NOT NULL,
  name character varying, -- Name of the product
  barcode character varying, -- Barcode of the product
  description character varying, -- Product details with string data type
  msrp double precision, -- Maximum Retail price
  our_price double precision, -- Selling price
  created_at timestamp without time zone NOT NULL,
  updated_at timestamp without time zone NOT NULL,
  CONSTRAINT products_pkey PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE products
  OWNER TO postgres;
COMMENT ON TABLE products
  IS 'Products table';
COMMENT ON COLUMN products.name IS 'Name of the product';
COMMENT ON COLUMN products.barcode IS 'Barcode of the product';
COMMENT ON COLUMN products.description IS 'Product details with string data type';
COMMENT ON COLUMN products.msrp IS 'Maximum Retail price';
COMMENT ON COLUMN products.our_price IS 'Selling price';


-- Index: index_products_on_name

-- DROP INDEX index_products_on_name;

CREATE UNIQUE INDEX index_products_on_name
  ON products
  USING btree
  (name COLLATE pg_catalog."default");
COMMENT ON INDEX index_products_on_name
  IS 'Index used to lookup product by name.';

If we update comments through migrations, corresponding comments will be updated in db/schema.rb file.

Rails 5 allows UUID as column type in create_join_table

This blog is part of our Rails 5 series.

In Rails 4.x create_join_table allows us to create new join table with name given in first two arguments.

class CreateJoinTableCustomerProduct < ActiveRecord::Migration
  def change
    create_join_table(:customers, :products)
  end
end

It will create new join table customer_products with columns customer_id and product_id. We can also use block with create_join_table.

class CreateJoinTableCustomerProduct < ActiveRecord::Migration
  def change
    create_join_table :customers, :products do |t|
      t.index :customer_id
      t.index :product_id
    end
  end
end

However create_join_table won’t allows us to define the column type. It will always create column of integer type. Because Rails 4.x ,by default, supports primary key column type as an autoincrement integer.

If we wish to set uuid as a column type, then create_join_table won’t work. In such case we have to create join table manually using create_table.

Here is an example with Rails 4.x.

class CreateJoinTableCustomerProduct < ActiveRecord::Migration
  def change
    create_table :customer_products do |t|
      t.uuid :customer_id
      t.uuid :product_id
    end
  end
end

Rails 5 allows to have UUID as column type in join table

Rails 5 has started supporting UUID as a column type for primary key, so create_join_table should also support UUID as a column type instead of only integers. Hence now Rails 5 allows us to use UUID as a column type with create_join_table.

Here is revised example.

class CreateJoinTableCustomerProduct < ActiveRecord::Migration[5.0]
  def change
    create_join_table(:customers, :products, column_options: {type: :uuid})
  end
end

Rails 5 adds another base class Application Job for jobs

This blog is part of our Rails 5 series.

Rails 5 has added another base class ApplicationJob which inherits from ActiveJob::Base. Now by default all new Rails 5 applications will have application_job.rb.

# app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
end

In Rails 4.x if we want to use ActiveJob then first we need to generate a job and all the generated jobs directly inherit from ActiveJob::Base.

# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ActiveJob::Base
  queue_as :default

  def perform(*guests)
    # Do something later
  end
end

Rails 5 adds explicit base class ApplicationJob for ActiveJob. As you can see this is not a big change but it is a good change in terms of being consistent with how controllers have ApplicationController and models have ApplicationRecord.

Now ApplicationJob will be a single place to apply all kind of customizations and extensions needed for an application, instead of applying patch on ActiveJob::Base.

Upgrading from Rails 4.x

When upgrading from Rails 4.x to Rails 5 we need to create application_job.rb file in app/jobs/ and add the following content.

# app/jobs/application_job.rb
class ApplicationJob < ActiveJob::Base
end

We also need to change all the existing job classes to inherit from ApplicationJob instead of ActiveJob::Base.

Here is the revised code of GuestCleanupJob class.

# app/jobs/guests_cleanup_job.rb
class GuestsCleanupJob < ApplicationJob
  queue_as :default

  def perform(*guests)
    # Do something later
  end
end

Rails 5 allows to update records in AR Relation with callbacks and validations

This blog is part of our Rails 5 series.

The update_all method when called on an ActiveRecord::Relation object updates all the records without invoking any callbacks and validations on the records being updated.

Rails 5 supports update method on an ActiveRecord::Relation object that runs callbacks and validations on all the records in the relation.

people = Person.where(country: 'US')
people.update(language: 'English', currency: 'USD')

Internally, the above code runs update method on each Person record whose country is 'US'.

Let’s see what happens when update is called on a relation in which validations fail on few records.

We have a Note model. For simplicity let’s add a validation that note_text field cannot be blank for first three records.

class Note < ApplicationRecord
  validate :valid_note
  
  def valid_note
   errors.add(:note_text, "note_text is blank") if id <= 3 && note_text.blank?
  end
end

Now let’s try and update all the records with blank note_text.

 > Note.all.update(note_text: '')
  Note Load (0.3ms)  SELECT `notes`.* FROM `notes`
   (0.1ms)  BEGIN
   (0.1ms)  ROLLBACK
   (0.1ms)  BEGIN
   (0.1ms)  ROLLBACK
   (0.1ms)  BEGIN
   (0.1ms)  ROLLBACK
   (0.1ms)  BEGIN
  SQL (2.9ms)  UPDATE `notes` SET `note_text` = '', `updated_at` = '2016-06-16 19:42:21' WHERE `notes`.`id` = 3
   (0.7ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (0.3ms)  UPDATE `notes` SET `note_text` = '', `updated_at` = '2016-06-16 19:42:21' WHERE `notes`.`id` = 4
   (1.2ms)  COMMIT   
   (0.1ms)  BEGIN
  SQL (0.3ms)  UPDATE `notes` SET `note_text` = '', `updated_at` = '2016-06-16 19:42:21' WHERE `notes`.`id` = 5
   (0.3ms)  COMMIT
   (0.1ms)  BEGIN
  SQL (3.4ms)  UPDATE `notes` SET `note_text` = '', `updated_at` = '2016-06-16 19:42:21' WHERE `notes`.`id` = 6
   (0.2ms)  COMMIT
   
 => [#<Note id: 1, user_id: 1, note_text: "", created_at: "2016-06-03 10:02:54", updated_at: "2016-06-16 19:42:21">,
 #<Note id: 2, user_id: 1, note_text: "", created_at: "2016-06-03 10:03:54", updated_at: "2016-06-16 19:42:21">,
 #<Note id: 3, user_id: 1, note_text: "", created_at: "2016-06-03 12:35:20", updated_at: "2016-06-03 12:35:20">,
 #<Note id: 4, user_id: 1, note_text: "", created_at: "2016-06-03 14:15:15", updated_at: "2016-06-16 19:14:20">,
 #<Note id: 5, user_id: 1, note_text: "", created_at: "2016-06-03 14:15:41", updated_at: "2016-06-16 19:42:21">,
 #<Note id: 6, user_id: 1, note_text: "", created_at: "2016-06-03 14:16:20", updated_at: "2016-06-16 19:42:21">] 

We can see that failure of validations on records in the relation does not stop us from updating the valid records.

Also the return value of update on AR Relation is an array of records in the relation. We can see that the attributes in these records hold the values that we wanted to have after the update.

For example in the above mentioned case, we can see that in the returned array, the records with ids 1, 2 and 3 have blank note_text values even though those records weren’t updated.

Hence we may not be able to rely on the return value to know if the update is successful on any particular record.

For scenarios where running validations and callbacks is not important and/or where performance is a concern it is advisable to use update_all method instead of update method.

Rails 5 prevents destructive action on production database

This blog is part of our Rails 5 series.

Sometimes while debugging production issue mistakenly developers execute commands like RAILS_ENV=production rake db:schema:load. This wipes out data in production.

Users of heroku download all the config variables to local machine to debug production problem and sometimes developers mistakenly execute commands which wipes out production data. This has happened enough number of times to heroku users that Richard Schneeman of heroku decided to do something about this issue.

Rails 5 prevents destructive action on production database

Rails 5 has added a new table ar_internal_metadata to store environment version which is used at the time of migrating the database.

When the first time rake db:migrate is executed then new table stores the value production. Now whenever we load database schema or database structure by running rake db:schema:load or rake db:structure:load Rails will check if Rails environment is “production” or not. If not then Rails will raise an exception and thus preventing the data wipeout.

To skip this environment check we can manually pass DISABLE_DATABASE_ENVIRONMENT_CHECK=1 as an argument with load schema/structure db command.

Here is an example of running rake db:schema:load when development db is pointing to production database.

$ rake db:schema:load

rake aborted!
ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your 'production' database.
If you are sure you want to continue, run the same command with the environment variable:
DISABLE_DATABASE_ENVIRONMENT_CHECK=1

As we can see Rails prevented data wipeout in production.

This is one of those features which hopefully you won’t notice. However if you happen to do something destructive to your production database then this feature will come in handy.

Rails 5 adds finish option in find_in_batches

This blog is part of our Rails 5 series.

In Rails 4.x we had start option in find_in_batches method.

Person.find_in_batches(start: 1000, batch_size: 2000) do |group|
  group.each { |person| person.party_all_night! }
end

The above code provides batches of Person starting from record whose value of primary key is equal to 1000.

There is no end value for primary key. That means in the above case all the records that have primary key value greater than 1000 are fetched.

Rails 5 introduces finish option that serves as an upper limit to the primary key value in the records being fetched.

Person.find_in_batches(start: 1000, finish: 9500, batch_size: 2000) do |group|
  group.each { |person| person.party_all_night! }
end

The above code ensures that no record in any of the batches has the primary key value greater than 9500.

Rails 5 introduces country_zones helper method to ActiveSupport::TimeZone

This blog is part of our Rails 5 series.

Before Rails 5, we could fetch all time zones for US by using us_zones method as follows.

> puts ActiveSupport::TimeZone.us_zones.map(&:to_s)
(GMT-10:00) Hawaii
(GMT-09:00) Alaska
(GMT-08:00) Pacific Time (US & Canada)
(GMT-07:00) Arizona
(GMT-07:00) Mountain Time (US & Canada)
(GMT-06:00) Central Time (US & Canada)
(GMT-05:00) Eastern Time (US & Canada)
(GMT-05:00) Indiana (East)

Such functionality of getting all the TimeZone objects for a country was implemented only for one country, US.

The TimeZone class internally uses the TzInfo gem which does have an api for providing timezones for all the countries.

Realizing this, the Rails community decided to introduce a helper method country_zones to ActiveSupport::TimeZone class that is able to fetch a collection of TimeZone objects belonging to a country specified by its ISO 3166-1 Alpha2 code.

> puts ActiveSupport::TimeZone.country_zones('us').map(&:to_s)
(GMT-10:00) Hawaii
(GMT-09:00) Alaska
(GMT-08:00) Pacific Time (US & Canada)
(GMT-07:00) Arizona
(GMT-07:00) Mountain Time (US & Canada)
(GMT-06:00) Central Time (US & Canada)
(GMT-05:00) Eastern Time (US & Canada)
(GMT-05:00) Indiana (East)

>puts ActiveSupport::TimeZone.country_zones('fr').map(&:to_s)
 (GMT+01:00) Paris

Rails 5 provides fragment caching in Action Mailer views

This blog is part of our Rails 5 series.

Fragment cache helps in caching parts of the view instead of caching the entire view. Fragment caching is used when different parts of the view need to be cached and expired separately. Before Rails 5, fragment caching was supported only in Action View templates.

Rails 5 provides fragment caching in Action Mailer views . To use this feature, we need to configure our application as follows.

config.action_mailer.perform_caching = true

This configuration specifies whether mailer templates should perform fragment caching or not. By default, this is set to false for all environments.

Fragment caching in views

We can do caching in mailer views similar to application views using cache method. Following example shows usage of fragment caching in mailer view of the welcome mail.

<body>

 <% cache 'signup-text' do %>
   <h1>Welcome to <%= @company.name %></h1>
   <p>You have successfully signed up to <%= @company.name %>, Your username is:
 <% end %>

     <%= @user.login %>.
     <br />
   </p>

 <%= render :partial => 'footer' %>

</body>

When we render view for the first time, we can see cache digest of the view and its partial.

  Cache digest for app/views/user_mailer/_footer.erb: 7313427d26cc1f701b1e0212498cee38
  Cache digest for app/views/user_mailer/welcome_email.html.erb: 30efff0173fd5f29a88ffe79a9eab617
  Rendered user_mailer/_footer.erb (0.3ms)
  Rendered user_mailer/welcome_email.html.erb (26.1ms)
  Cache digest for app/views/user_mailer/welcome_email.text.erb: 77f41fe6159c5736ab2026a44bc8de55
  Rendered user_mailer/welcome_email.text.erb (0.2ms)
UserMailer#welcome_email: processed outbound mail in 190.3ms

We can also use fragment caching in partials of the action mailer views with cache method. Fragment caching is also supported in multipart emails.

Rails 5 adds OR support in Active Record

This blog is part of our Rails 5 series.

Rails 5 has added OR method to Active Relation for generating queries with OR clause.

>> Post.where(id: 1).or(Post.where(title: 'Learn Rails'))
   SELECT "posts".* FROM "posts" WHERE ("posts"."id" = ? OR "posts"."title" = ?)  [["id", 1], ["title", "Learn Rails"]]

=> <ActiveRecord::Relation [#<Post id: 1, title: 'Rails'>]>

This returns ActiveRecord::Relation object, which is logical union of two relations.

Some Examples of OR usage

With group and having

>> posts = Post.group(:user_id)
>> posts.having('id > 3').or(posts.having('title like "Hi%"'))
SELECT "posts".* FROM "posts" GROUP BY "posts"."user_id" HAVING ((id > 2) OR (title like "Rails%"))

=> <ActiveRecord::Relation [#<Post id: 3, title: "Hi", user_id: 4>,
#<Post id: 6, title: "Another new blog", user_id: 6>]>

With scope

class Post < ApplicationRecord
  scope :contains_blog_keyword, -> { where("title LIKE '%blog%'") }
end

>> Post.contains_blog_keyword.or(Post.where('id > 3'))
SELECT "posts".* FROM "posts" WHERE ((title LIKE '%blog%') OR (id > 3))

=> <ActiveRecord::Relation [#<Post id: 4, title: "A new blog", user_id: 6>,
#<Post id: 5, title: "Rails blog", user_id: 4>,
#<Post id: 6, title: "Another new blog", user_id: 6>]>

With combination of scopes

class Post < ApplicationRecord

  scope :contains_blog_keyword, -> { where("title LIKE '%blog%'") }
  scope :id_greater_than, -> (id) {where("id > ?", id)}

  scope :containing_blog_keyword_with_id_greater_than, ->(id) { contains_blog_keyword.or(id_greater_than(id)) }
end

>> Post.containing_blog_keyword_with_id_greater_than(2)
SELECT "posts".* FROM "posts" WHERE ((title LIKE '%blog%') OR (id > 2)) ORDER BY "posts"."id" DESC

=> <ActiveRecord::Relation [#<Post id: 3, title: "Hi", user_id: 6>,
#<Post id: 4, title: "A new blog", user_id: 6>,
#<Post id: 5, title: "Another new blog", user_id: 6>,
<#Post id: 6, title: "Another new blog", user_id: 6>]>

Constraints for using OR method

The two relations must be structurally compatible, they must be scoping the same model, and they must differ only by WHERE or HAVING.

In order to use OR operator, neither relation should have a limit, offset, or distinct.

>> Post.where(id: 1).limit(1).or(Post.where(:id => [2, 3]))

ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:limit]

When limit, offset or distinct is passed only with one relation, then it throws ArgumentError as shown above.

As of now, we can use limit, offset or distinct when passed with both the relations and with same the parameters.

>> Post.where(id: 1).limit(2).or(Post.where(:id => [2, 3]).limit(2))

SELECT  "posts".* FROM "posts" WHERE ("posts"."id" = ? OR "posts"."id" IN (2, 3)) LIMIT ?  [["id", 1], ["LIMIT", 2]]

=> <ActiveRecord::Relation [#<Post id: 1, title: 'Blog', user_id: 3, published: true>,
#<Post id: 2, title: 'Rails 5 post', user_id: 4, published: true>]>

There is an issue open in which discussions are ongoing regarding completely stopping usage of limit, offset or distinct when using with or.

Rails 5 adds ArrayInquirer and provides friendlier way to check contents in an array

This blog is part of our Rails 5 series.

Rails 5 introduces Array Inquirer that wraps an array object and provides friendlier methods to check for the presence of elements that can be either a string or a symbol.

pets = ActiveSupport::ArrayInquirer.new([:cat, :dog, 'rabbit'])

> pets.cat?
#=> true

> pets.rabbit?
#=> true

> pets.elephant?
#=> false

Array Inquirer also has any? method to check for the presence of any of the passed arguments as elements in the array.

pets = ActiveSupport::ArrayInquirer.new([:cat, :dog, 'rabbit'])

> pets.any?(:cat, :dog)
#=> true

> pets.any?('cat', 'dog')
#=> true

> pets.any?(:rabbit, 'elephant')
#=> true

> pets.any?('elephant', :tiger)
#=> false

Since ArrayInquirer class inherits from Array class, its any? method performs same as any? method of Array class when no arguments are passed.

pets = ActiveSupport::ArrayInquirer.new([:cat, :dog, 'rabbit'])

> pets.any?
#=> true

> pets.any? { |pet| pet.to_s == 'dog' }
#=> true

Use inquiry method on array to fetch Array Inquirer version

For any given array we can have its Array Inquirer version by calling inquiry method on it.

pets = [:cat, :dog, 'rabbit'].inquiry

> pets.cat?
#=> true

> pets.rabbit?
#=> true

> pets.elephant?
#=> false

Usage of Array Inquirer in Rails code

Rails 5 makes use of Array Inquirer and provides a better way of checking for the presence of given variant.

Before Rails 5 code looked like this.

request.variant = :phone

> request.variant
#=> [:phone]

> request.variant.include?(:phone)
#=> true

> request.variant.include?('phone')
#=> false

Corresponding Rails 5 version is below.

request.variant = :phone

> request.variant.phone?
#=> true

> request.variant.tablet?
#=> false

Rails 5 renamed transactional fixtures to transactional tests

This blog is part of our Rails 5 series.

In Rails 4.x we have transactional fixtures that wrap each test in a database transaction. This transaction rollbacks all the changes at the end of the test. It means the state of the database, before the test is same as after the test is done.

By default this functionality is enabled. We can choose to disable it in a test case class by setting the class attribute use_transactional_fixtures to false

class FooTest < ActiveSupport::TestCase
  self.use_transactional_fixtures = false
end

Rails also comes with fixtures for tests. So it may seem that use_transactional_fixtures has something to do with the Rails fixtures. A lot of people don’t use fixtures and they think that they should disable use_transactional_fixtures because they do not use fixtures.

To overcome this confusion, Rails 5 has renamed transactional fixtures to transactional tests making it clear that it has nothing to do with the fixtures used in tests.

In Rails 5, the above example will be written as follows.

class FooTest < ActiveSupport::TestCase
  self.use_transactional_tests = false
end

Data exchange between React Native app and WebView

A project we recently worked on needed some complicated charts. We built those charts using JavaScript library and it worked fine on browsers.

Now we need to build mobile app using React Native and it would take a lot of time to build those charts natively. So we decided to use WebView to render the html pages which already displays charts nicely.

React Native comes with WebView component by default. So rendering the html page using WebView was easy. However, once the page is rendered the React Native app could not exchange any data with the web page.

In this blog post we’ll discuss how to make React Native app communicate with the pages rendered using WebView with the help of react-native-webview-bridge library.

What is React Native WebView Bridge ?

react-native-webview-bridge is a wrapper on top of React Native’s WebView component with some extra features.

First we need to install react-native-webview-bridge package.

npm install react-native-webview-bridge --save

Next we need to import the WebView bridge module.

// ES6
import WebViewBridge from 'react-native-webview-bridge';

// ES5
let WebViewBridge = require('react-native-webview-bridge')

Now let’s create a basic React component. This component will be responsible for rendering html page using WebView.

React.createClass({

  render: function() {
    return (
      <WebViewBridge
        ref="webviewbridge"
        onBridgeMessage={this.onBridgeMessage.bind(this)}
        source={ {uri: "https://www.example.com/charts"} } />
    );
  }
});

After the component is mounted then we will send data to web view.

componentDidMount() {
  let chartData = {data: '...'};

  // Send this chart data over to web view after 5 seconds.
  setTimeout(() => {
    this.refs.webviewbridge.sendToBridge(JSON.stringify(data));
  }, 5000);
},

Next, We will add code to receive data from web view.

onBridgeMessage: function (webViewData) {
  let jsonData = JSON.parse(webViewData);

  if (jsonData.success) {
    Alert.alert(jsonData.message);
  }
  console.log('data received', webViewData, jsonData);
  //.. do some react native stuff when data is received
}

At this time code should look something like this.

React.createClass({

  componentDidMount() {
    let chartData = {data: '...'};

    // Send this chart data over to web view after 5 seconds.
    setTimeout(() => {
      this.refs.webviewbridge.sendToBridge(JSON.stringify(data));
    }, 5000);
  },

  render: function() {
    return (
      <WebViewBridge
        ref="webviewbridge"
        onBridgeMessage={this.onBridgeMessage.bind(this)}
        source={ {
          uri: "https://www.example.com/charts"
        } } />
    );
  },

  onBridgeMessage: function (webViewData) {
    let jsonData = JSON.parse(webViewData);

    if (jsonData.success) {
      Alert.alert(jsonData.message);
    }
    console.log('data received', webViewData, jsonData);
    //.. do some react native stuff when data is received
  }
});

Okay, We’ve added all the React Native side of code. We now need to add some JavaScript code on our web page to complete the functionality.

Why do we need to add JavaScript snippet on my web page?

This is a two way data exchange scenario. When our React Native app sends any data, this JavaScript snippet will parse that data and will trigger functions accordingly. We’ll also be able to send some data back to React Native app from JavaScript.

The example in the README of WebViewBridge library shows how to inject JavaScript snippet in React component. However, we prefer JavaScript code to be added to web page directly since it provides more control and flexibility.

Coming back to our implementation, Let’s now add the snippet in our web page.

<script>
 (function () {
    if (WebViewBridge) {

      // This function gets triggered when data received from React Native app.
      WebViewBridge.onMessage = function (reactNativeData) {

        // Converts the payload in JSON format.
        var jsonData = JSON.parse(reactNativeData);

        // Passes data to charts for rendering
        renderChart(jsonData.data);

        // Data to send from web view to React Native app.
        var dataToSend = JSON.stringify({success: true, message: 'Data received'});

        // Keep calm and send the data.
        WebViewBridge.send(dataToSend);
      };
    }
  }())
</script>

Done! We’ve acheived our goal of having a two way communication channel between our React Native app and the web page.

Checkout this link for more examples of how to use WebView Bridge.

Rails 5 adds ignored_columns for Active Record

This blog is part of our Rails 5 series.

Somtimes we need to ignore a database column. However Rails 4.x doesn’t have any officially defined method which ignores a database column from Active Record. We can apply our patch on model to ignore certain columns.

class User < ActiveRecord::Base

  # Ignoring employee_email column
  def self.columns
    super.reject {|column| column.name == 'employee_email'}
  end

end

Rails 5 added ignored_columns

Rails 5 has added ignored_columns to ActiveRecord::Base class.

Here is revised code after using ignored_columns method.

class User < ApplicationRecord
  self.ignored_columns = %w(employee_email)
end

Rails 5 adds a hidden field on collection radio buttons

This blog is part of our Rails 5 series.

Consider the following form which has only one input role_id which is accepted through collection_radio_button.

<%= form_for(@user) do |f| %>
  <%= f.collection_radio_buttons :role_id, @roles, :id, :name %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

In the controller, we can access role_id using the strong parameters.

def user_params
  params.require(:user).permit(:role_id)
end

When we try to submit this form without selecting any radio button in Rails 4.x, we will get 400 Bad Request error with following message.

ActionController::ParameterMissing (param is missing or the value is empty: user):.

This is because following parameters were sent to server in Rails 4.x .

Parameters: {"utf8"=>"✓", "authenticity_token"=>"...", "commit"=>"Create User"}

According to HTML specification, when multiple parameters are passed to collection_radio_buttons and no option is selected, web browsers do not send any value to the server.

Solution in Rails 5

Rails 5 adds hidden field on the collection_radio_buttons to avoid raising an error when the only input on the form is collection_radio_buttons. The hidden field has the same name as collection radio button and has blank value.

Following parameters will be sent to server in Rails 5 when above user form is submitted:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"...", "user"=>{"role_id"=>""}, "commit"=>"Create User"}

In case we don’t want the helper to generate this hidden field, we can specify include_hidden: false.

<%= f.collection_radio_buttons :role_id, Role.all, :id, :name, include_hidden: false %>

Rails 5 extracted attributes assignment from Active Record to Active Model

This blog is part of our Rails 5 series.

Before Rails 5, we could use assign_attributes and have bulk assignment of attributes only for objects whose classes are inherited from ActiveRecord::Base class.

In Rails 5 we can make use of assign_attributes method and have bulk assignment of attributes even for objects whose classes are not not inherited from ActiveRecord::Base.

This is possible because the attributes assignment code is now moved from ActiveRecord::AttributeAssignment to ActiveModel::AttributeAssignment module.

To have this up and running, we need to include ActiveModel::AttributeAssignment module to our class.

class User
  include ActiveModel::AttributeAssignment
  attr_accessor :email, :first_name, :last_name
end

user = User.new
user.assign_attributes({email:      'sam@example.com',
                        first_name: 'Sam',
                        last_name:  'Smith'})
> user.email
#=> "sam@example.com"
> user.first_name
#=> "Sam"

Rails 5 supports configuring Active Job backend adapter for each job

This blog is part of our Rails 5 series.

Before Rails 5 we had ability to configure Active Job queue_adapter at an application level. If we want to use sidekiq as our backend queue adapter we would configure it as following.

config.active_job.queue_adapter = :sidekiq

This queue_adapter would be applicable to all jobs.

Rails 5 provides ability to configure queue_adapter on a per job basis. It means queue_adapter for one job can be different to that of the other job.

Let’s suppose we have two jobs in our brand new Rails 5 application. EmailJob is responsible for processing basic emails and NewsletterJob sends out news letters.

class EmailJob < ActiveJob::Base
  self.queue_adapter = :sidekiq
end

class NewletterJob < ActiveJob::Base
end

EmailJob.queue_adapter
 => #<ActiveJob::QueueAdapters::SidekiqAdapter:0x007fb3d0b2e4a0>

NewletterJob.queue_adapter
 => #<ActiveJob::QueueAdapters::AsyncAdapter:0x007fb3d0c61b88>

We are now able to configure sidekiq queue adapter for EmailJob. In case of NewsletterJob we fallback to the global default adapter which in case of a new Rails 5 app is async.

Moreover, in Rails 5, when one job inherits other job, then queue adapter of the parent job gets persisted in the child job unless child job has configuration to change queue adapter.

Since news letters are email jobs we can make NewsLetterJob inherit from EmailJob.

Below is an example where EmailJob is using rescue while NewsLetterJob is using sidekiq.

class EmailJob < ActiveJob::Base
  self.queue_adapter = :resque
end

class NewsletterJob < EmailJob
end

EmailJob.queue_adapter
 => #<ActiveJob::QueueAdapters::ResqueAdapter:0x007fe137ede2a0>

NewsletterJob.queue_adapter
 => #<ActiveJob::QueueAdapters::ResqueAdapter:0x007fe137ede2a0>

class NewsletterJob < EmailJob
  self.queue_adapter = :sidekiq
end

NewsletterJob.queue_adapter
 => #<ActiveJob::QueueAdapters::SidekiqAdapter:0x007fb3d0b2e4a0>

Rails 5 accepts 1 or true for acceptance validation

This blog is part of our Rails 5 series.

validates_acceptance_of is a good validation tool for asking users to accept “terms of service” or similar items.

Before Rails 5, the only acceptable value for a validates_acceptance_of validation was 1.

class User < ActiveRecord::Base
  validates_acceptance_of :terms_of_service
end

> user = User.new(terms_of_service: "1")
> user.valid?
#=> true

Having acceptable value of 1 does cause some ambiguity because general purpose of acceptance validation is for attributes that hold boolean values.

So in order to have true as acceptance value we had to pass accept option to validates_acceptance_of as shown below.

class User < ActiveRecord::Base
  validates_acceptance_of :terms_of_service, accept: true
end

> user = User.new(terms_of_service: true)
> user.valid?
#=> true

> user.terms_of_service = '1'
> user.valid?
#=> false

Now this comes with the cost that 1 is no longer an acceptable value.

In Rails 5, we have true as a default value for acceptance along with the already existing acceptable value of 1.

In Rails 5 the previous example would look like as shown below.

class User < ActiveRecord::Base
  validates_acceptance_of :terms_of_service
end

> user = User.new(terms_of_service: true)
> user.valid?
#=> true

> user.terms_of_service = '1'
> user.valid?
#=> true

Rails 5 allows user to have custom set of acceptable values

In Rails 5, :accept option of validates_acceptance_of method supports an array of values unlike a single value that we had before.

So in our example if we are to validate our terms_of_service attribute with any of true, "y", "yes" we could have our validation as follows.

class User < ActiveRecord::Base
  validates_acceptance_of :terms_of_service, accept: [true, "y", "yes"]
end

> user = User.new(terms_of_service: true)
> user.valid?
#=> true

> user.terms_of_service = 'y'
> user.valid?
#=> true

> user.terms_of_service = 'yes'
> user.valid?
#=> true