Overview
By the end of this chapter, you will be able to describe associations and the various types of Active Record associations; implement associations in your application model; evaluate methods that trigger and skip validations; implement model-level validations; develop programs using Rails scaffolding and deploy your application to the World Wide Web using Heroku.
In this final chapter, we'll continue to build upon the previous chapter's application and learn about REST principles and other fundamental features/concepts of Ruby on Rails.
In the previous chapter, we were introduced to the Ruby on Rails framework and how the MVC pattern helps us to create a modular web application. We also learned about the fundamentals of the Rails framework, the anatomy of a Rails application, how to save data in a database, how to interact with data using views, and how to test Ruby on Rails code using the Rails console.
In this chapter, we will dive deep into more advanced topics, such as creating associations between models using Active Record association. Like everything else, Rails has built-in helper methods to assist us. Associations are very commonly used in real-world applications. If we take the example of the citireview application from the previous chapter, we can easily see a relation between a review and its comments. These are joints that link one model to another with the use of primary and foreign keys. We will learn about six types of such associations provided by Rails.
Then, we will understand how validations work in Rails since validations are a critical feature of any application to avoid garbage data getting into the system, especially in the age of bots, when validations are essential for any real-world application. After that, we will make use of a unique Rails feature called scaffolding, for rapid development that will help generate a model, a view, a controller, and their supporting files all in one simple command. Lastly, we will learn how to host a Rails application on the internet using a popular hosting service called Heroku, because no application is complete unless it goes into production and sees the daylight of the World Wide Web.
In the previous chapter, we created a review web application where only authenticated users were allowed to write reviews. Ideally, every review would always allow other users to comment on it, and in this section, we will build that feature.
This means we will create a new model called Comment, which will be associated with the Review model since every review could have many comments, while every comment belongs to one review only. This is called a one-to-many relationship between the two models.
Associations are a set of methods for connecting objects of different models using foreign keys. Associations make it really simple to interact and perform various operations with records using Ruby code.
The following are six types of associations provided by Rails:
Let's discuss them one by one.
In the belongs_to association, the first model needs to have a one-to-one connection with the second model. This means that each record that the first model has belongs to an instance of the second model, thus creating an association. As can be seen in the following diagram, we have two models, Review and Author, where every record in the Review model belongs to only one author and in order to link Review with Author, the id primary key from the Author model acts as an author_id foreign key in the Review model:
Consider the following code snippet:
class Review < ApplicationRecord
belongs_to :author
end
Rails provides helper methods for all associations. In the preceding example, we added the belongs_to helper method to the Review model. It is very important to use singular terms for belongs_to associations, based on the Rails philosophy of convention over configuration. You would get an uninitialized constant Review::Authors error if you used the plural form of an author association in the preceding example. The reason for this is that Rails automatically infers the class name from the association name. Hence, if the association name is in the plural form, then the inferred class will also be in the plural form, which is incorrect.
The has_one association also establishes one-to-one mapping with another model but in a different manner. This association suggests that one instance of a model will have only one occurrence in another model. For example, only one user profile can be created using a particular email address on any portal, as shown in the following diagram:
Consider the following code snippet:
class Profile < ApplicationRecord
has_one :user
end
In this example, we have added a has_one association to the Profile model since every Profile can have only one user.
The distinction between belongs_to and has_one largely depends on where the foreign key is placed. Considering the preceding example, we can call the association belongs_to when one user creates user accounts on multiple platforms using the same email address. Here, the foreign key is the email address that is linked to multiple platforms.
This association generates a one-to-many connection with another model. This is exactly the opposite of a belongs_to association. It specifies that each instance of the model has zero or more occurrences in the other model. For example, on a publishing portal, one author may have more than one review, as depicted in the following example:
Consider the following code snippet:
class Author < ApplicationRecord
has_many :reviews
end
When declaring a has_many association, the name of the other model must be in the plural form.
The has_many :through association is used to build multiple connections to another model. This association indicates that the model in which said association is declared can be associated with none or more than one occurrence of another model by progressing through a third model. For example, in a hospital, Doctors and Patients have interaction via a third model, Appointments, as shown in the following figure:
Consider the following code snippet:
class Doctor < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :doctor
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :doctors, through: :appointments
end
Here, we have three models where Doctor can have many patients and a patient can have many doctors. All of the models link with each other via the Appointment model such that Doctor can have many appointments and Patient can have many appointments, but an appointment belongs to a doctor and a patient. In short, the Patient model is associated with the Doctor model through the Appointment model.
This type of association is used to initiate a one-to-one connection with another model. It indicates that an association that is declared in a model can be linked with an occurrence of another model by using a third model. For example, every Contractor has one account on a certain portal, and every Account is linked with AccountHistory. The association should look like the following diagram:
Consider the following code snippet:
class Contractor < ApplicationRecord
has_one :account
has_one :account_history, through: :account
end
class Account < ApplicationRecord
belongs_to :contractor
has_one :account_history
end
class AccountHistory < ApplicationRecord
belongs_to :account
end
Here, we have three models, Contractor, AccountHistory, and Account, where every contractor has one account and account history and every AccountHistory belongs to an account, while an Account has one account history but belongs to a contractor. In short, Contractor and AccountHistory have one-to-one mapping through the Account model.
This type of association builds direct multiple links with another model, without using a third model. For example, a discussion forum may have multiple users and groups within it. Each user may be part of numerous groups and every group may have numerous users who contribute to a discussion, as depicted in the following diagram:
Consider the following code snippet:
class User < ApplicationRecord
has_and_belongs_to_many :groups
end
class Group < ApplicationRecord
has_and_belongs_to_many :users
end
In this example, unlike HABTM (Has And Belongs To Many) associations, we use the has_and_belongs_many helper method to build an association between the User and Group models via the user_groups model.
In Chapter 11, Introduction to Ruby on Rails I, we built a review application model and added an authentication sign-in page to the model. In this exercise, we will create a comment model and associate it with the Review model, and also run the migration. Remember, each review can have many comments but every comment belongs to only one review. We will add a form after a review for users to add comments.
The following steps will enable you to complete the exercise:
$ rails generate model Comment commenter:string body:text review:references
The output should be as shown in the following screenshot:
Here, we have generated a new model comment with commenter, body, and a foreign_key columns for the Review model. Let's open the migration file to understand this better. Open the db/migrate/20190801052630_create_comments.rb file:
class CreateComments < ActiveRecord::Migration[5.1]
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :review, foreign_key: true
t.timestamps
end
end
end
This migration will create a table, comments, with commenter, body, and review attributes.
The references keyword is a special datatype for models. The columns that will be created in the database will have their names appended with _id, which will hold an integer value of id from the review table.
$ rails db:migrate
The output should be as shown in the following screenshot:
class Comment < ApplicationRecord
belongs_to :review
end
Here, we have created an association between the two models where every record of the Comment model belongs to a review.
class Review < ApplicationRecord
has_many :comments
end
Once we have added the associations, associated behavior between the two models is enabled. For example, if we have 10 comments belonging to a review and we create a review object such as @review, we will get access to many methods that are already available for us to use, such as @review.comments, which will give associated comments for this review.
$ rails generate controller Comments
The output should be as shown in the following screenshot:
This command has generated our required folder structure for the comment feature. Next, let's add a form to submit a comment.
show.html.erb
1 <h1>Citireviews Review for </h1>
2 <p>
3 <strong>Name:</strong>
4 <%= @review.name %>
5 </p>
6
7 <p>
8 <strong>Description:</strong>
9 <%= @review.description %>
10 </p>
11 <h2>Write your comment:</h2>
12 <%= form_with(model: [ @review, @review.comments.build ], local: true) do 13 |form| %>
14 <p>
15 <%= form.label :commenter %><br>
Here, we have created a form that will allow the user to submit a comment for a review. On submission, the create action will be called in the controller. Before that, we should also add routes for the comments resource.
resources :reviews do
resources :comments
end
These are nested routes that create comments as a nested resource inside reviews.
class CommentsController < ApplicationController
def create
@review = Review.find(params[:review_id])
@comment = @review.comments.create(comment_params)
redirect_to review_path(@review)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
Here, before we persist the comment, we first find the review that is associated with this comment and create a Review object and assign it to the @review instance variable. We then make use of the methods available with Active Record associations in order to insert the record into our database. Of course, as implemented in the previous chapter, we allow strong parameters by declaring them in the comment_params method.
$ rails server
Open the browser and access http://localhost:3000/reviews/3 to find any review. It should look as shown in the following figure:
Once the comment is added, the Create Comment button should be highlighted as shown in the following figure:
Write the review and submit it with Create Comment.
We did not get an error, but we cannot see whether our comment was saved or not. Try creating more comments, but to check whether the comments are saved refer to the Terminal window to see a SQL query being executed as shown in the following figure:
In the next step, we will display these comments associated with a review and add a few more comments so that you can see them all.
show.html.erb
29 <h1>Citireviews Review for </h1>
30 <p>
31 <strong>Name:</strong>
32 <%= @review.name %>
33 </p>
34
35 <p>
36 <strong>Description:</strong>
37 <%= @review.description %>
38 </p>
39 <br/>
40 <strong><i>Comments for this review:</i></strong>
41 <% @review.comments.each do |comment| %>
42 <p>
43 <strong>Commenter:</strong>
Here, we have added a section to display all the comments associated with this review. Run rails server to check it on the browser. It should display as shown in the following figure:
Now, if you submit a fresh comment, it will be displayed immediately in the list of comments associated with this review.
Thus, we have successfully created a Review model for the application.
Validations are added to an application so that only valid data is saved to your database – in other words, only keeping the signal and avoiding noise. For example, a user does not add their name in the age section, but uses a proper email ID format, and adds a proper cell phone number to validate the data on a portal, which is the basic requirement of the portal.
There are various ways and junctures at which we can set these validations in an application before the data gets persisted to the database. Mainly, there are four types of such validations:
Let's look at each of these types one by one.
With database validations, we use database constraints or store procedures to check whether valid data is being stored. This is difficult to maintain since at the database level, changes are difficult to implement and there is more resistance against future changes. However, at the database level, validations such as uniqueness validations absolutely belong to the models as well.
Validations can be implemented in a controller, but this is against the philosophy of thin controllers, hence it's better to keep them at the model level only. Also, models could be interacted with by different controllers. Data validation is a concern at the data level, which is the model's concern.
These are the first alerts when it comes to a validation example adding constraints right in the form that adds data. However, they are unreliable if they are the only ones to be used since they can be easily bypassed. But in combination with other validations, they are very useful.
This is the best place for adding any validations before your data gets saved in the database. Also, these are database-agnostic validations so, irrespective of which database is used now or is changed later, validations will work. Rails makes it really easy to add validations with lots of helper methods that augment our own custom code.
With Active Record, there are certain methods that trigger validations before saving data in the database. The following is a list of such methods:
With Active Record, there are certain methods that skip validations before saving data in the database. The following is a list of such methods:
While the save method triggers validation, there is a way to bypass this by adding a flag along with it, as in save(validate: false).
Rails runs a validation before saving an Active Record object. If the validation criteria are not met, the object is rejected. To test these validations, there are helper methods available: valid? and invalid?. You can try them on your model in the Rails console.
Consider the following example:
class User < ApplicationRecord
validates :name, presence: true
end
The output in the Rails console would be as follows:
User.create(name: "Steve Carell").valid? # => true
User.create(name: nil).valid? # => false
User.create(name: nil).invalid? # => true
Here, we have created a User model, which could have many fields, such as name, age, location, and so on, with validation on the name parameter. At the time of creating a record, this record validation triggers, and using the valid? and invalid? helper methods, we can check whether the result would be true or false.
In this exercise, we will add validation for the citireview application such that every review must have a name and a comment body should not exceed more than 50 characters:
class Review < ApplicationRecord
has_many :comments
validates :name, presence: true
end
With the presence of validations, if the name field is empty, data won't be saved in the database.
$ rails server
The output would be as follows:
Redirection to the home page will look as follows:
Check the Rails server in the Terminal window to see the redirection operation as shown in the following figure:
As you can see, the reason why we did not see the review listed is because the record was never created in our database and the transaction was rolled back.
class Comment < ApplicationRecord
belongs_to :review
validates :body, length: { maximum: 50 }
end
Similar to the previous step, we have added a validation where we check that the text added in the body field is not more than 50 characters.
Submit it to see whether it gets saved and displayed instantly:
The comment failed to save since it was over 50 characters.
Rails scaffolding is a feature in Rails that helps to autogenerate code. In Ruby on Rails, this refers to the autogeneration of Model, View, and Controller along with CRUD operations, usually for a single database table. This saves a lot of time and is yet another feature of Rails for speedy development.
The following is the scaffold command:
$ rails generate scaffold ScaffoldName [attributes]
This one-line command will set up the model, controller, views, migrations, and test suite – all in one go.
In this exercise, we will create a Rails application for a store allowing a user to enter a list of products with two fields, name and price:
$rails new scaffold_app
The output should be as follows:
This will generate our new Rails application. Create a Store model to save the name and description of a product using the following scaffold command:
$rails generate scaffold Store name:string description:string
As you can see, we have a model, controller, views, migrations, and assets all set up in just one command. Earlier, we saw that we had to generate every part using different Rails generators; scaffolding, therefore, saves a lot of time.
$rails db:migrate
The output should be as follows:
As you can see, our Stores table is created with this migration, which was generated in step 2.
$rails server
The output should be as follows:
At present, there is no data in this application. Click on New Store to add data.
Our record has been successfully created.
So, on the index page of this resource, we can now see a list of data from this model.
As you have seen, with scaffolding, generating repetitive CRUD operation code is quick and easy.
In this activity, we will create an application model for a blogging platform. The activity has to be performed in the following parts:
Part I – Create a blog application with the following features:
Part II – Host the application on a cloud platform such as Heroku so that it is publicly available.
Use the Rails scaffolding feature to build the application rapidly.
The following steps will help you with the solution:
Part I – Create a blog application with the following features
Part II – Host the application on a cloud platform such as Heroku so that it is publicly available
$ heroku create
$ git push heroku master
$ heroku run rails db:migrate
Expected Output:
Note
The solution to the activity can be found on page 495.
In this chapter, we deep-dived into the Rails framework and learned about features such as Active Record associations, which are essential for any real-world application, and with six associations provided by Rails, working with models and databases becomes easier and more organized. We then learned about validations and the various options Rails provides. After that, we learned about scaffolding in Rails, which is a very powerful tool for rapid application development. Lastly, we learned about how to host our application on the World Wide Web with a popular hosting service called Heroku.
We have learned a lot in the last 12 chapters and we are confident that you will be excited to work with one of the most beautiful and powerful programming languages, Ruby (with the equally powerful Rails framework) – for your next software assignment.
3.135.187.210