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

Generating OR in queries feature lands in Rails 5

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 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 %>
<% 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

Rails 5 supports bi-directional destroy dependency

This blog is part of our Rails 5 series.

In Rails 4.x, it is not possible to have destroy dependency on both sides of a bi-directional association between the two models as it would result in an infinite callback loop causing SystemStackError: stack level too deep.

class User < ActiveRecord::Base
  has_one :profile, dependent: :destroy
end

class Profile < ActiveRecord::Base
  belongs_to :user, dependent: :destroy
end

Calling User#destroy or Profile#destroy would lead to an infinite callback loop.

>> user = User.first
=> <User id: 4, name: "George">

>> user.profile
=> <Profile id: 4>

>> user.destroy
=> DELETE FROM `profiles` WHERE `profiles`.`id` = 4
   ROLLBACK
SystemStackError: stack level too deep

Rails 5 supports bi-directional destroy dependency without triggering infinite callback loop.

>> user = User.first
=> <User id: 4, name: "George">

>> user.profile
=> <Profile id: 4, about: 'Rails developer', works_at: 'ABC'>

>> user.destroy
=> DELETE FROM "profiles" WHERE "posts"."id" = ?  [["id", 4]]
   DELETE FROM "users" WHERE "users"."id" = ?  [["id", 4]]
=> <User id: 4, name: "George">

There are many instances like above where we need to destroy an association when it is destroying itself, otherwise it may lead to orphan records.

This feature adds responsibility on developers to ensure adding destroy dependency only when it is required as it can have unintended consequences as shown below.

class User < ApplicationRecord
  has_many :posts, dependent: :destroy
end

class Post < ApplicationRecord
  belongs_to :user, dependent: :destroy
end

>> user = User.first
=> <User id: 4, name: "George">

>> user.posts
=> <ActiveRecord::Associations::CollectionProxy [<Post id: 11, title: 'Ruby', user_id: 4>, #<Post id: 12, title: 'Rails', user_id: 4>]>

As we can see “user” has two posts. Now we will destroy first post.

>> user.posts.first.destroy
=> DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 11]]
   SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ?  [["user_id", 4]]
   DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 12]]
   DELETE FROM "users" WHERE "users"."id" = ?  [["id", 4]]

As we can see, we wanted to remove post with id “11”. However post with id “12” also got deleted. Not only that but user record got deleted too.

In Rails 4.x this would have resulted in SystemStackError: stack level too deep .

So we should use this option very carefully.

Rails 5 adds after_{create,update,delete}_commit callbacks aliases

This blog is part of our Rails 5 series.

Rails 4.x has after_commit callback. after_commit is called after a record has been created, updated or destroyed.

class User < ActiveRecord::Base
  after_commit :send_welcome_mail, on: create
  after_commit :send_profile_update_notification, on: update
  after_commit :remove_profile_data, on: destroy

  def send_welcome_mail
    EmailSender.send_welcome_mail(email: email)
  end
end

Rails 5 added new aliases

Rails 5 had added following three aliases.

  • after_create_commit
  • after_update_commit
  • after_destroy_commit

Here is revised code after using these aliases.

class User < ApplicationRecord
  after_create_commit	:send_welcome_mail
  after_update_commit	:send_profile_update_notification
  after_destroy_commit	:remove_profile_data

  def send_welcome_mail
    EmailSender.send_welcome_mail(email: email)
  end
end

Note

We earlier stated that after_commit callback is executed at the end of transaction. Using after_commit with a transaction block can be tricky. Please checkout our earlier post about Gotcha with after_commit callback in Rails .

Rails 5 allows updating a record without updating timestamps

This blog is part of our Rails 5 series.

In Rails 4.x, when we save an ActiveRecord object then Rails automatically updates fields updated_at or updated_on.

>> user = User.new(name: 'John', email: 'john@example.com')
>> user.save
 INSERT INTO "users" ("name", "created_at", "updated_at", "email") VALUES (?, ?, ?, ?)  [["name", "John"], ["created_at", 2016-03-16 09:12:44 UTC], ["updated_at", 2016-03-16 09:12:44 UTC], ["email", "john@example.com"]]
