Rails 5 fixes ambiguous column issue for projected fields in group by query

This blog is part of our Rails 5 series.

users(:id, :name)
posts(:id, :title, :user_id)
comments(:id, :description, :user_id, :post_id)

>> Post.joins(:comments).group(:user_id).count
Mysql2::Error: Column 'user_id' in field list is ambiguous: SELECT COUNT(*) AS count_all, user_id AS user_id FROM `posts` INNER JOIN `comments` ON `comments`.`post_id` = `posts`.`id` GROUP BY user_id

As we can see user_id has conflict in both projection and GROUP BY as they are not prepended with the table name posts in the generated SQL and thus, raising SQL error Column 'user_id' in field list is ambiguous.

Fix in Rails 5

This issue has been addressed in Rails 5 with this pull request.

With this fix, we can now group by columns having same name in both the tables.

users(:id, :name)
posts(:id, :title, :user_id)
comments(:id, :description, :user_id, :post_id)

>> Post.joins(:comments).group(:user_id).count
SELECT COUNT(*) AS count_all, "posts"."user_id" AS posts_user_id FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" GROUP BY "posts"."user_id"

=> { 1 => 1 }

This shows that now both projection and Group By are prepended with the posts table name and hence fixing the conflict.

Rails 5 adds Expression Indexes and Operator Classes support for PostgreSQL

This blog is part of our Rails 5 series.

Let’s assume that in our health care application we have a page which shows all Patients. This page also has a filter and it allows us to filter patients by their name.

We could implement the filter as shown here.

Patient.where("lower(first_name) = ?", first_name.downcase)

There might be many users with the same name. In such cases, to speed up the search process, we can add an index. But, adding a regular index will not trigger an index scan since we are using an expression in the where clause i.e lower(name). In such cases, we can leverage expression indexes given by PostgreSQL.

Before Rails 5 adding an expression index is not straightforward since the migrate api does not support it. In order to add one we would need to ditch schema.rb and start using structure.sql. We would also need to add following migration.

def up
  execute <<-SQL
    CREATE INDEX patient_lower_name_idx ON patients (lower(name));
  SQL
end

def down
  execute <<-SQL
    DROP INDEX patient_lower_name_idx;
  SQL
end

Rails 5 adds support for expression indexes

Rails 5 provides ability to add an expression index using add_index method as follows:

def change
  add_index :patients,
            'lower(last_name)',
            name: "index_patients_on_name_unique",
            unique: true
end

And we also get to keep schema.rb.

Time goes on. everyone is happy with the search functionality until one day a new requirement comes along which is, in short, to have partial matches on patient names.

We modify our search as follows:

User.where("lower(name) like ?", "%#{name.downcase}%")

Since the query is different from before, PostgreSQL query planner will not take the already existing btree index into account and will revert to a sequential scan.

Quoting directly from Postgresql documents,

'The operator classes text_pattern_ops, varchar_pattern_ops, and bpchar_pattern_ops support B-tree indexes on the types text, varchar, and char respectively. The difference from the default operator classes is that the values are compared strictly character by character rather than according to the locale-specific collation rules. This makes these operator classes suitable for use by queries involving pattern matching expressions (LIKE or POSIX regular expressions) when the database does not use the standard "C" locale.'

We need to add an operator class to the previous index for the query planner to utilize the index that we created earlier.

Rails 5 adds support for specifying operator classes on expression indexes

In order to add an index with an operator class we could write our migration as shown below.

def change
  remove_index :patients, name: :index_patients_on_name_unique
  add_index :patients,  'lower(last_name) varchar_pattern_ops',
                        name: "index_patients_on_name_unique",
                        unique: true
end

Attach arbitrary metadata to an Active Job in Rails 5

This blog is part of our Rails 5 series.

Rails 4.2 came with built-in support for executing jobs in the background using Active Job. Along with many enhancements to Active Job, Rails 5 provides the ability to attach arbitrary metadata to any job.

