[007.2] Building a To-Do List in Rails Via TDD, Part 2

Test-driving our model layer

Subscribe now

Building a To-Do List in Rails Via TDD, Part 2 [12.05.2017]

Today we will keep working on our app Produciton. We will start this episode with the tag after_episode_007.1.

We implemented some functional tests last time, and today we will start by checking our models and implementing tests as well.

Let's start by creating the model CheckListItem. It will have a reference for Checklist.

rails g model ChecklistItem title:string checklist:references

It creates the model, migration and the factory. Let's go to the test file.

  describe 'validations' do
    it { is_expected.to validate_presence_of(:title) }
  end

  describe 'fields' do
    it { is_expected.to respond_to(:title) }
  end

  describe 'relations' do
    it { is_expected.to belong_to(:checklist) }
  end

Let's execute this test. It fails because we didn't run our migration, and we didn't add the validations in our model.

class ChecklistItem < ApplicationRecord
  belongs_to :checklist
  validates :title, presence: true

Now we can run the migration.

rake db:migrate

And our test passes.

Our Checklist should have many CheckListItems. Let's go to the test for CheckList and add it.

vim spec/models/checklist_spec.rb
  describe 'relations' do
    it { is_expected.to have_many(:checklist_items) }
  end

Executing our test. It fails, because we didn't put the ‘has many’ association in our model. Let's do that.

class Checklist < ApplicationRecord
  validates :title, presence: true, uniqueness: true
  has_many :checklist_items, dependent: :destroy
end

Now, our test passes.

Checklist Template

A checklist can be a template, and we can clone it. Let's add a boolean field called template to our Checklist.

Let's add this in our test. Our checklist should respond to template.

  describe 'fields' do
    it { is_expected.to respond_to(:title) }
    it { is_expected.to respond_to(:template) }
  end

And our test fails, because we don't have the template as a field. Let's run a migration to add this field, and add an index on it. And run our migrations.

rails g migration AddTemplateToChecklists template:boolean:index
rake db:migrate

Now, let's re-run our test, and it should pass. It passes!

Checklist Item as complete or incomplete

A checklist Item can be completed or not. Let's add a boolean called completed in the ChecklistItem. To do this, let's start by creating our test.

vim spec/models/checklist_item_spec.rb
  describe 'fields' do
    it { is_expected.to respond_to(:completed) }
  end

Our test fails, but we can solve that! We’ll create the migration to create this field, and run the migration.

 rails g migration AddCompletedToChecklistItems completed:boolean

And let's now run our test and it passes.

Users can have checklists

In our app, users can have checklists. Let's add this relationship.

 describe 'relations' do
    it { is_expected.to have_many(:checklists) }
  end

Our test does not pass, let's create the relationship. In this case, we will create a migration to add the user reference in the model Checklist. And we will run our migration.

rails g migration AddUsersToChecklists user:references
rake db:migrate

To finish the relationship, we will add the has_many to our User model.


 has_many :checklists, dependent: :destroy
...

Let's re-run our test related to the relationship between User and Checklists. And it passes. We are seeing our code is being driven by our tests, and it makes our life easier when we know what we are doing and our goal when we start the test.

Sharing Checklists

Now that our Users can have checklists, let's add the ability to share these checklists.

We will have a model called ChecklistShare, it will have reference for User and Checklist.

rails g model ChecklistShare user:references checklist:references
rake db:migrate

Ok. Model created and migration created. Let's add the tests.

RSpec.describe ChecklistShare, type: :model do
  describe 'relations' do
    it { is_expected.to belong_to(:user) }
    it { is_expected.to belong_to(:checklist) }
  end
end

So, now once we want to share a Checklist, we will create the relationship between the user we want to share this checklist and the checklist.

Checklist Items can have images

Let's add paperclip to the CheckListItem. For the moment, we will not set up S3.

gem 'paperclip', '~> 5.0.0'

And we will install it by doing a bundle install.

bundle install

And we need to add the image to the CheckListItem. One way we can do this is by using generate script from paperclip. In this case, we will call the image just image. If it was an image from a user it could be called an avatar, for example.

rails generate paperclip checklist_item image

It generates a migration for us. As this is a migration, which is not totally ready for Rails 5. We need to set the rails version of this migration. In this case, we will add the 4.2 at the end of ActiveRecord::Migration.

Now, let's execute it.

rake db:migrate

Migration executed. Now, we need to add the file we have to the model.

vim app/models.checklist_item.rb
...
  has_attached_file :image
  validates_attachment_content_type :image, content_type: %r{/\Aimage\/.*\z/}
...

In this case, we are saying we have an attached file called image and we are validating the content type.

Summary

Today we wrote some model tests, and we have created some models driven by tests. Tomorrow we will keep working on this application and we will write some acceptance tests.

Resources