=> true

>> user.updated_at
=> Wed, 16 Mar 2016 09:12:44 UTC +00:00

>> user.name = "Mark"
>> user.save
  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Mark"], ["updated_at", 2016-03-16 09:15:30 UTC], ["id", 12]]
=> true

>> user.updated_at
=> Wed, 16 Mar 2016 09:15:30 UTC +00:00

Addition of touch option in ActiveRecord::Base#save

In Rails 5, by passing touch: false as an option to save, we can update the object without updating timestamps. The default option for touch is true.

>> user.updated_at
=> Wed, 16 Mar 2016 09:15:30 UTC +00:00

>> user.name = "Dan"
>> user.save(touch: false)
  UPDATE "users" SET "name" = ? WHERE "users"."id" = ?  [["name", "Dan"], ["id", 12]]
=> true

>> user.updated_at
=> Wed, 16 Mar 2016 09:15:30 UTC +00:00

This works only when we are updating a record and does not work when a record is created.

>> user = User.new(name: 'Tom', email: 'tom@example.com')
>> user.save(touch: false)
 INSERT INTO "users" ("name", "created_at", "updated_at", "email") VALUES (?, ?, ?, ?)  [["name", "Tom"], ["created_at", 2016-03-21 06:57:23 UTC], ["updated_at", 2016-03-21 06:57:23 UTC], ["email", "tom@example.com"]])

>> user.updated_at
=> Mon, 21 Mar 2016 07:04:04 UTC +00:00

Rails 5 adds a way to get information about types of failed validations

This blog is part of our Rails 5 series.

Let’s look at a validation example in Rails 4.x.

class User < ActiveRecord::Base
  validates :email, presence: true
end

>> user = User.new
>> user.valid?
=> false

>> user.errors.messages
=> {:email=>["can't be blank"]}

In this case, we do not get any information about the type of failed validation as ActiveModel#Errors only gives the attribute name and the translated error message.

This works out well for normal apps. But in case of API only applications, sometimes we want to allow the client consuming the API to generate customized error message as per their needs. We don’t want to send the final translated messages in such cases. Instead if we could just send details that presence validation failed for :name attribute, the client app would be able to customize the error message based on that information.

In Rails 5, it is now possible to get such details about which validations failed for a given attribute.

We can check this by calling details method on the ActiveModel#Errors instance.

class User < ApplicationRecord
  validates :email, presence: true
end

>> user = User.new
>> user.valid?
=> false

>> user.errors.details
=> {:email=>[{:error=>:blank}]}

We can also add custom validator types as per our need.

# Custom validator type
>> user = User.new
>> user.errors.add(:name, :not_valid, message: "The name appears invalid")

>> user.errors.details
=> {:name=>[{:error=>:not_valid}]}

# Custom error with default validator type :invalid

>> user = User.new
>> user.errors.add(:name)

>> user.errors.details
=> {:name=>[{:error=>:invalid}]}

# More than one error on one attribute

>> user = User.new
>> user.errors.add(:password, :invalid_format, message: "Password must start with an alphabet")
>> user.errors.add(:password, :invalid_length, message: "Password must have atleast 8 characters")

>> user.errors.details
=> {:password=>[{:error=>:invalid_format}, {:error=>:invalid_length}]}

Passing contextual information about the errors

We can also send contextual data for the validation to the Errors#add method. This data can be later accessed via Errors#details method because Errors#add method forwards all options except :message, :if, :unless, and :on to details.

For eg. we can say that the password is invalid because ! is not allowed, as follows.

class User < ApplicationRecord
  validate :password_cannot_have_invalid_character

  def password_cannot_have_invalid_character
    if password.scan("!").present?
      errors.add(:password, :invalid_character, not_allowed: "!")
    end
  end
end

>> user = User.create(name: 'Mark', password: 'Ra!ls')
>> user.errors.details
=> {:password=>[{:error=>:invalid_character, :not_allowed=>"!"}]}