Consider the scenario where we would like to get notified when a job has failed for more than three times. With the new enhancement, we can make it work by overriding serialize and deserialize methods of the job class.

class DeliverWebhookJob < ActiveJob::Base
  def serialize
    super.merge('attempt_number' => (@attempt_number || 0) + 1)
  end

  def deserialize(job_data)
    super(job_data)
    @attempt_number = job_data['attempt_number']
  end

  rescue_from(TimeoutError) do |ex|
    notify_job_tried_x_times(@attempt_number) if @attempt_number == 3
    retry_job(wait: 10)
  end
end

Earlier, deserialization was performed by #deserialize class method and therefore was inaccessible from the job instance. With the new changes, deserialization is delegated to the deserialize method on job instance thereby allowing it to attach arbitrary metadata when it gets serialized and read it back when it gets performed.

Partial template name need not be a valid Ruby identifier in Rails 5

This blog is part of our Rails 5 series.

Before Rails 5, partials name should start with underscore and should be followed by any combination of letters, numbers and underscores.

This rule was required because before commit, rendering a partial without giving :object or :collection used to generate a local variable with the partial name by default and a variable name in ruby can’t have dash and other things like that.

In the following case we have a file named _order-details.html.erb. Now let’s try to use this partial.

<!DOCTYPE html>
<html>
<body>
  <%= render :partial => 'order-details' %>
</body>
</html>

We will get following error, if we try to render above view in Rails 4.x.

ActionView::Template::Error (The partial name (order-details) is not a valid Ruby identifier;
make sure your partial name starts with underscore,
and is followed by any combination of letters, numbers and underscores.):
    2: <html>
    3: <body>
    4: Following code is rendered through partial named _order-details.erb
    5: <%= render :partial => 'order-details' %>
    6: </body>
    7: </html>

In the above the code failed because the partial name has a dash which is not a valid ruby variable name.

In Rails 5, we can give our partials any name which starts with underscore.

Rails 5 allows passing record being validated to error message generator

This blog is part of our Rails 5 series.

Active Record validations by default provides an error messages, based on applied attributes. But some time we need to display a custom error message while validating a record.

We can give custom error message by passing String or Proc to :message.

class Book < ActiveRecord::Base
  # error message with a string
  validates_presence_of :title, message: 'You must provide the title of book.'

  # error message with a proc
  validates_presence_of :price,
      :message => Proc.new { |error, attributes|
      "#{attributes[:key]} cannot be blank."
      }
end

What’s new in Rails 5?

Rails 5 allows passing record to error message generator. Now we can pass current record object in a proc as an argument, so that we can write custom error message based on current object.

Revised example with current record object.

class Book < ActiveRecord::Base
  # error message with simple string
  validates_presence_of :title, message: 'You must provide the title of book.'

  # error message with proc using current record object
  validates_presence_of :price,
      :message => Proc.new { |book, data|
      "You must provide #{data[:attribute]} for #{book.title}"
      }
end

Apply platform specific styles in stylesheet in React Native

While writing cross platform applications we need to add platform specific styles. There are many ways of accomplishing this.

React Native introduced Platform.select helper in version 0.28 which allows us to write platform specific styles in a concise way. In this blog we will see how to use this newly introduced feature.

StyleSheet

A basic stylesheet file might look something like this.

import {StyleSheet} from 'react-native'

export default StyleSheet.create({
  container: {
    flex: 1,
  },

  containerIOS: {
    padding: 4,
    margin: 2,
  },

  containerAndroid: {
    padding: 6,
  },
});

Now let’s see how we can re-write this StyleSheet using Platform.select.

import {StyleSheet, Platform} from 'react-native';

export default StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {
        padding: 4,
        margin: 2
      },
      android: {
        padding: 6
      }
    }),
  },
});

Passing user agent or custom header in React Native WebView

