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

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.