We can also use this feature in our Rails 4.x apps by simply installing gem active_model-errors_details.

Using Image as a Container in React Native

Adding a nice looking background to a screen makes an app visually appealing. It also makes the app look more sleek and elegant. Let us see how we can leverage this technique in React Native and add an image as a background.

We’ll need to create different sizes of the background image, which we’re going to use as a container. React Native will pick up appropriate image based on the device’s dimension (check Images guide for more information).

- login-background.png (375x667)
- login-background@2x.png (750x1134)
- login-background@3x.png (1125x2001)

Now we’ll use these images in our code as container.

//...
render() {
    return (
      <Image
        source={require('./images/login-background.png')}
        style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.ios.js
        </Text>
        <Text style={styles.instructions}>
          Press Cmd+R to reload,{'\n'}
          Cmd+D or shake for dev menu
        </Text>
      </Image>
    );
  }
//...

const styles = StyleSheet.create({
  container: {
    flex: 1,
    width: undefined,
    height: undefined,
    backgroundColor:'transparent',
    justifyContent: 'center',
    alignItems: 'center',
  },
});

We’ve intentionally left height and width of the image as undefined. This will let React Native take the size of the image from the image itself. This way, we can use Image component as View and add other components as a children to build UI. image as container in react native

Rails 5 - What's in it for me?

This blog is part of our Rails 5 series.

I recently did a webinar with Srijan on upcoming changes in Rails 5. In this webinar I discussed various features and additions coming up in Rails 5.

Major Features

Ruby 2.2.2+ dependency.

Action Cable.

API only apps.

Features for Development mode

Puma as default web server.

rails CLI over rake.

Restarting app using rails restart.

Enable caching using rails dev:cache.

Enhanced filtering of routes using rails routes -g

Evented file system monitor.

Features for Test mode

Test Runner.

Changes to controller tests.

Cache content forever using http_cache_forever.

Collection caching using ActiveRecord#cache_key.

Partials caching using multi_fetch_fragments.

Caching in Action Mailer views.

Changes in Active Record

Introduction of ApplicationRecord.

ActiveRelation#or.

has_secure_token for generating secure tokens.

Versioned migrations for backward compatibility.

Changes in Active Support

Improvements to Date/Time.

Enumerable#pluck, Enumerable#without.

Change in behavior related to halting callback chains.

Rails 5 officially supports MariaDB

This blog is part of our Rails 5 series.

MariaDB is an open source fork of the MySQL database and it acts as a drop-in replacement for MySQL.

After the Oracle’s take over of MySQL there was some confusion about the future of MySQL. To remove any ambiguity about whether in future MySQL will remain free or not MariaDB was started .

Some of you might be wondering what advantages MariaDB offers over MySQL.Here is an article which lists 10 reasons to migrate to MariaDB from MySQL.

MariaDB is bundled as default on systems like Redhat’s RHEL 7+, Archlinux, Slackware and OpenBSD.

Some of the users of MariaDB are Google, Mozilla, Facebook and Wikipedia. Later we found out that Basecamp has already been using MariaDB for a while.

Active Record support for MariaDB

Recently, Ian Gilfillan from MariaDB Foundation sent a Pull Request to include MariaDB as part Rails Documentation.

Accepting that pull request means Rails is committing to supporting MariaDB along with MySQL, PostgreSQL and SQLite.

The tests revealed an issue related to micro-precision support on time column.

If a column has time field and if we search on that column then the search was failing for MariaDB.

time = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
Task.create!(start: time)
Task.find_by(start: time) # => nil

In the above case we created a record. However query yielded no record.

Now let’s see why the query did not work for MariaDB.

MariaDB vs MySQL time column difference

First let’s examine the tasks table.

 mysql> desc tasks;
+--------+---------+------+-----+---------+----------------+
| Field  | Type    | Null | Key | Default | Extra          |
+--------+---------+------+-----+---------+----------------+
| id     | int(11) | NO   | PRI | NULL    | auto_increment |
| start  | time    | YES  |     | NULL    |                |
+--------+---------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