Using WebView in a React Native application allows us to re-use already built web pages.

We have seen that in most of the “web view” based applications the links in header are mostly turned in native navigational components. It means server should not be sending header if React Native component is asking for web pages. However those headers should be present if the request is not coming from the React Native app.

Passing custom request headers

We can configure React Native app to pass custom request headers when request is made to the server to fetch pages.

let customHeaders = {
  "X-DemoApp-Version": "1.1",
  "X-DemoApp-Type": "demo-app-react-native"
}

While invoking WebView component we can pass customHeaders as shown below.

renderWebView() {
  return (
    <WebView
      source={ {uri: this.props.url, headers: customHeaders} }
    />
  )
}

Passing user agent

React Native also allows us to pass “userAgent” as a prop. However it is only supported by android version of React Native.

renderWebView() {
  return (
    <WebView
      source={ {uri: this.props.url} }
      userAgent="demo-react-native-app"
    />
  )
}

For iOS, we would need to add the following lines to our AppDelegate.m to set the userAgent.

NSString *newAgent = @"demo-react-native-app";
NSDictionary *dictionnary = [[NSDictionary alloc] initWithObjectsAndKeys:newAgent, @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionnary];

Skip mailers while generating Rails 5 app

This blog is part of our Rails 5 series.

We can now skip requiring Action Mailer while generating Rails 5 app.

$ rails new my_app --skip-action-mailer

# OR

$ rails new my_app -M

This comments out requiring action_mailer/railtie in application.rb.

It also omits mailer specific configurations such as config.action_mailer.raise_delivery_errors and config.action_mailer.perform_caching in production/development and config.action_mailer.delivery_method by default in test environment.

# application.rb

require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
# require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"

As, we can see action_mailer/railtie is commented out.

Errors can be indexed with nested attributes in Rails 5

This blog is part of our Rails 5 series.

We use accepts_nested_attributes_for when we want a single form to cater to multiple models. By using this we can easily provide attributes for associated models.

In Rails 4.x, if a validation fails for one or more of the associated models, then it is not possible to figure out from error message, which of the associated model object is the error related to.

class Product < ApplicationRecord
  has_many :variants
  accepts_nested_attributes_for :variants
end

class Variant < ApplicationRecord
  validates :display_name, :price, presence: true
end

>> product = Product.new(name: 'Table')
>> variant1 = Variant.new(price: 10)
>> variant2 = Variant.new(display_name: 'Brown')
>> product.variants = [variant1, variant2]
>> product.save
=> false

>> product.error.messages
=> {:"variants.display_name"=>["can't be blank"], :"variants.price"=>["can't be blank"]}

In the example above we can see that if this error message is sent as JSON API, we cannot find out which variant save failed because of which attribute.

This works well when we render forms using Active Record models, as errors are available on individual instances. But, the issue arises with an API call, where we don’t have access to these instances.

Rails 5 allows indexing of errors on nested attributes

In Rails 5, we can add an index to errors on nested models.

We can add the option index_errors: true to has_many association to enable this behavior on individual association.

class Product < ApplicationRecord
  has_many :variants, index_errors: true
  accepts_nested_attributes_for :variants
end

class Variant < ApplicationRecord
  validates :display_name, :price, presence: true
end

>> product = Product.new(name: 'Table')
>> variant1 = Variant.new(price: 10)
>> variant2 = Variant.new(display_name: 'Brown')
>> product.variants = [variant1, variant2]
>> product.save
=> false

>> product.error.messages
=> {:"variants[0].display_name"=>["can't be blank"], :"variants[1].price"=>["can't be blank"]}

Using global configuration

In order to make this change global, we can set configuration config.active_record.index_nested_attribute_errors = true which is false by default.

config.active_record.index_nested_attribute_errors = true

class Product < ApplicationRecord
  has_many :variants
  accepts_nested_attributes_for :variants
end

