Chapter 5 introduced the basics of Active Record and how to use it. This chapter delves more deeply into Active Record and teaches you how to enhance your models.
Model enhancement is a general term. It refers to endowing your models with attributes and capabilities that go beyond what you get from subclassing ActiveRecord::Base. A model contains all the logic that governs its citizenship in the world of your application. In the model, you can define how it interacts with other models, what a model should accept as a minimum amount of information for it to be considered valid, and other abilities and responsibilities.
Models need to relate to one another. In the real world, bank accounts have transactions, books belong to authors, and products have categories. These relationships are referred to as associations, and Active Record makes them easy to work with. Models also have requirements. For instance, you can’t have a transaction without an amount—it might break your system if someone tried to have an empty transaction. So Active Record gives you easy ways to tell a model what it should expect in order to be saved to the database.
This chapter will teach you how to programmatically enhance your models so they’re more than just simple maps of your tables. To demonstrate the concepts, you build on the blog application you started in Chapter 3, so keep it handy if you want to follow along with the examples.
Adding Methods
That’s all you really need to have Active Record map the users table and get all the basic CRUD functionality described in Chapter 5. But few models are actually this bare.
So far, you’ve left your model classes unchanged. That’s a good thing, and it speaks to the power and simplicity of Active Record. However, it leaves something to be desired. Most of the time, your models need to do a lot more than just wrap a table.
If you’re familiar with SQL, you’re probably feeling that Active Record provides only simple case solutions and can’t handle complicated cases. That’s entirely untrue. Although SQL is useful for highly customized database queries, most Rails projects rarely need to touch SQL, thanks to some clever tricks in Active Record.
The primary way in which you enhance models is by adding methods to them. This is referred to as adding domain logic. With Active Record, all the logic for a particular table is contained in one place: the model. This is why the model is said to encapsulate all the domain logic. This logic includes access rules, validations, relationships, and, well, just about anything else you feel like adding.
In addition to all the column-based reader and writer methods you get by wrapping a table, you’re free to define your own methods on the class. An Active Record subclass isn’t much different from a regular Ruby class; about the only difference is that you need to make sure you don’t unintentionally overwrite any of Active Record’s methods (e.g., find, save, or destroy). For the most part, though, this isn’t a problem.
Custom long_title Method, in app/models/article.rb: https://gist.github.com/nicedawg/0355af37c2e0375b004d4e0c12566b4b
There is no difference between the methods Active Record creates and those you define. Here, instead of asking the model for one of the attributes garnered from the database column names, you define your own method called long_title , which does a bit more than the standard title method.
The methods you add to your models can be as simple as returning true or false or as complicated as doing major calculations and formatting on the object. The full power of Ruby is in your hands to do with as you please.
Don’t worry if you don’t feel comfortable adding your own methods to models just yet. The important part to note from this section is that Active Record models are regular Ruby classes that can be augmented, modified, played with, poked, and turned inside out with sufficient Ruby-fu. Knowing this is extremely helpful in being able to pull back the curtain and understand the advanced features of Active Record.
Some might be nervous by the long_title method you just used. They may see it as a violation of the MVC paradigm. They might ask, “Isn’t formatting code supposed to be in the view?” In general, the answer is yes. However, it often helps to have models that act as intelligent objects. If you ask a model for some information about itself, it’s natural to assume that it can give you a decent answer that doesn’t require a large amount of work later on to figure out what it means. So small formatted strings and basic data types that faithfully represent the data in the model are good things to have in your code.
An intelligent model like this is often called fat. Instead of performing model-related logic in other places (i.e., in controllers or views), you keep it in the model, thus making it fat. This makes your models easier to work with and helps your code stay DRY.
A basic rule of thumb while trying to stay DRY is that if you find yourself copying and pasting a bit of code, it may be worth your time to take a moment and figure out if there is a better way to approach the problem. For instance, if you had kept the Article#long_title formatting outside the model, you might have needed to repeat the same basic string-formatting procedure every time you wanted a human-friendly representation of an article’s title. Then again, creating that method is a waste of time if you’re going to use it in only one place in the application and never again.
This is where programmer experience comes in. As you learn and mature in your Rails programming, you’ll find it easier and easier to figure out where stuff is supposed to go. If you’re always aiming for a goal of having the most maintainable and beautiful code you can possibly write, your projects will naturally become easier to maintain.
Next, let’s look at another common form of model enhancement: associations. Active Record’s associations give you the ability to define in simple terms how models relate to and interact with one another.
Using Associations
It’s a lowly application that has only one table. Most applications have many tables, and these tables typically need to relate to one another in one way or another. Associations are a common model enhancement that let you relate tables to one another.
Associations are natural constructs that you encounter all the time in the real world: articles have comments, stores have products, magazines have subscriptions, and so on. In a relational database system, you relate tables using a foreign key reference in one table to the primary key of another table.
The terms relationship and association can be used pretty much interchangeably. However, when this book refers to associations, it generally means the association on the Active Record side, as opposed to the actual foreign key relationships at the database level.
The example in Figure 6-1 uses a column named article_id in the comments table to identify the related article in the articles table. In database speak, comments holds a foreign key reference to articles.
Sample Foreign Key References
Model | Table | Foreign Key to Reference This Table |
---|---|---|
Article | articles | article_id |
Person | people | person_id |
Friend | friends | friend_id |
Category | categories | category_id |
Book | books | book_id |
Whenever you need one table to reference another table, remember to create the foreign key column in the table doing the referencing. In other words, the model that contains the “belongs_to” needs to have the foreign key column in it. That’s all your table needs before you can put Active Record’s associations to work.
Declaring Associations
has_one
has_many
belongs_to
has_and_belongs_to_many
Given these instructions, Active Record expects to find a table called attachments that has a field in it called message_id (the foreign key reference). It uses this association to let you enter things like Message.first.attachments and get an array (or a collection) of Attachment objects that belongs to the first Message in the database. Moreover, you can work with your associations in both directions. So you can enter Attachment.first.message to access the Message to which the first Attachment belongs. It sounds like a mouthful, but when you get the hang of it, it’s quite intuitive.
Whenever you declare an association, Active Record automatically adds a set of methods to your model that makes dealing with the association easier. This is a lot like the way in which Active Record creates methods based on your column names. When it notices you’ve declared an association, it dynamically creates methods that enable you to work with that association. The following sections go through the different types of associations and describe how to work with them. You also learn about the various options you can use to fine-tune associations.
Creating One-to-One Associations
One-to-one associations describe a pattern where a row in one table is related to exactly one row in another table.
Suppose that in your blog application, you have users and profiles, and each user has exactly one profile. Assume you have User and Profile models, and the corresponding users and profiles tables have the appropriate columns. You can tell your User model that it has one Profile and your Profile model that it belongs to a User. Active Record takes care of the rest. The has_one and belongs_to macros are designed to read like regular English, so they sound natural in conversation and are easy to remember. Each represents a different side of the equation, working in tandem to make the association complete.
Part of the Rails philosophy about development is that the gap between programmers and other project stakeholders should be bridged. Using natural language, such as has one and belongs to, in describing programmatic concepts helps bridge this gap, providing a construct that everyone can understand.
Adding the User and Profile Models
When you started the blog application, you decided to let anyone create new articles. This worked fine when only one person was using the system; but you want this to be a multiple-user application and let different people sign up, sign in, and start writing their own articles separately from one another.
Migration to Create the users Table , db/migrate/20200204011416_create_users.rb
This is standard migration fare. In the change definition, you use the create_table method to create a new users table. The new table object is yielded to the block in the variable, t, on which you call the string method to create each column. Along with the standard email field, you specify a password field, which you use for authentication, as explained in the “Reviewing the Updated Models” section later in this chapter. The primary key, id, is created automatically, so there’s no need to specify it here.
You also have a migration file for the Profile model in db/migrate/20200204013911_create_profiles.rb—feel free to take a peek. Notice the existence of the foreign key for users in the profiles schema. Also recall that you don’t need to specify primary keys in migrations because they’re created automatically.
The User Model, app/models/user.rb: https://gist.github.com/nicedawg/b66208334e6be63b24fcdf1d74eebf3b
The Profile Model, app/models/profile.rb: https://gist.github.com/nicedawg/06e2f87d7159eb1af94a16cde5a9fe06
The has_one declaration on the User model tells Active Record that it can expect to find one record in the profiles table that has a user_id matching the primary key of a row in the users table. The Profile model, in turn, declares that each of its records belongs_to a particular User.
The reload! method reloads the Rails application environment within your console session. You need to call it when you make changes to existing code. It’s exactly as if you had restarted your console session—all the variables you may have instantiated are lost.
The profile failed to save because its user is nil. The belongs_to association also adds a validation to ensure it is present. We could change the Profile class to optionally belong to a user—belongs_to :user, optional: true—but for our purposes, we don’t want profiles to exist without a user.
Using the create_profile method to create a new profile initializes the Profile object, sets its foreign key to user.id, and saves it to the database. This works for any has_one association, no matter what it’s named. Active Record automatically generates the create_#{association_name} method for you. So if you had an Employee model set up with an association like has_one :address, you would get the create_address method automatically.
These alternatives for doing the same thing may seem confusing, but they’re really variations on the same theme. In all cases, you’re creating two objects (the parent and the child) and telling each about the other. Whether you choose to do this in a multistep operation or all on one line is entirely up to you.
Methods Added by the has_one Association in the User/Profile Example
Method | Description |
---|---|
user.profile | Returns the associated (Profile) object; nil is returned if none is found. |
user.profile=(profile) | Assigns the associated (Profile) object, extracts the primary key, and sets it as the foreign key. |
user.profile.nil? | Returns true if there is no associated Profile object. |
user.build_profile(attributes={}) | Returns a new Profile object that has been instantiated with attributes and linked to user through a foreign key but hasn’t yet been saved. |
user.create_profile(attributes={}) | Returns a new Profile object that has been instantiated with attributes and linked to user through a foreign key and that has already been saved. |
Common has_one Options
Option | Description | Example |
---|---|---|
:class_name | Specifies the class name of the association. Used when the class name can’t be inferred from the association name. | has_one :profile, class_name: 'Account' |
:foreign_key | Specifies the foreign key used for the association in the event that it doesn’t adhere to the convention of being the lowercase, singular name of the target class with _id appended. | has_one :profile, foreign_key: 'account_id' |
:dependent | Specifies that the associated object should be removed when this object is. If set to :destroy, the associated object is deleted using the destroy method. If set to :delete, the associated object is deleted without calling its destroy method. If set to :nullify, the associated object’s foreign key is set to NULL. | has_one :profile, dependent: :destroy |
Creating One-to-Many Associations
One-to-many associations describe a pattern where a row in one table is related to one or more rows in another table. Examples are an Email that has many Recipients or a Magazine that has many Subscriptions.
Associating User and Article Models
Just as you associated users and profiles, you want to have a similar relationship between users and articles. You need to add a foreign key user_id in the articles table that points to a record in the users table.
Notice that we used rails g instead of rails generate. Some of the most commonly used Rails commands have a shortcut—rails generate, rails server, rails console, rails test, and rails dbconsole. We’ll generally use the full command in this book, but feel free to use the shortcuts:
Migration to Add User to Articles 20200204020148_add_user_reference_to_articles
We needed to remove null: false because our existing articles don’t yet have a user_id and our migration would have failed.
The Article Model, belongs_to Declaration in app/models/article.rb:https://gist.github.com/nicedawg/53289dd2be8683975f28283761f06fa0
The User Model, has_many Declaration in app/models/user.rb:https://gist.github.com/nicedawg/48f1cdf5917a3322347642e6ada25016
That’s all there is to it. This bit of code has endowed your Article and User models with a lot of functionality.
For has_one and has_many associations, adding a belongs_to on the other side of the association is always recommended. The rule of thumb is that the belongs_to declaration always goes in the class with the foreign key.
Creating a New Associated Object
Your associations are in place; so let’s get back into the code to put what you’ve learned to the test. Do this exercise on the console: either run rails console to start a new console session or type reload! if you still have a console window open from the previous section.
Great! The has_many association is working correctly, and the User instance now has an articles method, which was created automatically by Active Record when it noticed the has_many declaration.
By using the append (<<) operator, you attach Article.first onto your user object. When you use << with associations, it automatically saves the new association. Some things in Active Record don’t happen until you say save, but this is one of the examples where that part is done automatically.
Methods Added by the has_many Association in the User and Article Models
Method | Description |
---|---|
user.articles | Returns an array of all the associated articles. An empty array is returned if no articles are found. |
user.articles=(articles) | Replaces the articles collection with the one supplied. |
user.articles << article | Adds the article to the user’s articles collection. |
user.articles.delete(articles) | Removes one or more articles from the collection by setting their foreign keys to NULL. |
user.articles.empty? | Returns true if there are no associated Article objects for this user. |
user.articles.size | Returns the number of associated Article objects for this user. |
user.article_ids | Returns an array of associated article ids. |
user.articles.clear | Clears all associated objects from the association by setting their foreign keys to NULL. |
user.articles.find | Performs a find that is automatically scoped off the association; that is, it finds only within items that belong to user. |
user.articles.build(attributes={}) | Returns a new Article object that has been instantiated with attributes and linked to user through a foreign key but hasn’t yet been saved. Here’s an example: user.articles.build(title: 'Ruby 1.9'). |
user.articles.create(attributes={}) | Returns a new Article object that has been instantiated with attributes and linked to user through a foreign key and has already been saved. Here’s an example: user.articles.create(title: 'Hoedown'). |
Common has_many Options
Option | Description | Example |
---|---|---|
:class_name | Specifies the class name of the association. Used when the class name can’t be inferred from the association name. | has_many :articles, class_name: 'Post' |
:foreign_key | Specifies the foreign key used for the association in the event that it doesn’t adhere to convention of being the lowercase, singular name of the target class with _id appended. | has_many :articles, foreign_key: 'post_id' |
:dependent | Specifies that the associated objects should be removed when this object is. If set to :destroy, the associated objects are deleted using the destroy method. If set to :delete, the associated objects are deleted without calling their destroy method. If set to :nullify, the associated objects’ foreign keys are set to NULL. | has_many :articles, dependent: :destroy |
There’s much more to has_many associations than can possibly be covered here, so be sure to check out the Rails API documentation (https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many) for the full scoop.
Applying Association Options
It’s time to apply what you’ve learned to your domain model. Specifically, you use the :order option to apply a default order to the User.has_many :articles declaration , and you use the :dependent option to make sure when you delete a user, all their articles are deleted as well.
Specifying a Default Order
A Default Order Added to has_many: https://gist.github.com/nicedawg/68b6e0849dd05c1c70cb3cc076724bc4
You give the name of the field that you want to order by, and then you say either ASC (ascending) or DESC (descending) to indicate the order in which the results should be returned. Because time moves forward (to bigger numbers), you want to make sure you’re going back in time, so you use the DESC keyword here.
ASC and DESC are SQL keywords. You’re actually specifying an SQL fragment here, as discussed in the “Advanced Finding” section later in this chapter.
Adding the Title to the Default Order for has_many: https://gist.github.com/nicedawg/d2054b9db2cfbb105c41c60dd9134f4c
Notice that you use ASC for ordering on the title. This is because as letters go up in the alphabet, their value goes up. So, to sort alphabetically, use the ASC keyword.
Specifying Dependencies
The :dependent Option Added to has_many: https://gist.github.com/nicedawg/820570d78eefefda528ef502de7f1492
By passing in the symbol :destroy, you declare not only that articles are dependent but also that when the owner is deleted, you want to call the destroy method on every related article. This ensures that any *_destroy callbacks on the Article instances are called (callbacks are discussed later, in the “Making Callbacks” section). If you want to skip the callbacks, you can use the :delete option instead of :destroy, which deletes the records directly via SQL.
The :dependent :Option Set to :nullify: https://gist.github.com/nicedawg/82417ace639cd900d89832a4057362fc
Creating Many-to-Many Associations
Sometimes, the relationship between two models is many-to-many. This describes a pattern where two tables are connected to multiple rows on both sides. You use this in the blog application to add categories to articles. If you wanted to allow only one category to be selected for a given article, you could use has_many. But you want to be able to apply multiple categories.
Think about this for a minute: an article can have many categories, and a category can have many articles—where does the belongs_to go in this situation? Neither model belongs to the other in the traditional sense. In Active Record speak, this kind of association is has_and_belongs_to_many (often referred to as habtm for short).
By naming our migration this way and passing the singular names of the models involved, this gave the rails generate command a hint that we wanted to create a join table and populated the resulting migration file with the commands we need.
Remember that when you use create_table inside a migration, you don’t need to specify the primary key, because it’s created automatically. Well, in the case of a join table, you don’t want a primary key. This is because the join table isn’t a first-class entity in its own right. Creating tables without primary keys is the exception and not the rule, so you need to explicitly tell create_table that you don’t want to create an id. It’s easy to forget, so Rails has added a new migration method called create_join_table to take care of that for you.
The db/migrate/20200214004646_create_join_table_articles_categories.rb: File
Adding the has_and_belongs_to_many Declaration in the Article Model app/models/article.rb: https://gist.github.com/nicedawg/c4ddd9231b23014146f1e06efaee8999
Adding the has_and_belongs_to_many Declaration in Category Model app/models/category.rb:https://gist.github.com/nicedawg/df770f7673069c0d2ba0b45b3d8cc54f
Seeding Data
The db/seeds.rb File: https://gist.github.com/nicedawg/9d4c4a01b8453cddc58e46150d38c105
If you need to add more default categories later, you can append them to the seeds file and reload it. If you want to rerun the seed data, the trick lies in the fact that the seeds file doesn’t know whether the records already in the database have to be cleaned up; running rake db:seed again adds all records one more time, and you end up with duplicate user and categories. You should instead call rails db:setup, which recreates the database and adds the seed data as you may expect.
You just did the opposite of the previous test: has_and_belongs_to_many works in both directions, right? So you found your category and asked it for its first article titled “Associations” because that’s what you associated in the other direction.
Using has_and_belongs_to_many is a very simple way to approach many-to-many associations. However, it has its limitations. Before you’re tempted to use it for more than associating categories with articles, note that it has no way of storing additional information on the join. What if you want to know when or why someone assigns a category to an article? This kind of data fits naturally in the join table. Rails includes another type of association called has_many :through , which allows you to create rich joins like this.
Creating Rich Many-to-Many Associations
Sometimes, when you’re modeling a many-to-many association, you need to put additional data on the join model. But because Active Record’s has_and_belongs_to_many uses a join table (for which there is no associated model), there’s no model on which to operate. For this type of situation, you can create rich many-to-many associations using has_many :through. This is really a combination of techniques that ends up performing a similar but more robust version of has_and_belongs_to_many.
The Comment Model in app/models/Comment.rb: https://gist.github.com/nicedawg/1704eb1a760eb147d802b92d20d2ad29
The Article Model in app/models/article.rb: https://gist.github.com/nicedawg/016592081e5ea73a4170af60afa7043a
Nothing is new here—what you implement is very similar to the users and articles relationship you saw earlier, but instead of a user having many articles, an article has many comments.
The Updated User Model, has_many :through Declarations in app/models/user.rb: https://gist.github.com/nicedawg/23d2377fd1ac4d87c992acc341c6b8ad
Notice that you rework how you name associations. One aspect of the Rails philosophy is that you should always be questioning and refactoring your code to work with best practices. In this incarnation, comments that users receive on their articles are called replies.
As an added benefit, has_many :through allows you to easily have nice names for your associations. The :source option lets you define the source name of the association. In this case, the replies are the articles’ comments, so you set the :source option accordingly.
Advanced Finding
Chapter 5 covered use of the find class method in Active Record. This section expands on different find operations using the where method. Building advanced finder methods is one of the most important things you do with your models.
Using the where Method
The hash syntax works well for straightforward where operations where you use only ANDs to join together the conditions (i.e., all conditions must match). However, sometimes you need more flexibility than exact matches.
Using an SQL Fragment
To specify conditions, you can pass in an SQL fragment as a string that is sent directly to the query. You need to have a pretty decent knowledge of SQL to use this kind of syntax; but it provides a lot of flexibility, and you can create arbitrarily complex SQL fragments if you’re an SQL ninja.
You also use the SQL LIKE (modified using NOT, for negation) operator, which allows you to make partial matches. Normally, when using =, SQL requires that the strings match perfectly. However, LIKE is more permissive and allows partial matches when used with the % wildcard. The % symbols are SQL wildcard characters that apply in LIKE clauses. A % at the beginning of a pattern says that the pattern must match at the end of the field (the beginning can be any sequence of characters); a % at the end means that the pattern must match at the beginning, where the end can be any sequence of characters. Using a % on both sides of the pattern means that it must match anywhere in the field. Using %model% means that the word model must occur somewhere (anywhere) in the body of the article. In the previous example, you don’t want articles that have the word model; therefore, an article with the sentence “I don’t have your match” is accepted as a match.
As you can see, this usage has all the flexibility of SQL, but it also has SQL’s natural limitations. For instance, you may need to find information based on what the user passes into the application via the request parameters in your application (Chapter 8 covers request parameters). If you aren’t careful, those data can be very dangerous to your application, because they are open to SQL injection attacks. In such an attack, a user submits malicious code that tricks your database server into doing far more than you intended. For more information about SQL injection, check out the Wikipedia article at https://en.wikipedia.org/wiki/SQL_injection. Fortunately, Rails gives you a way to avoid such threats by using the array condition syntax, which performs correctly quoted replacements.
Using an Array Condition Syntax
The array condition syntax gives you the ability to specify conditions on your database calls in a safer way than using SQL syntax. Also, you don’t need to worry as much about SQL specifics like quoting and other concerns, because it does automatic conversions for you on the inputs you give it. This is how it protects against SQL injection—it ensures that the substituted values are safely quoted, thereby preventing malicious users from injecting arbitrary SQL into your queries.
You can see the SQL statements issued by your application in the file log/development.log. It’s often useful to monitor what the server is doing. You may have already noticed that when you run rails server, it tells you about what is going on in your application. However, different web servers (depending on what you’ve installed) give different outputs, some more descriptive than others.
Fortunately, Rails prints all of its activities to a log file. If you look in your log directory, you see log/development.log. This is the file where all the activities of your application are output. If you’re running in production mode, the log file is log/production.log.
This file is written to live by your server. Sometimes it’s useful (especially on a live server) to monitor the events occurring on your server. If you’re on a UNIX system, you can run the command tail -f log/development.log to get a live feed from your logs. If you’re on a Windows system, you can find several applications that behave like tail with a quick Google search.
Both of these messages print directly to the log file and can be extremely useful for figuring out what is happening with your server.
The main disadvantage with the array syntax is that it can become confusing to remember the order of the elements you’re passing in for the conditions.
As you can see, you can reuse the same term in multiple places in your condition. If you were using the regular array syntax, you’d have to pass the same value '%association%' twice. This is especially useful if you have many, many conditions.
Using Association Proxies
This code returns all the articles of the first user. The all method (off articles) is automatically scoped to the user, which is to say it finds articles that belong to that user. If you recall, articles is a has_many relationship on the User model.
Scoped finders are also more secure. Imagine a multiple-user system where data owned by one user shouldn’t be accessible by another user. Finding an associated object (say, an article) by its id doesn’t restrict it to articles owned by a particular user. You could pass in the article_id and the user_id as conditions, but that’s sloppy and prone to error. The correct way to do this is to scope all find operations off the user in question. For example, assuming you have a User object stored in the variable current_user, current_user.articles.find(1) ensures that the article with id 1 is returned only if it belongs to the current_user.
Anyone who has done database work will realize that this incredibly simple syntax is far easier than the SQL queries that need to be created to achieve similar results. If you play around with these chains, you can check out the log to see the SQL that’s generated—be happy that you didn’t have to write it yourself!
This is much better than the alternative, which is to go through the Article model directly and set the user_id as an attribute (Article.create(user_id: current_user.id). As a rule, whenever you need to restrict find operations to an owner or if you’re assigning ownership, you should use the power of the association proxy.
Other Finder Methods
Some Active Record Finder Methods
Method | Description | Example |
---|---|---|
where(conditions) | Specifies the conditions in which the records are returned as a WHERE SQL fragment. | Article.where("title = 'Advanced Active Record'") |
order | Specifies the order in which the records are returned as an ORDER BY SQL fragment. | Article.order("published_at DESC") |
limit | Specifies the number of records to be returned as a LIMIT SQL fragment. | Article.limit(1) |
joins | Specifies associated tables to be joined in as a JOIN SQL fragment. | Article.joins(:comments) |
includes | Specifies associated tables to be joined and loaded as Active Record objects in a JOIN SQL fragment. | Article.includes(:comments) |
You first retrieve a list of articles with all; then, you retrieve all articles ordered alphabetically by their title using the order method. After that, you retrieve a single article using the limit method. Finally, you chain the limit method to order to retrieve a couple of articles after sorting them. All methods listed in Table 6-6 are chainable; when you chain finder methods to one another, Rails combines their specifics to form a single query to the database.
Default Scope
The default_scope Declaration in app/models/category.rb: https://gist.github.com/nicedawg/f16cbf50672545b569db91995dc1ee6c
As you can see, your categories are sorted alphabetically by default.
Named Scope
The default scope is useful. But in most cases, the only code you want to have there is default ordering for your application, because adding a condition to default_scope would cause that condition to be applied every time. For queries that you run often, you should create named scopes that make your code easier to read and maintain.
Named Scope Declarations in app/models/article.rb: https://gist.github.com/nicedawg/322f265fd4499310098e37b80bc66fef
Recent Named Scope Declaration in app/models/article.rb: https://gist.github.com/nicedawg/aac8a3412e80ea6f3398dfc5817a2f14
Wondering what the strange -> { } syntax is which we use for scopes? It’s a shorthand syntax for generating a lambda—a self-contained standalone method in Ruby, which is executed only when you invoke it. You must use a lambda or some other object that responds to call when defining a scope. By wrapping our code in a lambda, we’re assured it will be reevaluated every time the scope is used. Without using a lambda, it would only be evaluated once, meaning, for example, calling Article.draft may end up returning stale data—including some articles which are no longer drafts—and perhaps omitting new draft articles which were created since the first usage of Article.draft.
The where_title Named Scope Declaration in app/models/article.rb: https://gist.github.com/nicedawg/271672430205b48c7ffb068582991ca8
Applying Validations
Like associations, validations are sets of high-level macros that let you selectively apply common validation requirements to your model’s attributes. In this section, you create a full set of validations for your blog application, and you see firsthand how easy it is to perform basic validations with Active Record. You start by applying some of the built-in validations, and then you build a couple custom validation methods.
Using Built-in Validations
Rails has myriad built-in validators, all of which are accessible through the validates method. Here you will learn about some of the options the validates method accepts as you apply them to your blog application. Check the API for details of all the Rails validators (https://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html).
Default Options for All Validators
Option | Description | Example |
---|---|---|
:message | Specifies the error message shown if validation fails. | message: 'too long' |
:on | Specifies when this validation happens. The default is :save. Other options are :create and :update. | on: :create |
Validating That a Value Has Been Entered
The Article Model, Validating Presence in app/models/article.rb https://gist.github.com/nicedawg/869fe075987f4a8f0d20a1bb1ac1632c
The default message is “can’t be blank.”
Validating That a Value Is Unique
The validates_uniqueness_of Method in app/models/user.rb
When the record is created, a check is performed to ensure no record exists in the database with the given value for the specified attribute email (that maps to a column). When the record is updated, the same check is made, disregarding the record itself. The default error message is “#{value} has already been taken.”
Validating Length or Size
The validates_length_of Method in app/models/user.rb
Options for Validating :length
Option | Description |
---|---|
:minimum | Specifies the minimum size of the attribute. |
:maximum | Specifies the maximum size of the attribute. |
:is | Specifies the exact size of the attribute. |
:in | Specifies the valid range (as a Ruby Range object) of values acceptable for the attribute. |
:allow_nil | Specifies that the attribute may be nil; if so, the validation is skipped. |
:too_long | Specifies the error message to add if the attribute exceeds the maximum. |
:too_short | Specifies the error message to add if the attribute is below the minimum. |
:wrong_length | Specifies the error message to add if the attribute is of the wrong size. |
:message | Specifies the error message to add if :minimum, :maximum, or :is is violated. |
Validating the Format of an Attribute
Update validates :format Method in app/models/user.rb: https://gist.github.com/nicedawg/7814013e0aa05613ea1e8f6a0b6ba89f
Don’t be put off by how complicated this looks. You pass in the :with option and a regex object to say what patterns you want to match.
If you want to learn more about using regular expressions, you can find many tutorials and books on the subject. One good reference is Nathan Good’s Regular Expression Recipes (Apress, 2005).
Validating Confirmation
The validates :confirmation Method in app/models/user.rb: https://gist.github.com/nicedawg/d5398b7f716bfa919a233e6cb68b6925
The password attribute is a column in the users table, but the password_confirmation attribute is virtual. It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation isn’t nil and runs whenever the object is saved.
Other Validations
There is one other important validation helper, :acceptance, which validates the acceptance of a Boolean field.
Building Custom Validation Methods
Adding the published? Method in app/models/article.rb: https://gist.github.com/nicedawg/9751e165bf7106c97044f4b133d1e322
Adding the article_should_be_published Method in app/models/comment.rb
This checks whether you should apply the error by evaluating the if statement. If that if statement is true, you want to add an error into the errors object. Note that before you test whether the article is published, you make sure article isn’t nil. This is so your test doesn’t throw an error. If article is nil, that should be handled by another validator: the validates_presence_of method .
The validate Method in app/models/comment.rb
Making Callbacks
You often want to have certain things happen during the lifecycle of the model. Certain actions need to happen during certain events pertaining to a particular model. For instance, what if you want to send an email to your administrator whenever someone cancels an account? Or perhaps you want to make sure to create a new model because some other model was also created. Sometimes, certain actions in the life of a model should execute associated actions.
before_create
after_create
before_save
after_save
before_destroy
after_destroy
As you can see, the names of the Rails callbacks describe their purpose. When you create a method with any of these names in your model, the method is called automatically by the model during the time the name suggests. For instance, if you make a before_save method , that method is called right before the model object is saved.
This can be a gotcha later if you’re doing something like an assignment of false to a variable. If you’re ever confused why a model won’t save, check your before_ filters.
Adding after_create Method in app/models/comment.rb
The email_article_author Method Specified as an after_create Callback in app/models/comment.rb: https://gist.github.com/nicedawg/03868d69e4b318cfcd73d87fe495dd23
Active Record provides many more callbacks than are mentioned here, but those listed at the beginning of this section are the ones you’ll find yourself using often. Some of the others are used in extremely rare cases (for instance, after_initialize, which is called after an object is initialized). These callbacks can help you with just about anything you need to do during the lifecycle of a model. They’re part of smart models, which know how to deal with their own birth, life, and death. See https://guides.rubyonrails.org/active_record_callbacks.html for more information.
Updating the User Model
You still need to do a little work on your User model. You can apply many of the techniques described in this chapter, such as custom methods to allow you to perform user authentication and validation methods to make sure your data stay clean.
When you created the user migration (Listing 6-2), you added a field called password. This field stores a plain-text password, which, if you think about it, isn’t very secure. It’s always a good idea to encrypt any sensitive data so they can’t be easily read by would-be intruders. You deal with the encryption in the User model itself, but the first thing you do is rename the field in the database from password to hashed_password. This is so you can create a custom accessor called password with which to set the password while maintaining a field to store the encrypted version in the database. The plain-text password is never saved.
Migration to Rename password to hashed_password in db/migrate/20200214024558_rename_password_to_hashed_password.rb: https://gist.github.com/nicedawg/1b40a825d44fb4a99393fec8884f4043
Current User Model in app/models/user.rb: https://gist.github.com/nicedawg/fd5a29e943c06b7e93824a0b71cfd16c
The SHA1 hashing algorithm used in this example is weak and was only used for an example. For production websites, you should take a look at the bcrypt gem (https://github.com/codahale/bcrypt-ruby).
Whenever you store something sensitive like a password, you should encrypt it. To encrypt the password in your User model, you use a simple algorithm called a hash that creates a random-looking string from the provided input. This hashed output can’t be turned back into the original string easily, so even if someone steals your database, they will have a prohibitively difficult time discovering your users’ passwords. Ruby has a built-in library called Digest that includes many hashing algorithms.
require 'digest': You start by requiring the Digest library you use for encrypting the passwords. This loads the needed library and makes it available to work within your class.
attr_accessor :password: This defines an accessor attribute, password, at the top of the class body. It tells Ruby to create reader and writer methods for password. Because the password column doesn’t exist in your table anymore, a password method isn’t created automatically by Active Record. Still, you need a way to set the password before it’s encrypted, so you make your own attribute to use. This works like any model attribute, except that it isn’t persisted to the database when the model is saved.
before_save :encrypt_new_password: This before_save callback tells Active Record to run the encrypt_new_password method before it saves a record. That means it applies to all operations that trigger a save, including create and update.
encrypt_new_password: This method should perform encryption only if the password attribute contains a value, because you don’t want it to happen unless a user is changing their password. If the password attribute is blank, you return from the method, and the hash_password value is never set. If the password value isn’t blank, you have some work to do. You set the hashed_password attribute to the encrypted version of the password by laundering it through the encrypt method.
encrypt: This method is fairly simple. It uses Ruby’s Digest library, which you included on the first line, to create an SHA1 digest of whatever you pass it. Because methods in Ruby always return the last thing evaluated, encrypt returns the encrypted string.
password_required?: When you perform validations, you want to make sure you’re validating the presence, length, and confirmation of the password only if validation is required. And it’s required only if this is a new record (the hashed_password attribute is blank) or if the password accessor you created has been used to set a new password (password.present?). To make this easy, you create the password_required? predicate method, which returns true if a password is required or false if it’s not. You then apply this method as an :if condition on all your password validators.
self.authenticate: You can tell this is a class method because it’s prefixed with self (it’s defined on the class itself). That means you don’t access it via an instance; you access it directly off the class, just as you would find, new, or create (User.authenticate, not @user = User.new; @user.authenticate). The authenticate method accepts an email address and an unencrypted password. It uses a dynamic finder (find_by_email) to fetch the user with a matching email address. If the user is found, the user variable contains a User object; if not, it’s nil. Knowing this, you can return the value of user if, and only if, it isn’t nil and the authenticated? method returns true for the given password (user && user.authenticated?(password)).
authenticated?: This is a simple predicate method that checks to make sure the stored hashed_password matches the given password after it has been encrypted (via encrypt). If it matches, true is returned.
When you ask the User model to authenticate someone, you pass in the email address and the plain-text password. The authenticate method hashes the given password and then compares it to the stored (hashed) password in the database. If the passwords match, the User object is returned, and authentication was successful. When you try to use an incorrect password, nil is returned. In Chapter 8, you write code in your controller to use these model methods and allow users to log in to the site. For now, you have a properly built and secure backend for the way users authenticate.
Current Seeds File in db/seeds.rb: https://gist.github.com/nicedawg/86d1950f400c39eadd23067a3f26bd5e
Reviewing the Updated Models
Current Article Model in app/models/article.rb
Current Category Model in app/models/category.rb
Current Comment Model in app/models/comment.rb
Summary
After reading this chapter, you should have a complete understanding of Active Record models. The chapter covered associations, conditions, validations, and callbacks at breakneck speed. Now the fun part starts. In the next chapter, you get to use all the groundwork established in this chapter to produce the web interface for the data structures you’ve created. This is when you get to reap the benefits of your hard work.