Active Record - brief introduction for beginners
When creating an app in Ruby on Rails, from the very beginning, on every single step of your developer's journey, you're going to use ActiveRecord methods. Lots of them, and frankly speaking, it's good to know them well (if not by heart).
Despite almost everyone uses AR methods to develop almost every app, there are still many methods which are not widely known. Many of them can save your time (and to be honest, sometimes even your nerves) and keep your code nice and clean. Below you can find some useful examples, which are good to be familiar with.
Querying
If you want to query a single or multiple columns from a model, pluck
comes to help. Accepting a list of column names as arguments, returns an array of values of the specified columns:
Thanks to that, it makes possible to replace code like:
or
or
with
or
However, pluck
converts a database result into a Ruby Array
directly. This can lead to a better performance for large queries, but any model method overrides will not be available -- you have to keep it in mind!
What's more, pluck
triggers an immediate query. Meaning, it cannot be chained with any further scopes (but it works with previously constructed ones):
There’s also another method -- ids
-- which can replace pluck
, as it does exactly the same work: plucking all the IDs for the relation using the table's primary key.
or
Pretty neat methods, aren’t they?
Presence
When it comes to checking existence of an object, there are some handy methods. Let’s start with exists?
. It works in a similar way as find
, but instead of returning an object (or collection of objects) it will return either true
or false
.
This method also takes multiple values, and will return true
if any of those records exists.
And to check whether table is empty or not, you can always write
and got false
or true
, accordingly.
You can also use any?
and many?
to check the existence of a model, named scope, association, or relation:
and the result will be either true
or false
.
Worth mentioning are persisted?
and new_record?
methods, too. The first one returns true
if the record is persisted, meaning it is not a new record and it was not destroyed, and false
otherwise. The latter returns true
if the object has not been saved yet (a record for the object does not exist in database), otherwise returns false
.
Important note: exists?
can be used on collections (ActiveRecord::Base
classes or relations), and persisted?
and new_record?
on single records (ActiveRecord::Base
instances).
exists?
(& any?
) vs. present?
At an early stage of coding when your app is relatively small, if you want to check whether something in your database is present or not, you could use method present?
(what is, somehow, intuitive but not necessarily correct). But as your application grows and your database becomes more and more loaded with records, the time difference needed for execution of methods exists?
and present?
grows as well.
It's due to the character of those methods -- exists?
, as an ActiveRecord method, will stop when it finds the first record (and return a boolean
value); you can also use any?
method, which works in a slightly different way (when called, it executes SELECT COUNT(*) FROM
instead of SELECT 1 AS one FROM
like exists?
does, therefore it takes more time). On the other hand, present?
, as an extension to an Object
, will load all records from the table (and initialize model’s instance for each present record).
Generally speaking, in this case there are two main issues with present?
method. For relation it’s defined as !blank?
, and blank?
as to_a.blank?
(blank?
method is opposite to present?
: it will return true if an object is blank (i.e. false, empty, or a whitespace string), and false if not). This to_a
chunk extracts all records from the database which falls into our criteria. The first issue to deal with is the time needed to do so -- more records mean more time. The latter one is time and memory needed to create an ActiveRecord Object of each one: in some border cases we could use ALL available memory (what is a very big problem, obviously).
You need to be cautious with present?
because of one more reason, too. You can be surprised (not in a positive way), when you try to use present?
on ActiveRecord::Base
class. User.present?
will return true
regardless of whether there are records in users
table or there are not. This is because present?
method, as we saw above, is defined only for ActiveRecord::Relation
and not for ActiveRecord::Base
.
To simplify it (as it’s not very beginner-friendly):
present?
is equal to!blank?
blank?
is equal torespond_to?(:empty?) ? !!empty? : !self
(what we know from documentation linked some way up)empty?
is a method valid only for arrays, strings, and hashes.ActiveRecord::Base
class is neither of them, so the result is!self
- As
present?
equals!blank?
, the final result is!!self
, what equalsself
, what eventually counts astruthy
argument (truthy
andfalsey
mean non-boolean value treated astrue
orfalse
, and in Ruby onlyfalse
andnil
arefalsey
. Everything else is truthy (yes, even 0 is truthy)) - And that’s why calling
present?
on class always results withtrue
At some point the difference between exists?
and present?
becomes really significant: Let’s check the case of 1500 records:
Not bad, hmm?
Execution in case of 60000 records:
Ok, it took some time, but still -- it's not like really, really bad, right?
But how about database with 500000 records?
Ouch. So, yes, it's important to know the difference.
Calculations
In many cases you will encounter a necessity to calculate something, like number of products in your basket, range of users' age, or average value of clients' orders. And there are few useful methods for doing that, waiting to be used.
But: use them with care! They DO NOT sanitize raw SQL arguments and are not intended to be called with unsafe user input. If you are not cautious enough, your mistake can open up code to SQL Injection exploits, which you obviously DON’T want to happen.
count
Method which (not surprisingly) counts the number of whatever you want to count. Works on a model, relation, or even in more complicated chains:
What is worth to notice, count
and count(:id)
differ slightly when it comes to SQL queries (as shown below). Member.count
returns the total count of all members in your database, and Member.count(:id)
returns the total count of all members in your database whose ID is present in database (i.e. all records where ID is not NULL
).
Bear in mind that the latter one might be slower, though: just the number of rows in this table has been already cached, and :id
column is not defined as NOT NULL -- hence there can be some NULL values in it and so MySQL have to perform full table scan to find out. (important note: it’s true for MySQL but not necessarily for other DB’s, and for sure not for PostgreSQL)
There’s also one more way to count something in your database: .count(1)
. But as “1” is a non-null expression, it's the same as COUNT(*). The optimizer recognizes it for what it is: trivial.
average
If you want to see, for example, the average value of your clients' orders, you can call average
method on the class that related to the table:
minimum
& maximum
If you want to find a minimum or maximum value of a field (let's say, your Users' age) in your table, you can call minimum
and maximum
methods, respectively:
sum
If you want to find a sum of a field for all records in your table (for example, likes for a blog posts) you can call sum
method:
All above calculation methods (average
, minimum
, maximum
, and sum
) work on a model, relation, and more complex chains -- just like count
.
group
There’s also one more method which might save you a lot of time (or at least simplify your life a bit) -- group
. For example, if you want to check rating of your products, or views count of your posts, you can extract it one-by-one. But you could always group it and got the result more efficiently, as shown below:
Summary
To wrap it up -- methods described in this post might help you with your application when it comes to calculate something, query it, or check presence of specific records in your database. But they are just a mere fraction of what ActiveRecord offers. There's a bunch of handy solutions, which are (sadly) not very wide-known – so we highly recommend you to benefit from it!
photo from: unsplash.com