Model

Model is the first component of MVC. It's the core of data management in Rails because it lives between the controller and the database. This is done by Object-relational Mapping we discussed earlier. It's an abstraction designed in a way such that you don't have to directly interact with the database using SQL.

Model is a generic name for any MVC frameworks. For Rails, it's been given a special name ActiveRecord. So, in the future, when you are dealing with ActiveRecord in Rails, you are working with the Model part of MVC.

ActiveRecord Models and Database Tables are coupled together. When you create a table called books in the database, a model called Book will be generated and linked to the database model.

Let's try to implement our previous relational database example with books and authors in ActiveRecord in Rails.

So, in order to store data about books, let's create both the model called Book and the actual table books in our database.

In Rails, you can run $ rails generate model <name in singular form> to generate the necessary files to get started.

$ rails g model book

Running via Spring preloader in process 17589
      invoke  active_record
      create    db/migrate/20161212100533_create_books.rb
      create    app/models/book.rb
      invoke    test_unit
      create      test/models/book_test.rb
      create      test/fixtures/books.yml

Notice that book is singular because all model names are singular in rails

Here, db/migrate/xxxx_create_books.rb is the migration file necessary to modify the database schema. app/models/book.rb is the model file for the MVC framework.

In db/migrate/xxxx_create_books.rb,

class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|

      t.timestamps
    end
  end
end

Let's look at this file:

  • We are creating a new class called CreateBooks which will inherit ActiveRecord::Migration. We don't know what's in the superclass ActiveRecord::Migration, but that's fine for now. We just need to know the conventions of using it. That's the point of using Rails.
  • The instance method change is for changing and modifying the database schema.
  • Inside the change method, we have another method called create_table with the parameter :books as the table name. This means that we are trying to create a table called books in the database.
  • Also, what is t.timestamps? This will create the timestamp for created_at and updated_at to track the time when a record is created or updated.

Inside create_table, we can configure how we like the table to be. For example, we want the table books to have the following attributes: name, publishing_year and num_of_pages (number of pages).

So, how do we modify this file to add the attributes name, publishing_year and num_of_pages?

class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      # t.<data type> :<attribute name>
      t.string  :name
      t.string  :publishing_year
      t.integer :num_of_pages

      t.timestamps
    end
  end
end
  • t.string :name defines that we want an attribute called name with the data type string
  • t.string :publishing_year defines an attribute called publishing_year with the data type string
  • t.integer :num_of_pages defines an attribute called num_of_pages with the data type integer (number)

Now, we have finished defining how we want to modify the database schema. But, have we actually modified the schema yet? No. We were only defining the changes. If we want it to take effect, we have to run another command to execute the changes.

In order to modify the database schema, we need to not only define but also "migrate" migration files.

$ rails db:migrate

== 20161212101030 CreateBooks: migrating ======================================
-- create_table(:books)
   -> 0.0020s
== 20161212101030 CreateBooks: migrated (0.0022s) =============================

This log message tells you that your migration file has successfully modified the database schema.

You can check the migration file status with $ rails db:migrate:status

Status   Migration ID    Migration Name
--------------------------------------------------
  up     20161212101030  Create books

Great! We have successfully created a table in our database, just like how we created a new spreadsheet in Excel. We are ready to put in some data. In Excel, it would be easy. I just click and add some rows of data. But, how do you do it in Rails?

Well, remember MVC's Model is automatically linked to the database? Let's look at the other file that we've generated app/models/book.rb.

In app/models/book.rb,

class Book < ApplicationRecord
end

We have a Book class inheriting the superclass ApplicationRecord. We've defined nothing in Book yet. But, Book has already linked with the books table in the database.

Let's try to use the model Book to create a new record for the books table in the database. We first need to go into Rails console.

Why do we need Rails console? Because we want to run code in Rail's environment.

$ rails console

Loading development environment (Rails 5.0.0.1)
irb(main)>

To exit Rails console, simply type "exit" and hit enter

Now, let's try to insert a new record on the books table through the model Book with Book.create.

irb(main)> Book.create

   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "books" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", 2016-12-13 01:51:52 UTC], ["updated_at", 2016-12-13 01:51:52 UTC]]
   (1.0ms)  commit transaction
=> #<Book id: 1, name: nil, publishing_year: nil, num_of_pages: nil, created_at: "2016-12-13 01:51:52", updated_at: "2016-12-13 01:51:52">