In the above case column start is of type time.

Let’s insert a record in tasks table.

mysql> INSERT INTO `tasks` (`start`) VALUES ('2000-01-01 12:30:00');

Now let’s query the table.

mysql> SELECT  `tasks`.* FROM `tasks` WHERE `tasks`.`start` = '2000-01-01 12:30:00' LIMIT 1;
Empty set (0.00 sec)

In the above case query is passing date part(2000-01-01) along with the time part(12:30:00) for column start and we did not get any result.

Now let’s query again but this time we will pass only the time part to the start column.

mysql> SELECT  `tasks`.* FROM `tasks` WHERE `tasks`.`start` = '12:30:00' LIMIT 1;
+----+----------+
| id | start    |
+----+----------+
|  1 | 12:30:00 |
+----+----------+
1 row in set (0.00 sec)

So in the query if we pass 2000-01-01 12:30:00 to a column which is of type time then MariaDB fails.

Passing 2000-01-01 12:30:00 to MySQL, PostgreSQL and SQLite will work fine. That’s because the adapters for those databases will drop the date part if date is passed in the query string.

For MariaDB similar action was needed and soon enough a Pull Request, to take care of this behavior from Rails side, was landed. MariaDB is itself, working on supporting this behavior now.

Summary

In summary Rails 5 officially supports MariaDB and MariaDB can now safely be used as an alternative to MySQL for Ruby on Rails Applications.

Changes to test controllers in Rails 5

This blog is part of our Rails 5 series.

In Rails 5, controller tests have undergone some major changes. In this blog post, we will walk through some of those changes.

ActionController::TestCase is deprecated

In Rails 5, controller tests are generated with superclass ActionDispatch::IntegrationTest instead of ActionController::TestCase which is deprecated . It will be moved into a separate gem in Rails 5.1 .

Rails 5 will use ActionDispatch::IntegrationTest by default for generating scaffolds as well controller tests stubs.

Use URL instead of action name with request methods in Rails 5

In Rails 4.x, we pass controller action as shown below.

class ProductsControllerTest < ActionController::TestCase

  def test_index_response
    get :index
    assert_response :success
  end
end

But in Rails 5, controller tests expect to receive URL instead of action. Otherwise test will throw exception URI::InvalidURIError: bad URI.

class ProductsControllerTest < ActionDispatch::IntegrationTest

  def test_index
    get products_url
    assert_response :success
  end
end

If we are upgrading an older Rails 4.x app to Rails 5, which have test cases with superclass ActionController::TestCase, then they will continue to work as it is without requiring to change anything from above.

Deprecation of assigns and assert_template in controller tests

In Rails 4.x, we can test instance variables assigned in a controller action and which template a particular controller action renders using assigns and assert_template methods.

class ProductsControllerTest < ActionController::TestCase
  def test_index_template_rendered
    get :index
    assert_template :index
    assert_equal Product.all, assigns(:products)
  end
end

But in Rails 5, calling assert_template or assigns will throw an exception.

class ProductsControllerTest < ActionDispatch::IntegrationTest
  def test_index_template_rendered
    get products_url
    assert_template :index
    assert_equal Product.all, assigns(:products)
  end
end

# Throws exception
NoMethodError: assert_template has been extracted to a gem. To continue using it,
  add `gem 'rails-controller-testing'` to your Gemfile.

These two methods have now been removed from the core and moved to a separate gem rails-controller-testing. If we still want to use assert_template and assigns, then we can do this by adding this gem in our applications.

Reasons for removing assigns and assert_template

The idea behind the removal of these methods is that instance variables and which template is rendered in a controller action are internals of a controller, and controller tests should not care about them.

According to Rails team, controller tests should be more concerned about what is the result of that controller action like what cookies are set, or what HTTP code is set rather than testing of the internals of the controller. So, these methods are removed from the core.

Use of Keywords arguments in HTTP request methods in Rails 5

