How to Optimize Performance in Rails. Active Record Tips

… when a page with simple content takes a second or more to come back from the server

Querying with ActiveRecord (where, save, etc...) is easy and fast enough. However we can meet the case when a page with simple content takes a second or more to come back from the server. And it's getting complicated with Timeout errors coming back from nginx.

Load only the Data you need!

pluck

User.all.pluck(:id)
(2.6ms) SELECT "users"."id" FROM "users"
=> [166, 178, 210, 204]
User.all.pluck(:id, :created_at)
(1.7ms) SELECT "users"."id", "users"."created_at" FROM "users"
=> [[166, Mon, 07 Mar 2016 12:34:32 GMT +00:00], [178, Wed, 03 Aug 2016 14:02:43 BST +01:00], [210, Sun, 05 Mar 2017 18:08:25 GMT +00:00], [204, Tue, 07 Feb 2017 08:46:46 GMT +00:00], [159, Sun, 21 Feb 2016 19:46:18 GMT +00:00]]
User.pluck(<<-PLUCK)
UPPER(first_name)
PLUCK
(3.2ms) SELECT UPPER(first_name) FROM "users"
=> ["OLIVIER", "VDFVFD", "DANIEL", "ANNA", "SDGDSZG"]
  • pluck will return an array instead of ActiveRecord :: Relation, it should be last in a chain.
  • You always need to remember which operations are performed by the database and which — by ruby code.
User.distinct.pluck (:first_name)
  • For simple transformations or to resolve a conflict in a column name — you can pass the request by string:
User.joins(:projects).pluck('projects.created_at')

select

User.select(:id, :created_at)
User Load (2.7ms) SELECT "users"."id", "users"."created_at" FROM "users"
=> #<ActiveRecord::Relation [#<User id: 166, created_at: "2016-03-07 12:34:32">, #<User id: 178, created_at: "2016-08-03 13:02:43">, …]

Grab all the data at once in ActiveRecord

Processing by UsersController#index as HTML
User Load (1.6ms) SELECT `users`.* FROM `users`
Profile Load (1.3ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 1 LIMIT 1
Profile Load (1.2ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 2 LIMIT 1
Profile Load (1.1ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 3 LIMIT 1
Profile Load (1.5ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 4 LIMIT 1
Profile Load (1.0ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` = 5 LIMIT 1
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.profile.age %></td>
```
You don’t need to hit the database N+1 times. You want to hit it at most twice: once for the users you’re trying to find, and once for all of the profiles associated with all of those users. This is called “eager loading” and you can do it really easily with .includes:

```ruby
@users = User.all.includes(:profile)
User Load (1.2ms)  SELECT `users`.* FROM `users`
Profile Load (3.0ms) SELECT `profiles`.* FROM `profiles` WHERE `profiles`.`user_id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Use indexes

User.where(email: user.email)
class AddEmailIndexToUsers < ActiveRecord::Migration
def change
add_index :users, :email, unique: true
end
end
class User < ActiveRecord::Base
has_many :profiles, foreign_key: 'email', primary_key: 'email'
end

Let’s sum up Active Record Tips

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Active Bridge

Active Bridge

26 Followers

Ruby on Rails development house. We assist businesses in building products that people enjoy. Share knowledge about #RoR #Web #CloudSoftware #ProductDevelopment