Let's look at the log here.

  • begin transaction signals that it has initiated the process with the database.
  • A "transaction" occurs every time you create / update / delete record from the database.
  • SQL (0.6ms) INSERT INTO "books" ("created_at", "updated_at") VALUES (?, ?) [["created_at", 2016-12-13 01:51:52 UTC], ["updated_at", 2016-12-13 01:51:52 UTC]] is SQL that was generated for you when you use Book.create, so you don't need to type it yourself. This is where the ORM magic happens. Remember we said this earlier:
  • We use SQL to add, update, retrieve and delete records. There are simple and advanced operations in which you can do. Now, a lot of MVC frameworks recognize that SQL is not a friendly language to use in general practice. You should be able to achieve the same SQL tasks by using, say, Ruby or JavaScript.

  • commit transaction is important because the keyword "commit" signals that a transaction is successful. On the contrary, "rollback" signals a transaction has failed, hence rolling back to the previous state.

  • #<Book id: 1, name: nil, publishing_year: nil, num_of_pages: nil, created_at: "2016-12-13 01:51:52", updated_at: "2016-12-13 01:51:52"> is the new database record represented as a Ruby object.

Notice that, name, publishing_year and num_of_pages are all nil? That's because we didn't pass those attributes when we created the record. So, let's try to put these attributes in this time creating a new record.

irb(main)> Book.create(name: "Harry Potter and the Philosopher's Stone", publishing_year: "1997", num_of_pages: 223)

   ...
   (0.7ms)  commit transaction
=> #<Book id: 2, name: "Harry Potter and the Philosopher's Stone", publishing_year: "1997", num_of_pages: 223, created_at: "2016-12-13 02:05:10", updated_at: "2016-12-13 02:05:10">

Now, we have 2 records in the books table. How do we check it?

irb(main)> Book.count

   (0.2ms)  SELECT COUNT(*) FROM "books"
=> 2

So, Book.count returned 2. The .count is a class method inherited from ApplicationRecord. Its job is to generate the raw SQL SELECT COUNT(*) FROM "books", send this SQL to the database, and return the number of records in the books table.

How do you access the first record we made and update it? Use Book.find(<id of the record>).

irb(main)> Book.find(1)

  Book Load (0.3ms)  SELECT  "books".* FROM "books" WHERE "books"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<Book id: 1, name: nil, publishing_year: nil, num_of_pages: nil, created_at: "2016-12-13 01:51:52", updated_at: "2016-12-13 01:51:52">

The ID of the first record starts at 1.

How do you show all records we have created? Book.all.

irb(main)> Book.all

  Book Load (0.2ms)  SELECT "books".* FROM "books"
=> #<ActiveRecord::Relation [
     #<Book id: 1, name: nil, publishing_year: nil, num_of_pages: nil, created_at: "2016-12-13 01:51:52", updated_at: "2016-12-13 01:51:52">,
     #<Book id: 2, name: "Harry Potter and the Philosopher's Stone", publishing_year: "1997", num_of_pages: 223, created_at: "2016-12-13 02:05:10", updated_at: "2016-12-13 02:05:10">
   ]>

Now you see that Rails has made it super easy for us to interact with the database because it generates all the raw SQL queries for us. The "Rails way of doing things" is also more human friendly and pleasant to use.

So, let's edit our Book model file book.rb to add some data validations. For example, let's make sure a book can only be created when the name of the book is present.

In app/models/book.rb,

class Book < ApplicationRecord
  validates :name, presence: true
end

Here, we are validating the presence of the attribute name. It means that name cannot be empty or nil.

We need to exit and re-enter Rails console for our changes to take effect. To exit Rails console: ctrl + c. To get back into Rails console $ rails c.

irb(main)> Book.create

   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Book id: nil, name: nil, publishing_year: nil, num_of_pages: nil, created_at: nil, updated_at: nil>

Now, we see that Book.create with no attributes didn't work as its transaction has been rollbacked because the attribute name is not present.

irb(main)> Book.create(name: 'test')

   ...
   (0.8ms)  commit transaction
=> #<Book id: 4, name: "test", publishing_year: nil, num_of_pages: nil, created_at: "2016-12-13 03:23:32", updated_at: "2016-12-13 03:23:32">
irb(main):003:0>

This time, transaction goes through when the attribute name is present.

results matching ""

    No results matching ""