Active Record in Rails | Basics
This is going to be a long article, so grab a cup of whatever you want and come have some fun!
Let’s first of all create a new project, create an card model that has:
- title :string
- publisher :string
- published_at :datetime
Open the rails console (type rails console
in your terminal) and let’s go!
Active Record is the Ruby object-relational mapping (ORM) library that handles database abstraction and interaction for Rails. It translates the Ruby you write into a language that databases understand.
Active Record is database agnostic, which means it doesn’t care what database software you use and supports almost every database available. Because it is a high-level abstraction, the code you write is the same regardless of which database you use.
To switch databases in rails: rails db:system:change
Active Record is based on a design pattern. It turns out that when you’re working in an object-oriented environment, the issue of how to effectively communicate with a database (which is not object oriented) is quite common. Because of this, a number of smart people have devoted themselves to the question of how to bring object-oriented programming and relational databases together. Martin Fowler is one of those smart people who, in his book Patterns of Enterprise Application Architecture (Addison Wesley, 2002), first described a pattern he called an Active Record. In Fowler’s pattern, there is a one-to-one mapping between a database record and an object that represents it. Fowler’s pattern served as a guide for Rails creator David Heinemeier Hansson when he was developing an ORM for his framework.
Introducing Active Record: Object-Relational Mapping on Rails
ORM: the mapping of tables to classes, objects, and attributes in tables In the world of software development, this technique is known as object-relational mapping (ORM).
For Active Record to work, a table’s mapping to a class requires almost no configuration at all. Creating a Ruby class named after the table you wish to map and extending the Active Record Base class is all that is required.
class Book < ApplicationRecord
end
Notice the part that reads < ApplicationRecord
. The less-than sign indicates that the Book class on the left is a subclass of the one on the right, ApplicationRecord.
As an example, in Ruby, inheriting from a class like this gives you access to all of the parent class’s features. Your app/models/application_record.rb defines ApplicationRecord. ActiveRecord::Base is the initial implementation. The ActiveRecord::Base class contains a lot of code, but you don’t need to look at it. Your class simply inherits it, and you’re done.
In this case, the table is automatically mapped if Active Record knows where to look for your database and you have a table called books (notice how the table name is plural while the class name is singular). You can do this in any Ruby context if you know your books table contains the fields title, publisher, and published at.
book = Book.new
book.title = "Lovely Book"
book.publisher = "Loush"
book.published_at = "2022-07-10"
book.save
New records are added to the bookshelf after reading these five lines. Subclassing is a great way to improve your skills. As a result, Active Record is a breeze to work with. Use your object’s methods to read and write to the table’s fields (such as title, publisher, and published at) (book). In addition, you didn’t have to inform Active Record of the names of your fields or the fact that you had any at all. That’s what it came up with on its own. There is more to Active Record than creating new records. It can also read, update, and delete records, as well as many other functions.
Seeing as you’ve brought it up, how about SQL?
A fundamentally different paradigm exists between relational databases and object-oriented programming. The mathematical nature of the relational paradigm is reflected in its focus on relationships. Although the object-oriented paradigm deals with objects, their attributes, and their relationships to one another, it is not the only paradigm. At first glance, the object-relational rift becomes apparent when you try to make objects persistable by employing a relational database. Active Record, an ORM library, can assist you in bridging that gap.
Using Active Record to abstract your SQL generation does not imply that SQL is evil. For those times when SQL is required, Active Record makes it possible to use SQL directly. Even if an ORM doesn’t work in all cases, the truth is that SQL is databases’ native language.
Using Active Record, you can simulate physical objects in your program. In Rails, these real-world objects are referred to as models, hence the M in MVC. The database has a table for each of these model names: person, product, or book, for example. The app/models directory contains a Ruby class for each model. Class-to-table connectivity is provided by Active Record, which enables you to work with what appear to be regular objects and persist them to the database. This eliminates the need to use low-level SQL to communicate with the database. Instead, you work with your data as if it were an object, and Active Record handles all the SQL translation for you. As a result of this policy, Rails is a single-language platform: Ruby.
Active Record Conventions
The zero-configuration reputation of Active Record is built on convention. The majority of conventions it employs are simple to understand. Because they are conventions, everyone is already familiar with and comfortable with these ideas and practices. Conventions, even if you can override most of them to fit your database’s design, save a lot of time and effort in the long run.
Singular class names are used; plural table names and each table has an id column, which serves as an identifier.
The id column in a table is assumed to be unique. Ideally, the value in this column will serve as the table’s primary key. In database design, this is a common practice.
In the Rails philosophy, convention is preferred to configuration, so it should come as no surprise that there are many more conventions than those listed here. If you’re like most people, you’ll probably find that they all make sense and don’t require much thought.
Active Record Basics: CRUD
Let’s begin with the basics of Active Record. Here are the so-called “big four”: create, read, update, and delete (CRUD). Most of what you do with Active Record and databases in general is related to CRUD in one way or another. In order to streamline the modeling process, Rails has adopted CRUD as a design technique.
Creating New Records
In order to get started, you first need to create a book in the database. A variety of methods exist for creating new model objects, but they all fall under the same umbrella.
Using the new Constructor
The new constructor is the simplest method for creating a new model object. Crash course ruby classes readers will recognize it right away. Otherwise, knowing that new is the standard way to create new objects of any type should suffice. Active Record classes are no different from other types of classes in this regard.
book = Book.new
# {"id"=>nil, "title"=>nil, "publisher"=>nil, "published_at"=>nil, "created_at"=>nil, "updated_at"=>nil}
Creating a new Book object and storing it in the local variable book is all you’re doing here. The return value of the method, in this case a string representation of the model object, is returned to the console. When you inspect a Ruby object, it may look a little odd, but this is how it appears. Class Book attributes are listed in the response. You could start by calling some of the book’s variable methods from here. New record? tells you if this object has been saved to the database, and attributes returns a hash of attributes that Active Record obtained by reflecting on columns in the table. Each column name (title, created at, etc.) will be a key in the hash.
book.new_record?
# true
book.attributes
# {"id"=>nil, "title"=>nil, "publisher"=>nil, "published_at"=>nil, "created_at"=>nil, "updated_at"=>nil}
These reader methods read and return the value of the attribute in question, which is what you are doing here. Your attributes are all nil because this is a new record and you haven’t filled it out with any information. Let’s fix it now with the help of a writer’s methods:
book.title = 'Rails Is Fun!'
# "Rails Is Fun!"
Now, when you inspect your Book object, you can see that it has attributes:
# {"id"=>nil, "title"=>”Rails Is Fun”, "publisher"=>nil, "published_at"=>nil, "created_at"=>nil, "updated_at"=>nil}
You haven’t released a new album yet. In the database, the object you’re working with does not have a record in the books table. Notice that there is still no id assigned in the preceding object-inspection string if you look closely.) As a result, the object does not appear in your database. Thanks to the ease of the following procedure, saving an Active Record object is a breeze.
book.save
Whenever you save a new record, Rails generates a SQL INSERT statement; notice that Rails has shown the generated SQL for you. Save returns true if the INSERT operation succeeds, and false if it fails. To be certain that a record was created, you can request a count of the table’s rows.
Book.count
# 1
Note Despite the fact that writer methods appear to be assignments, their true nature is one of method. book. Loush is the functional equivalent of book for title. In this case, the method is title=(“Loush”). Ruby’s syntactic sugar gives writers a more natural appearance.
Using the create Method
When you want to create an object and save it in one fell swoop, you can use the create method. Use it now to create another book:
Book.create(title: "Ruby is Great!", publisher: "Loush world", published_at: '2022-07-12')
In this case, the create method doesn’t return true or false but rather the object it created, in this case a Book. A hash of attributes is actually being passed to the create method. It is not necessary to use curly braces around a hash when it is the final argument to a Ruby method. Alternatively, you can create the attribute’s hash first and then provide it to creators as a command argument:
book_attributes = { title: "Lets have some fun", publisher: "Coffee Publisher", published_at: "2022-07-11"}
coffee_book = Book.create(book_attributes)
To summarize; There are two ways to create and save an object: using the new constructor or creating and saving at the same time.
Go now and create more and more books!
Reading (Finding) Records
Begin with the fundamentals. Class members can use the method find. Like the new and create methods, you use it on the model class rather than an instance of that class. If a find operation is successful, a new object will be returned, just like with new and create.
You can call find four ways:
- all: Finds all records in the table
- first: Finds the first record
- last: Finds the last record
- find(:id): Finds a single record by its unique id or multiple records if :id is an array of ids
Finding a Single Record Using an ID
A single record is typically returned by the find method, as is the case with the first and last methods as well. You only use the :id option if you’re looking for a specific record and are confident in its unique identifier. A single id is either returned (if there is one) or an exception is raised if there isn’t (if there is). [1, 7] is an example of a parameter that returns an array of all records matching the given ids. A more forgiving first method returns the first record in the table or nil in the absence of any records.
You can find a single record using its unique id by using find(:id). Here’s how it works:
Book.find(1)
As you can see, you found the book with the id of 1. If you want to take a closer look at what was returned, you can store the result in a local variable:
found_book = Book.find(1)
found_book.id
# This will return found_book id
Here, you store the object that find returned in the local variable book. Then, you can interrogate it and ask for its attributes.
All this works because an book with the id 1 actually exists. If instead you search for a record that you know doesn’t exist (say, 9999), Active Record raises an exception:
ActiveRecord::RecordNotFound: (Couldn't find Book with ‘id’=9999)
You are aware that there is no such record. Using find(:id) to find a specific record is a good practice when you’re looking for something you know exists. As a result, Active Record raises the RecordNotFound exception if the requested record isn’t found.
You expect the record to exist when you use find with a single id. Rails error messages don’t have to be displayed directly to the user, but the language used in them can be changed. As a result, how do you gracefully recover from a RecordNotFound exception? Ruby’s error handling features, begin and rescue, are available to you. There are two ways to do this:
begin
Book.find(9999)
rescue ActiveRecord::RecordNotFound
puts "We couldn't find a book record matching the id"
end
This begins with a begin block. In order to get a RecordNotFound error, you search for a record that you know doesn’t exist. In the event of an error, Ruby executes the rescue body code, which prints a helpful message.
In the rescue block, you can put whatever you want: you can display a particular view, log an error, or even redirect to another location. Other error messages are handled in the same way. You can use rescue without specifying an error class if you need to be rescued from any kind of error.
Finding a Single Record Using first
You can find the first record that the database returns by using the first method. This always returns exactly one item, unless the table is empty, in which case nil is returned:
Book.first
Keep in mind that this may not be the first record in the table. Use the database software’s default retrieval order or change it to suit your needs. For the most part, records are sorted by the date they were created or updated. You should specify an order if you want to make sure you receive the first record you ordered. You can think of it as SQL’s equivalent of SELECT * FROM table LIMIT 1. For those times when you need to locate a record but don’t care which record it is, first can be a useful tool.
Note that first doesn’t raise an exception if the record can’t be found.
The last method is identical to the first, except that records are returned in the reverse order of when they were first added to the database. It’s not uncommon to see records from books arranged in chronological order for the first time, but then retrieved in inverse chronological order:
Book.last
Finding All Records
If you run the all method , it returns all records for that class:
all_books = Book.all
ActiveRecord::Relation was returned in the response, if you look closely. ActiveRecord::Relation is returned by most query methods. Instead of returning an array of Book instances, why not do this? As a result, more query methods can be chained together and the SQL can be executed as late as possible by returning an ActiveRecord::Relation.
For the same result, you can also take advantage of a more natural syntax by using the first method that all arrays respond to.
all_books.first.title
If you want to iterate over the collection, you can use the each method, which, again, works with all arrays. Here, you loop over the array, extract each item into a variable called book, and print its title attribute using the puts command:
all_books.each { |book| puts "Book #{book.id} #{book.publisher}" }
Ordering Results
Sometimes you want your results ordered. For example, if you’re listing all your books, you might want them listed alphabetically by the publisher. To do so, you can use the order method, which accepts as argument the name of the column or columns:
all_books = Book.order(:publisher)
Take note that when you call the order method, it returns a Relation object, as you may have guessed Using ActiveRecord::Relation’s feature of chaining calls to multiple methods before submitting the command to the database, you can construct more precise database queries. When you call the each method in this example, Active Record will use lazy loading, which only hits the database when it’s absolutely necessary.
Finding with Conditions
Using a primary key to find a record is useful, but it requires that you already know the id, which isn’t always the case. Other criteria may necessitate a search for records. This is where the rules of the game come in. Conditions are equivalent to the WHERE clause in SQL. Using the where method, you can search for a record based on its title. You can pass an array of conditions or a SQL fragment as a value.
Here, you use a hash of conditions to indicate you want the first book with the title ”Ruby is Great!”:
Book.where(title: 'Ruby is Great!').first
Because you use first, you get only one record (the first one in the result set, even if there is more than one result). If you instead use all , you get back a collection, even if the collection has only one item in it.
Updating Records
Updating a record is a lot like creating a record. You can update attributes one at a time and then save the result, or you can update attributes in one fell swoop. When you update a record, an SQL UPDATE statement is constructed behind the scenes. First, you use a find operation to retrieve the record you want to update; next, you modify its attributes; and finally, you save it back to the database:
first_book = Book.first
first_book.title = "Loush Is Editing"
first_book.save
By now, this should look familiar. In this process, instead of creating a new row, you retrieve an existing one. This is the only real difference. Same procedure for updating attributes and saving a record is followed. When you save an existing record, it returns true or false depending on whether the operation was successful, just like when you create a new record.
You use the update method if you want to update an object’s attributes and save it all in one go. It’s not like creating a new record, where you don’t need to fetch the record first. That’s the other small distinction. Update differs from create in that it is an instance method rather than a class method. A class’s instances can be accessed using instance methods. As an illustration, consider the following:
first_book = Book.first
first_book.update(title: "Updating book", published_at: 5.month.ago)
Deleting Records
CRUD’s final step is to delete, and you’ve reached it at last. Working with databases necessitates the deletion of records on a regular basis. Once an order has been cancelled, a book has run out of stock, or a row in the database has been mistakenly deleted, you may want to remove the row from the database. Some times you need to delete all the rows in a table, while other times you only need to delete a single row. With Active Record, deleting rows is just as simple as adding them.
Row deletion can be accomplished in two ways: by destroying the rows or by deleting them. The instance can be destroyed using the destroy style. The object is instantiated, which means it first locates a single row in the database and then deletes it. When deleting a row from a table, the delete style is applied to the entire table, not just a single row.
Using destroy
When you want to delete a record, the first step is to locate the record you want to delete:
first_book = Book.first
first_book.destroy
This means that book 1 has been permanently removed from the database. But the object is still in the variable book, so it’s impossible that it’s gone. The answer is that the object is frozen, even though it is still hydrated (retains all of its properties). You still have access to the object’s properties, but you can’t make any changes to them.
It appears that the book that was deleted has been put on hold. A read-only object is one that can’t be modified. Because of this, you don’t really need to create an explicit Book object if you’re just going to delete the record.
If you want to improve, you can still do so. In order to find, you can use the class method destroy. You don’t need to create an object before using destroy, as you can do with find and create. Rather than working row by row, it must be told which rows it should target because it only works on the table as a whole. Here’s how to get rid of the book identified by id 1:
Book.destroy(1)
Using delete
The delete method is a second way of deleting rows. Class methods delete and delete all are available in every Active Record class. For example, the delete family of methods does not instantiate or call back on the object it is deleting, unlike the destroy method. They delete the row from the database immediately.
Delete and delete all are used directly on the class, just like find and create; no objects are created first. It’s important to tell the method which rows you want to target because the method operates on the table and not the row.
Book.delete(1)
You can delete a book by entering a single primary key here. When a record is deleted, the operation responds by subtracting the number of records from the database. Only one record is deleted because a primary key uniquely identifies a single record.
Deleting with Conditions
All rows that meet a specific criteria can be deleted using the delete by class method. Remove all books published after the specified date with the following command:
Book.delete_by("published_at > '2022-07-11'")
This will return the number of rows deleted.