class Variant < ApplicationRecord
  validates :display_name, :price, presence: true
end

This will work exactly same as an example with has_many :variants, index_errors: true in Product.

Rails 5 doesn't convert empty array to nil when deep munging params

This blog is part of our Rails 5 series.

In older Rails version (< 3.2), when an empty array was passed to a where clause or to a find_by query, it generated SQL with an IS NULL clause.

User.find_by_email([]).to_sql
#=> "SELECT * FROM users WHERE email IS NULL"

User.find_by_email([nil]).to_sql
#=> "SELECT * FROM users WHERE email IS NULL"

Also, when JSON data of the request was parsed and params got generated the deep munging converted empty arrays to nil.

For example, When the following JSON data is posted to a Rails controller

{"property_grouping":{"project_id":289,"name":"test group2","property_ids":[]}}

It gets converted into the following params in the controller.

{"property_grouping"=>{"project_id"=>289, "name"=>"test group2", "property_ids"=>nil, },
"action"=>"...", "controller"=>"...", "format"=>"json"}

This in combination with the fact that Active Record constructs IS NULL query when blank array is passed became one of the security threats and one of the most complained issues in Rails.

The security threat we had was that it was possible for an attacker to issue unexpected database queries with “IS NULL” where clauses. Though there was no threat of an insert being carried out, there could be scope for firing queries that would check for NULL even if it wasn’t intended.

In later version of Rails(> 3.2), we had a different way of handling blank arrays in Active Record find_by and where clauses.

User.find_by_email([]).to_sql
#=> "SELECT "users".* FROM "users" WHERE 1=0 LIMIT 1"

User.find_by_email([nil]).to_sql
#=> "SELECT * FROM users WHERE email IS NULL"

As you can see a conditional for empty array doesn’t trigger IS NULL query, which solved part of the problem.

We still had conversion of empty array to nil in the deep munging in place and hence there was still a threat of undesired behavior when request contained empty array.

One way to handle it was to add before_action hooks to the action that could modify the value to empty array if it were nil.

In Rails 5, empty array does not get converted to nil in deep munging. With this change, the empty array will persist as is from request to the params in the controller.

Use as option to encode a request for a specific mime type in controller tests in Rails 5

This blog is part of our Rails 5 series.

Before Rails 5, while sending requests with integration test setup, we needed to add format option to send request with different Mime type.

class ProductsControllerTest < ActionController::TestCase
  def test_index

    get :index, { email: 'john@example.com' }.to_json,
        format: :json,
        headers: { 'Content-Type' => 'application/json' }

    assert_equal 'application/json', request.content_type
    assert_equal({ id: 1, name: 'Rails 5 book' }, JSON.parse(response.body))
  end
end

This format for writing tests with JSON type is lengthy and needs too much information to be passed to request as well.

Improvement with Rails 5

In Rails 5, we can provide Mime type while sending request by passing it with as option and all the other information like headers and format will be passed automatically.

class ProductsControllerTest < ActionDispatch::IntegrationTest
  def test_index
    get products_url, params: { email: 'john@example.com' }, as: :json

    assert_equal 'application/json', request.content_type
    assert_equal({ 'id' => 1, 'name' => 'Rails 5 book' }, response.parsed_body)
  end
end

As we can notice, we don’t need to parse JSON anymore.

With changes in this PR, we can fetch parsed response without needing to call JSON.parse at all.

Custom Mime Type

We can also register our own encoders for any registered Mime Type.

class ProductsControllerTest < ActionDispatch::IntegrationTest

  def setup
    Mime::Type.register 'text/custom', :custom

    ActionDispatch::IntegrationTest.register_encoder :custom,
      param_encoder: -> params { params.to_custom },
      response_parser: -> body { body }

  end

  def test_index
    get products_url, params: { email: 'john@example.com' }, as: :custom

    assert_response :success
    assert_equal 'text/custom', request.content_type
  end
end