In Rails 4.x, we pass various arguments like params, flash messages and session variables to request method directly.

class ProductsControllerTest < ActionController::TestCase

  def test_show
    get :show, { id: user.id }, { notice: 'Welcome' }, { admin: user.admin? }
    assert_response :success
  end
end

Where { id: user.id } are params, { notice: 'Welcome' } is flash and { admin: user.admin? } is session.

This becomes confusing sometimes, as it is not clear which argument belongs to which part.

Now in Rails 5, request methods accept only keyword arguments.

class ProductsControllerTest < ActionDispatch::IntegrationTest

  def test_create
    post product_url, params: { product: { name: "FIFA" } }
    assert_response :success
  end
end

This makes it easier to understand what arguments are being passed.

When we pass arguments without keywords arguments, then Rails logs a deprecation warning.

class ProductsControllerTest < ActionDispatch::IntegrationTest

  def test_create
    post product_url, { product: { name: "FIFA" } }
    assert_response :success
  end
end

DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept
only the following keyword arguments in future Rails versions:
params, headers, env, xhr

Rails 5 has added accessed_fields to find the fields that are actually being used in the application

This blog is part of our Rails 5 series.

Rails makes it very easy to select all the fields of a table.

@users = User.all

Above code is selecting all the columns of the table users. This might be ok in most cases. However in some cases we might want to select only certain columns for performance reason. The difficult task is finding what all columns are actually used in a request.

To help in this task, Rails 5 has added accessed_fields method which lists attributes that were actually used in the operation.

This is helpful in development mode in determining what all fields are really being used by the application.

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end
# app/views/users/index.html.erb

<table>
  <tr>
    <th>Name</th>
    <th>Email</th>
  </tr>
  <% @users.each do |user| %>
    <tr>
      <td><%= user.name %></td>
      <td><%= user.email %></td>
    </tr>
  <% end %>

</table>

Now, in order to find all the fields that were actually used, let’s add after_action to the controller.

class UsersController < ApplicationController

  after_action :print_accessed_fields

  def index
    @users = User.all
  end

  private

  def print_accessed_fields
    p @users.first.accessed_fields
  end
end

Let’s take a look at the log file.

Processing by UsersController#index as HTML
  User Load (0.1ms) SELECT "users".* FROM "users"
  Rendered users/index.html.erb within layouts/application (1.0ms)
  ["name", "email"]

As we can see, it returns ["name", "email"] as attributes which were actually used.

If users table has 20 columns then we do not need to load values all those other columns. We are using only two columns. So let’s change code to reflect that.

class UsersController < ApplicationController
  def index
    @users = User.select(:name, :email)
  end
end

Rails 5 adds warning when fetching big result set with Active Record

This blog is part of our Rails 5 series.

With large data set we can run into memory issue. Here is an example.

>> Post.published.count
=> 25000

>> Post.where(published: true).each do |post|
     post.archive!
   end

# Loads 25000 posts in memory

Rails 5 adds warning when loading large data set

To mitigate issue shown above Rails 5 has added config.active_record.warn_on_records_fetched_greater_than.

When this configuration is set to an integer value, any query that returns the number of records greater than the set limit, logs a warning.

config.active_record.warn_on_records_fetched_greater_than = 1500

>> Post.where(published: true).each do |post|
     post.archive!
   end

=> Query fetched 25000 Post records: SELECT "posts".* FROM "posts" WHERE "posts"."published" = ? [["published", true]]
   [#<Post id: 1, title: 'Rails', user_id: 1, created_at: "2016-02-11 11:32:32", updated_at: "2016-02-11 11:32:32", published: true>, #<Post id: 2, title: 'Ruby', user_id: 2, created_at: "2016-02-11 11:36:05", updated_at: "2016-02-11 11:36:05", published: true>,....]

This helps us find areas where potential problems exist and then we can replace inefficient queries with better ones.

config.active_record.warn_on_records_fetched_greater_than = 1500

>> Post.where(published: true).find_each do |post|
     post.archive!
   end

# No warning is logged