Earlier, you took a whirlwind tour through creating a basic Rails application using the built-in scaffolding feature. You sketched out a basic model for a blog application and created the project databases. You used the built-in web server to run the application locally and practiced adding and managing articles from the web browser. This chapter will take a more in-depth look at how things work, starting with what is arguably the most important part of Rails: Active Record.
You may recall from Chapter 1 that Active Record is the Ruby object-relational mapping (ORM) library that handles database abstraction and interaction for Rails. Whether you realized it or not, in Chapter 3 all access to the database—adding, editing, and deleting articles—happened through the magic of Active Record.
If you’re not sure what exactly object-relational mapping is, don’t worry. By the end of this chapter, you’ll know. For now, it’s best if you think of Active Record as being an intermediary that sits between your code and your database, allowing you to work with data effectively and naturally. When you use Active Record, you communicate with your database using pure Ruby code. Active Record translates the Ruby you write into a language that databases can understand.
This chapter teaches you how to use Active Record to talk to your database and perform basic operations. It introduces the concepts you need to know about communicating with databases and object-relational mapping. Then, you will look at Active Record and walk through the techniques you need to know to effectively work with a database from Rails. If you don’t have a lot of database experience under your belt, don’t worry. Working with databases through Active Record is a painless and even enjoyable experience. If you’re an experienced database guru, you’ll find that Active Record is an intelligent and efficient way to perform database operations without the need for low-level database-specific commands.
If you need to get the code at the exact point where you finished Chapter 3, download the zip file from GitHub (https://github.com/nicedawg/beginning-rails-6-blog/archive/chapter-03.zip).
Introducing Active Record: Object-Relational Mapping on Rails
The key feature of Active Record is that it maps tables to classes, table rows to objects, and table columns to object attributes. This practice is commonly known as object-relational mapping (ORM). To be sure, Active Record isn’t the only ORM in existence, but it may well be the easiest to use of the bunch.
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. In Ruby, when you inherit from a class like this, you automatically gain access to all the functionality in the parent class. ApplicationRecord is defined in your app/models/application_record.rb. Initially, it simply inherits from ActiveRecord::Base. There’s a lot of code in the ActiveRecord::Base class, but you don’t need to look at it. Your class merely inherits it, and your work is finished.
These five lines write a new record to the books table. You gain a lot of ability by the simple act of subclassing! And that’s why Active Record is easy to use. Notice how the table’s fields (title, publisher, and published_at) can be read and written to using methods on the object you created (book). And you didn’t need to tell Active Record what your fields were named or even that you had any fields. It figured this out on its own. Of course, Active Record doesn’t just let you create new records. It can also read, update, and delete records, plus a lot more.
Active Record is database agnostic, so it doesn’t care which database software you use, and it supports nearly every database out there. Because it’s a high-level abstraction, the code you write remains the same no matter which database you’re using. For the record (no pun intended), in this book you use SQLite. As explained in Chapter 2, SQLite is open source, easy to use, and fast, and it’s the default database used for Rails development. (Along with the SQLite site, https://sqlite.org, the Wikipedia entry on SQLite is a good resource: https://en.wikipedia.org/wiki/SQLite.)
At some point, you may need to switch to another database backend. (SQLite is great for development, but not appropriate for many production apps.) Rails 6 has added a command—rails db:system:change—which makes it easy to switch databases. We won’t run this command for this book, but just know this command exists.
Rails is also ORM agnostic: it allows you to hook up your ORM of choice. There are several alternatives to Active Record which you can use if you think Active Record has some deficiencies. (See www.ruby-toolbox.com/categories/orm for a list of popular ones.) However, we feel that sticking to the default ORM is the best way to learn. We don’t cover alternative ORMs in this book.
What About SQL?
To be sure, you don’t need Active Record (or any ORM) to talk to and manipulate your database. Databases have their own language: SQL, which is supported by nearly every relational database in existence. Using SQL, you can view column information, fetch a particular row or a set of rows, and search for rows containing certain criteria. You can also use SQL to create, drop, and modify tables and insert, update, and destroy the information stored in those tables. The problem with SQL is that it’s not object oriented. If you want to learn the basic SQL syntax, look at Appendix B.
Object-oriented programming and relational databases are fundamentally different paradigms. The relational paradigm deals with relations and is mathematical by nature. The object-oriented paradigm, however, deals with objects, their attributes, and their associations to one another. As soon as you want to make objects persistent using a relational database, you notice something: there is a rift between these two paradigms—the so-called object-relational gap. An ORM library like Active Record helps you bridge that gap.
Active Record is based on a design pattern. Design patterns are standard solutions to common problems in software design. Well, it turns out that when you’re working in an object-oriented environment, the problem of how to effectively communicate with a database (which isn’t object oriented) is quite common. Therefore, many smart people have wrapped their minds around the problem of how best to bring the object-oriented paradigm together with the relational database. One of those smart people is Martin Fowler, who, in his book Patterns of Enterprise Application Architecture (Addison Wesley, 2002), first described a pattern that he called an Active Record. In the pattern Fowler described, a one-to-one mapping exists between a database record and the object that represents it. When Rails creator David Heinemeier Hansson sought to implement an ORM for his framework, he based it on Fowler’s pattern.
Active Record lets you model real-world things in your code. Rails calls these real-world things models—the M in MVC. A model might be named Person, Product, or Article, and it has a corresponding table in the database: people, products, or articles. Each model is implemented as a Ruby class and is stored in the app/models directory. Active Record provides the link between these classes and your tables, allowing you to work with what look like regular objects, which, in turn, can be persisted to the database. This frees you from having to write low-level SQL to talk to the database. Instead, you work with your data as if they were an object, and Active Record does all the translation into SQL behind the scenes. This means that in Rails, you get to stick with one language: Ruby.
Just because you’re using Active Record to abstract your SQL generation doesn’t mean SQL is evil. Active Record makes it possible to execute SQL directly whenever that’s necessary. The truth is that raw SQL is the native language of databases, and there are some (albeit rare) cases when an ORM won’t cut it.
Active Record Conventions
Active Record achieves its zero-configuration reputation by way of convention. Most of the conventions it uses are easy to grasp. After all, they’re conventions, so they’re already in wide use. Although you can override most of the conventions to suit the particular design of your database, you’ll save a lot of time and energy if you stick to them.
Class names are singular; table names are plural.
Tables contain an identity column named id.
Table and Class Name Conventions
Table | Class |
---|---|
events | Event |
people | Person |
categories | Category |
order_items | OrderItem |
All tables are assumed to have a unique identity column named id. This column should be the table’s primary key (a value used to uniquely identify a table’s row). This is a fairly common convention in database design. (For more information on primary keys in database design, the Wikipedia entry has a wealth of useful information and links: https://en.wikipedia.org/wiki/Unique_key.)
The belief in convention over configuration is firmly entrenched in the Rails philosophy, so it should come as no surprise that there are more conventions at work than those listed here. You’ll likely find that they all make good sense, and you can use them without paying much attention.
Introducing the Console
Ruby comes with a great little tool: an interactive interpreter called irb (for Interactive Ruby). irb is Ruby’s standard REPL—a Read-Eval-Print Loop. Many programming languages have REPL tools, and Ruby has REPL tools other than irb. (pry is a popular alternative REPL for ruby with some great features.)
Most of the time, you invoke irb using the rails console command that ships with Rails, but you can start up an irb session whenever you want by typing irb at the command prompt. The advantage of the console is that it enjoys the special privilege of being integrated with your project’s environment. This means it has access to and knowledge of your models (and, subsequently, your database).
You use the console as a means to get inside the world of your Article model and to work with it in the exact same way your Rails application would. As you’ll see in a minute, this is a great way to showcase the capabilities of Active Record interactively.
You can execute any arbitrary Ruby code in irb and do anything you might otherwise do inside your Ruby programs: set variables, evaluate conditions, and inspect objects. The only essential difference between an interactive session and a regular old Ruby program is that irb echoes the return value of everything it executes. This saves you from having to explicitly print the results of an evaluation. Just run the code, and irb prints the result.
You can tell whenever you’re inside an irb session by looking for the double greater-than sign (>>)—or a slightly different sign depending on your environment—which indicates the irb prompt, and the arrow symbol (=>), which indicates the response.
As you continue to progress with both Ruby and Rails, you’ll find that irb is an essential tool. Using irb, you can play around with code and make sure it works as you expect before you write it into your programs.
If you’ve been following along with the previous chapters, then you should have a model called Article (in app/models/article.rb), and you’ve probably already entered some sample data when playing with scaffolding in Chapter 3. If not, make sure you get up to speed by reading Chapters 2 and 3 before moving on.
Depending on the version of Rails you are using and what gems you have installed, the number of methods might be different from 690. This is normal.
That’s a lot of methods! You may get a different number of methods depending on your environment. Many of the methods are inherited from ActiveRecord, but many of them come from classes that Active Record ultimately inherits from—like Ruby’s base Object class and its parent classes. Don’t worry—you don’t need to memorize all of these methods. Most of them are used internally so you’ll never have to use them directly. Still, it’s important, if for no other reason than to get a sense of what you get for free just by subclassing Active Record. Although in this case ApplicationRecord is considered the superclass, it sure makes your lowly Article class super, doesn’t it? (Sorry, enough bad humor.)
Object-oriented programming is all about objects. You create a class that encapsulates all the logic required to create an object, along with its properties and attributes, and use the class to produce new objects, each of which is a unique instance, distinct from other objects of the same class. That may sound a little abstract (and with good reason—abstraction, after all, is the name of the game), but if it helps, you can think of a class as being an object factory.
Apparently, if you want your car to have a make, you have to set it. This is where the writer method comes in handy:
my_car.make = 'Toyota'
That’s a simple example, but it illustrates a couple of very important points: classes are used to create objects, and objects have attributes. Every object has a unique set of attributes, different from other objects of the same class.
The reason for this crash course in Ruby class design is to illustrate the point that modeling with Active Record is a lot like modeling with standard Ruby classes. If you decided to think of Active Record as being an extension to standard Ruby classes, you wouldn’t be very far off. In practice, this fact makes using Active Record in Ruby quite natural. And because Active Record can reflect on your tables to determine which fields to map automatically, you need to define your attributes in only one place: the database. That’s DRY (don’t repeat yourself)! See Chapter 4 to learn more about Ruby’s syntax, classes, and objects.
Active Record Basics: CRUD
Active Record is a big topic, so let’s start with the basics. You’ve seen the so-called big four earlier, but here they are again: create, read, update, and delete, affectionately known as CRUD. In one way or another, most of what you do with Active Record in particular, and with databases in general, relates to CRUD. Rails has embraced CRUD as a design technique and as a way to simplify the modeling process. It’s no surprise then that this chapter takes an in-depth look at how to do CRUD with Active Record.
Let’s build on the blog application you started in Chapter 3. Although your application doesn’t do much yet, it’s at a stage where it’s easy to demonstrate these concepts in a more concrete fashion.
This section uses the console, so keep it open as you work, and feel free to experiment as much as you want. The more experimentation you do, the deeper your understanding will be.
Creating New Records
You start by creating a new article in the database so you have something to work with. There are a few different ways to create new model objects, but they’re all variations on the same theme. This section shows how each approach works and explains the often subtle differences among them.
Resetting the Database
This command drops the database, recreates it, loads the schema, and seeds your database with seed data according to your db/seeds.rb file (which is empty at this point).
Using the new Constructor
A return of nil always represents nothing. It’s a helpful little object that stands in the place of nothingness. If you ask an object for something and it returns false, then false is something, so it’s not a helpful representation. As a nerdy fact, in logics, false and true are equal and opposite values, but they’re values in the end. The same is true of zero (0). The number 0 isn’t truly nothing—it’s an actual representation of an abstract nothing, but it’s still something. That’s why in programming you have nil (or null in other languages).
Although writer methods look like assignments, they’re really methods in disguise. article.title = 'something' is the functional equivalent of article.title=('something'), where title=() is the method. Ruby provides a little syntactic sugar to make writers look more natural.
Not bad, but you can do even better. The new constructor creates a new object, but it’s your responsibility to save it. If you forget to save the object, it will never be written to the database. There is another method available that combines the creating and saving steps into one.
Using the create Method
You’re getting the hang of this now. To summarize, when you want to create a new object and save it manually, use the new constructor; when you want to create and save in one operation, use create. You’ve already created five new records, which are plenty for now, so let’s move on to the next step: finding records.
Reading (Finding) Records
Now that you have a few articles to play with, it’s time to practice finding them. Every model class understands the find method. It’s quite versatile and accepts a number of options that modify its behavior.
Let’s start with the basics. find is a class method. That means you use it on the model class rather than an object of that class, just as you did the new and create methods. Like new and create , a find operation, if successful, returns a new object.
find(:id): Finds a single record by its unique id or multiple records if :id is an array of ids
all: Finds all records in the table
first: Finds the first record
last: Finds the last record
The following sections go through the different ways to call find and explain how to use each.
Finding a Single Record Using an ID
The find, first, and last methods mostly return a single record. The :id option is specific; you use it when you’re looking for a specific record and you know its unique id. If you give it a single id, it either returns the corresponding record (if there is one) or raises an exception (if there isn’t one). If you pass an array of ids—like [4, 5]—as the parameter, the method returns an array with all records that match the passed in ids. The first method is a little more forgiving; it returns the first record in the table or nil if the table is empty, as explained in the next section.
Here, you store the object that find returned in the local variable article. Then, you can interrogate it and ask for its attributes.
Active Record raises a RecordNotFound exception and tells you it couldn’t find any articles with the id of 1037. Of course it couldn’t. You know that no such record exists. The lesson here is that you use find(:id) when you’re looking for a specific record that you expect to exist. If the record doesn’t exist, it’s probably an error you want to know about; therefore, Active Record raises RecordNotFound.
First, you open a begin block. Then, you cause a RecordNotFound error by deliberately searching for a record that you know doesn’t exist. When the error occurs, Ruby runs the code you put inside the rescue part of the body, which prints a friendly message.
You can put anything you like in the rescue block—you might want to render a specific view here, log the error, or even redirect to another location. Error handling works the same way with other error messages also. If you need to rescue from any error at all, you can just use rescue without specifying an error class.
Finding a Single Record Using first
Keep in mind that this isn’t necessarily the first record in the table. It depends on the database software you’re using and the default order in which you want your records to be retrieved. Usually records are ordered by either created_at or updated_at. If you need to be sure you get the first record you’re expecting, you should specify an order. It’s the equivalent of saying SELECT * FROM table LIMIT 1 in SQL. If you need to find a record and don’t particularly care which record it is, first can come in handy. Note that first doesn’t raise an exception if the record can’t be found.
Finding All Records
So far, you’ve looked at finding a single record. In each case, find, first, or last returns a single Article object. But what if you want to find more than one article? In your application, you want to display all the articles on the home page.
Look closely at the response, and you’ll see that an instance of ActiveRecord::Relation was returned. Most ActiveRecord query methods return an instance of ActiveRecord::Relation. Why do this instead of simply returning an array of Article instances? By returning an ActiveRecord::Relation, it allows more query methods to be chained together and for the SQL to be executed at the last possible moment. We’ll see an example of that soon.
Notice that when you call the order method, it returns an ActiveRecord::Relation object, as you may have expected. As mentioned earlier, one feature of ActiveRecord::Relation is that it allows you to chain calls to multiple methods before sending the command to the database; so you can call all, followed by order, and some other methods we’ll talk about in Chapter 6, to create more precise database queries. Also, Active Record is smart enough to use lazy loading , a practice that only hits the database when necessary—in this example, when you call the each method.
Finding with Conditions
Although finding a record by its primary key is useful, it requires that you know the id to begin with, which isn’t always the case. Sometimes you want to find records based on other criteria. This is where conditions come into play. Conditions correspond to the SQL WHERE clause. If you want to find a record by its title, you call the where method and pass a value that contains either a hash of conditions or an SQL fragment.
Updating Records
This should look pretty familiar by now. The only real difference between this process and the process of creating a new record is that instead of creating a brand-new row, you fetch an existing row. You update the attributes the exact same way, and you save the record the same way. Just as when you create a new record, when save operates on an existing record, it returns true or false, depending on whether the operation was successful.
Deleting Records
You’re finally at the last component of CRUD: delete. When you’re working with databases, you inevitably need to delete records. If a user cancels their order or if a book goes out of stock or even if you have an error in a given row, you may want to delete it. Sometimes you need to delete all rows in a table, and sometimes you want to delete only a specific row. Active Record makes deleting rows every bit as easy as creating them.
There are two styles of row deletion: destroy and delete. The destroy style works on the instance . It instantiates the object, which means it finds a single row first and then deletes the row from the database. The delete style operates on the class, which is to say it operates on the table rather than a given row from that table.
Using destroy
Here, the object instantiation is implicit. You’re still calling the destroy instance method, but you’re not storing an Article object in a local variable first.
Using delete
The second style of row deletion is delete. Every Active Record class has class methods called delete and delete_all. The delete family of methods differs from destroy in that they don’t instantiate or perform callbacks on the object they’re deleting. They remove the row immediately from the database.
Here you specify a single primary key for the article you want to delete. The operation responds with the number of records removed. Because a primary key uniquely identifies a single record, only one record is deleted.
The return of the delete method in this case is 0, since we didn’t have records with id’s 5 and 6 in our database. Zero records were deleted.
Unlike find, which is capable of collecting any arguments it receives into an array automatically, delete must be supplied with an array object explicitly. So, although Model.find(1,2,3) works, Model.delete(1,2,3) fails with an argument error (because it’s really receiving three arguments). To delete multiple rows by primary key, you must pass an actual array object. The following works, because it’s a single array (containing three items) and thus a single argument: Model.delete([1,2,3]).
Deleting with Conditions
The return value of delete_by is the number of records deleted.
When Good Models Go Bad
The app/models/article.rb File
You may have noticed in your generated scaffolding that you use a helper method called errors.full_messages to print out a helpful error message. That helper isn’t black magic; it’s a bit of code that asks the model associated with the form for its list of errors (also referred to as the errors collection) and returns a nicely formatted block of HTML to show the user.
You may have noticed that you call methods in Ruby with a dot (.). For instance, you say article.errors to get the errors collection back. However, Ruby documentation uses the # symbol along with the class name to let the reader know that there is a method it can call on for an instance of that class. For example, on the Article class, you can use the method article.title as Article#title, because it’s something that acts on a particular article but not the Article class itself. You’ve also seen that you can write the code Article.count, because you don’t need to know about a particular @article, but only Article objects in general. Keep this convention in mind when you’re reading Ruby documentation.
Every time you’ve used save before, the model has happily chirped true back to you. But this time, save returns false. This is because before the model allows itself to be saved, it runs through its gauntlet of validations, and one or more of those validations failed.
Voilà! Look how helpful the model is being. It’s passing back an array of error messages.
You get back an empty array, which lets you know that you didn’t find anything.
If you try that on a new object, the errors collection magically fills up with your pretty errors.
Summary
In this chapter, you’ve become familiar with using the console to work with models. You’ve learned how to create, read, update, and destroy model objects. Also, you’ve briefly looked into how to see the simple errors caused by the validations you set up on your model in Chapter 3.
The next chapter discusses how to create relationships (called associations) among your models, and you begin to see how Active Record helps you work with your data in extremely powerful ways. It also expands on the concept of validations and shows how you can do a lot more with validates. You’ll see that Rails provides a bevy of prewritten validators and an easy way to write your own customized validators.