Controller actions respond with 204 No Content by default in Rails 5 if no response is specified

This blog is part of our Rails 5 series.

Before Rails 5, when we forget to add template for an action, we get ActionView::MissingTemplate exception.

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

Started GET "/users" for ::1 at 2016-06-10 17:10:40 +0530
Processing by UsersController#index as HTML
Completed 500 Internal Server Error in 5ms

ActionView::MissingTemplate (Missing template users/index, application/index with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby]}...

Similarly, if we don’t specify response for a POST request, we will also get ActionView::MissingTemplate exception.

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    @user.save
  end
end
Started POST "/users"
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "user"=>{"name"=>"Max"}, "commit"=>"Create User"}
   (0.1ms)  begin transaction
  SQL (2.7ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Max"], ["created_at", 2016-06-10 12:29:09 UTC], ["updated_at", 2016-06-10 12:29:09 UTC]]
   (0.5ms)  commit transaction
Completed 500 Internal Server Error in 5ms

ActionView::MissingTemplate (Missing template users/create, application/create with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby]}...

In Rails 5, if we don’t specify response for an action then Rails returns 204: No content response by default. This change can cause some serious implications during the development phase of the app.

Let’s see what happens with the POST request without specifying the response.

class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    @user.save
  end
end
Started POST "/users"
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "user"=>{"name"=>"Max"}, "commit"=>"Create User"}
   (0.1ms)  begin transaction
  SQL (2.7ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Max"], ["created_at", 2016-06-10 12:29:09 UTC], ["updated_at", 2016-06-10 12:29:09 UTC]]
   (0.5ms)  commit transaction
No template found for UsersController#create, rendering head :no_content
Completed 204 No Content in 41ms (ActiveRecord: 3.3ms)

Rails happily returns with 204: No content response in this case.

This means users get the feel that nothing happened in the browser. Because Rails returned with no content and browser happily accepted it. But in reality, the user record was created in the database.

Let’s see what happens with the GET request in Rails 5.

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end
ActionController::UnknownFormat (UsersController#index is missing a template for this request format and variant.

request.formats: ["text/html"]
request.variant: []

NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.):

Instead of 204: No Content, we get ActionController::UnknownFormat exception. Rails is being extra smart here and hinting that we are probably missing corresponding template for this controller action. It is smart enough to show us this message as we requested this page via browser via a GET request. But if the same request is made via Ajax or through an API call or a POST request, Rails will return 204: No Content response as seen before.

In general, this change can trip us in the development phase, as we are used to incremental steps like adding a route, then the controller action and then the template or response. Getting 204 response can give a feel of nothing happening where things have actually happened in the background. So don’t forget to respond properly from your controller actions.

Rails 5 ensures compatibility between ActionDispatch::Request::Session and Rack::Session

This blog is part of our Rails 5 series.

Before Rails 5, there were errors in running integration tests when a Rack framework like Sinatra, Grape etc. were mounted within Rails with a motive of using its session.

Problems were reported at many places including github gists and stackoverflow regarding an error which was of the following form.

NoMethodError (undefined method `each' for #<ActionDispatch::Request::Session:0x7fb8dbe7f838 not yet loaded>):
  rack (1.5.2) lib/rack/session/abstract/id.rb:158:in `stringify_keys'
  rack (1.5.2) lib/rack/session/abstract/id.rb:95:in `update'
  rack (1.5.2) lib/rack/session/abstract/id.rb:258:in `prepare_session'
  rack (1.5.2) lib/rack/session/abstract/id.rb:224:in `context'
  rack (1.5.2) lib/rack/session/abstract/id.rb:220:in `call'

As we can see, the error occurs due to absence of method each on an ActionDispatch::Request::Session object.

In Rails 5, each method was introduced to ActionDispatch::Request::Session class making it compatible with Rack frameworks mounted in Rails and hence avoiding the above mentioned errors in integration testing.

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 coloration .

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.