Search
⌘K
    to navigateEnterto select Escto close

    Model validations

    When we are creating a new task with a blank title, the application will create a new task with no title. We don't want the title to be blank. We want the title field to be filled before a task is created.

    Making title field a required field

    We can edit the Task model to add a validation.

    Navigate to app/models/task.rb and add the following code for validating the title:

    1class Task < ApplicationRecord
    2  validates :title, presence: true
    3end

    Verifying validation

    Before saving a record to the database, Rails runs validations on these Active Record objects. If these validations produce any errors, Rails will not save the object.

    Let's fire up Rails console and let's create an instance of Task model using the new method. We are going to use reload! in console for the folks who already had the console open:

    1$ bundle exec rails console
    2
    3irb(main):011:0> reload!
    4
    5irb(main):011:0> t1 = Task.new(title: "I am a task with title")
    6=> #<Task id: nil, title: "I am a task with title", created_at: nil, updated_at: nil>
    7
    8irb(main):012:0> t2 = Task.new
    9=> #<Task id: nil, title: nil, created_at: nil, updated_at: nil>

    Now we have two instances t1 and t2. t1 has a title. t2 has no title.

    We can run validations using the built-in method valid?. Invoking valid? on an Active Record object runs validations and returns true if there are no errors and returns false if the record has any error:

    1irb(main):013:0> t1.valid?
    2=> true
    3
    4irb(main):014:0> t2.valid?
    5=> false

    valid? returns true for t1 and false for t2.

    We can check for the errors associated with object t2 using the errors.messages method:

    1irb(main):015:0> t2.errors.messages
    2=> {:title=>["can't be blank"]}

    As we can see the object t2 is not valid because we did not provide any title and when we check the error messages we can see the reason for it.

    Caveats with validations at the Rails level

    Let's execute a raw sql query and see what happens when we don't pass any value for title field. Here we are directly trying to add a record in the database and we are bypassing Active Record model.

    1irb> sql = "INSERT INTO TASKS (id, created_at, updated_at) VALUES (30, '2019-02-07 10:01:47', '2019-02-07 10:01:47')"
    2=> "INSERT INTO TASKS (id, created_at, updated_at) VALUES (30, '2019-02-07 10:01:47', '2019-02-07 10:01:47')"
    3
    4irb> ActiveRecord::Base.connection.execute(sql)
    5   (23.3ms)  INSERT INTO TASKS (id, created_at, updated_at) VALUES (30, '2019-02-07 10:01:47', '2019-02-07 10:01:47')
    6=> []
    7
    8==> Task.last
    9Task Load (0.2ms)  SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."id" DESC LIMIT ?  [["LIMIT", 1]]
    10=> #<Task id: 30, title: nil, created_at: "2019-02-07 10:01:47", updated_at: "2019-02-07 10:01:47">

    We are able to create a record without title. What it means is that just the validation in the model is not enough. We also need to make title not nullable at the database level.

    Let's add title to all the existing tasks. If there are records with null values for title then we can't make title not nullable at the database level:

    1$ bundle exec rails console
    2
    3==> Task.update_all title: "my title"
    4UPDATE "tasks" SET "title" = ?  [["title", "my title"]]

    Adding migration to make title field NOT NULL

    Let's add a constraint at the database level to not to allow null values for title field. Let's generate a migration to make this change:

    1bundle exec rails g migration MakeTitleNotNullable
    2Running via Spring preloader in process 24660
    3      invoke  active_record
    4      create    db/migrate/20190211162942_make_title_not_nullable.rb
    

    Now let's open this migration file and add the code to make title not nullable:

    1class MakeTitleNotNullable < ActiveRecord::Migration[6.1]
    2  def change
    3    change_column_null :tasks, :title, false
    4  end
    5end

    Run the command rails db:migrate to apply the migration:

    1$ bundle exec rails db:migrate
    2== 20190211162942 MakeTitleNotNullable: migrating ===========================
    3-- change_column_null(:tasks, :title, false)
    4   -> 0.0044s
    5== 20190211162942 MakeTitleNotNullable: migrated (0.0045s) ==================
    6
    

    Open the file db/schema.rb under the db folder. We can see that now "title" field has null: false statement.

    Verifying validation using console

    Now fire up the console again using rails c. Let's try creating a new task the same way we created before by inserting into the database directly:

    1#=> bundle exec rails console
    2
    3irb(main):002:0> sql = "INSERT INTO TASKS (id, created_at, updated_at) VALUES (4, '2019-02-07 10:01:53', '2019-02-07 10:01:53');"
    4=> "INSERT INTO TASKS (id, created_at, updated_at) VALUES (4, '2019-02-07 10:01:53', '2019-02-07 10:01:53');"
    5
    6irb(main):003:0> ActiveRecord::Base.connection.execute(sql)
    7   (0.7ms)  INSERT INTO TASKS (id, created_at, updated_at) VALUES (4, '2019-02-07 10:01:53', '2019-02-07 10:01:53');
    8Traceback (most recent call last):
    9        1: from (irb):3
    10ActiveRecord::NotNullViolation (SQLite3::ConstraintException: NOT NULL constraint failed: tasks.title: INSERT INTO TASKS (id, created_at, updated_at) VALUES (4, '2019-02-07 10:01:53', '2019-02-07 10:01:53');)
    11irb(main):004:0>
    

    As expected, it throws a violation NotNullViolation which means our title can't be nil.

    Adding length validation to title field

    Similarly, we can also add length validation to the ticket field. To add length validation we can use length validator method:

    1class Task < ApplicationRecord
    2  validates :title, presence: true, length: { maximum: 125 }
    3end

    This validation ensures that the length of the title would not exceed 125 characters.

    We should always use constants to store values such as maximum lengths and use the constant in our code instead of hardcoding the value each time. This is because, if we need to update the maximum length for an attribute, we will only need to update the value stored in the constant instead of updating the value throughout the codebase.

    Let's add a constant for the maximum length of the task title in an initializer file called constants. This constants initializer will be loaded automatically from config/initializers/constants.rb, by Rails, after it loads the framework plus any gems and plugins in our application.

    Let's create constants.rb file:

    1touch config/initializers/constants.rb

    Now add the following lines of code into constants.rb:

    1module Constants
    2  MAX_TASK_TITLE_LENGTH = 125
    3end

    Now, update the following line of code in TaskModel:

    1class Task < ApplicationRecord
    2  validates :title, presence: true, length: { maximum: Constants::MAX_TASK_TITLE_LENGTH }
    3end

    Let's commit these changes:

    1git add -A
    2git commit -m "Added constraint to make title not nullable"
    Previous
    Next