Oh, CRUD!
In chapter 3, you began writing stories for a CRUD (create, read, update,
delete) interface for your Project resource. Here you continue in that
vein, beginning with writing a story for the r part of CRUD:
reading. We often refer to reading as viewing in this
and future chapters—we mean the same thing, but sometimes viewing is a better word.
For the remainder of the chapter, you’ll round out the CRUD interface for projects, providing your users with ways to edit, update, and delete projects too. Best of all, you’ll do this using behavior-driven development (BDD) the whole way through, continuing your use of the RSpec and Capybara gems that we saw in use in the last chapter. This chapter’s length is testament to exactly how quickly you can get some CRUD actions up and running on a resource with Ruby on Rails.
Also in this chapter, you’ll see a way to create test data extremely
easily for your tests, using a gem called factory_bot, as well as a way to
make standard controllers a lot neater.
Viewing projects
The show action generated for the story in chapter 3 was only half of this
part of CRUD. The other part is the index action, which is responsible for
showing a list of all of the projects. From this list, you can navigate to the show
action for a particular project. The next story is about adding functionality
to allow you to do that.
Create a new file in the features directory called
spec/features/viewing_projects_spec.rb, shown in the following listing.
require "rails_helper"
RSpec.feature "Users can view projects" do
scenario "with the project details" do
project = FactoryBot.create(:project, name: "Visual Studio Code")
visit "/"
click_link "Visual Studio Code"
expect(page.current_url).to eq project_url(project)
end
end
To run this single test, you can use bundle exec rspec
spec/features/viewing_projects_spec.rb. When you do this, you’ll see the
following failure:
1) Users can view projects with the project details
Failure/Error: project = FactoryBot.create(:project,
name: "Visual Studio Code")
NameError:
uninitialized constant FactoryBot
The FactoryBot constant is defined by another gem: the factory_bot gem.
Introducing Factory Bot
The factory_bot gem, created by thoughtbot (http://thoughtbot.com),
provides an easy way to use factories to create new objects for your tests.
Factories define a bunch of default values for an object, allowing you to
easily craft example objects you can use in your tests.
Before you can use this gem, you need to add it to the :test group in your Gemfile.
Now the entire group looks like this:
group :test do
gem "capybara"
gem "factory_bot_rails", "~> 6.2"
end
To install the gem, run bundle. With the factory_bot_rails gem installed, the
FactoryBot constant is now defined. Run bundle exec rspec
spec/features/viewing_projects_spec.rb
again, and you’ll see a new error:
1) Users can view projects with the project details
Failure/Error: project = FactoryBot.create(:project,
name: "Visual Studio Code")
ArgumentError:
Factory not registered: project
When using Factory Bot, you must create a factory for each model you wish to
use the gem with. If a factory isn’t registered with Factory Bot, you’ll get the previous error. To register/create a factory, create a new directory in the spec directory called factories, and then in that directory create a new file called projects.rb. Fill that file with the content from the following
listing.
FactoryBot.define do
factory :project do
name { "Example project" }
end
end
When you define the factory in this file, you give it a name attribute, so that every
new project generated by the factory via FactoryBot.create :project will have the
name "Example project". The name: "Visual Studio Code" part of this method call in
spec/features/viewing_projects_spec.rb changes the name for that instance to the one
passed in. You use factories here because you don’t need to be concerned about
any other attribute on the Project object. If you weren’t using
factories, you’d just create the project the way you would anywhere else, like in the console:
Project.create(name: "Visual Studio Code")
Although this code is about the same length as its FactoryBot.create
variant, it isn’t future proof. If you were to add another field to the
projects table and add a validation (say, a presence one) for that field,
you’d have to change all occurrences of the create method in your tests to contain this
new field. Over time, as your Project model gets more and more attributes, the
actual information that you care about — in this case, the name — would get lost
in amongst all of the unrelated data. When you use a factory, you can change it in one place: where the
factory is defined. If you cared about what that field was set to, you could
modify it by passing it as one of the key-value pairs in the Factory call.
That’s a lot of theory—how about some practice? Let’s see what happens
when you run bundle exec rspec spec/features/viewing_projects_spec.rb
again:
1) Users can view projects with the project details
Failure/Error: click_link "Visual Studio Code"
Capybara::ElementNotFound:
Unable to find link "Visual Studio Code"
A link appears to be missing. You’ll add that right now.
Adding a link to a project
Capybara is expecting a link on the page with the words "Visual Studio Code", but
can’t find it. The page in question is the homepage, which is the index
action from your ProjectsController. Capybara can’t find it because you
haven’t yet put it there, which is what you’ll do now. Open
app/views/projects/index.html.erb and change the contents to the following:
<h1>Projects</h1>
<%= link_to "New Project", new_project_path %>
<div class="projects">
<% @projects.each do |project| %>
<h2><%= link_to project.name, project %></h2>
<p><%= project.description %></p>
<% end %>
</div>
We’ve added a heading, and some details on each of the projects. If you run the spec again, you get this error, which isn’t helpful at first glance:
1) Users can view projects with the project details
Failure/Error: visit "/"
ActionView::Template::Error:
undefined method `each' for nil:NilClass
# ./app/views/projects/index.html.erb:6:in ...
This error points at line 6 of your app/views/projects/index.html.erb file,
which reads <% @projects.each do |project| %>.
From this you can determine that the error must have something to do with the
@projects variable. This variable hasn’t yet been defined, and as mentioned in Chapter
3, instance variables in Ruby return nil rather than raise an exception. So because
@projects is nil, and there’s no each method on nil, you get this error
undefined method 'each' for nil:NilClass.
Watch out for this in Ruby—as you can see here, it can
sting you hard.
We need to define this variable in the index action of our controller. Open
ProjectsController at
app/controllers/projects_controller.rb, and change the index method
definition to look like this.
index action of ProjectsController
def index
@projects = Project.all
end
By calling all on the Project model, you retrieve all the records from the
database as Project objects, and they’re available as an enumerable Array-like
object. Now that you’ve put all the pieces in place, you can run the feature
with bundle exec rspec spec/features/viewing_projects_spec.rb, and it should
pass:
1 example, 0 failures
The spec now passes. Is everything else still working, though? You can check
by running bundle exec rspec. Rather than just running the one test, this code runs
all the tests in the spec directory. When you run the code, you should see
this:
3 examples, 0 failures
All the specs are passing, meaning all the functionality you’ve written so far is working as it should. Commit your changes and push them to GitHub, using these commands:
$ git add . $ git commit -m "Add the ability to view a list of all projects" $ git push
The reading part of this CRUD resource is done! You’ve got the index and
show actions for the ProjectsController behaving as they should. Now you
can move on to updating.
Editing projects
With the first two parts of CRUD (creating and reading) done, you’re ready
for the third part: updating. Updating is similar to creating and reading in that it has two
actions for each part (creation has new and create; reading has index and
show). The two actions for updating are edit and update. Let’s begin by
writing a feature and creating the edit action.
The edit action
As with the form used for creating new projects, you want a form that
allows users to edit the information of a project that already exists. You first put an "Edit
Project" link on the show page, that takes users to the edit action
where they can edit the project. Write the code from the following listing into
spec/features/editing_projects_spec.rb.
require "rails_helper"
RSpec.feature "Users can edit existing projects" do
scenario "with valid attributes" do
FactoryBot.create(:project, name: "Visual Studio Code")
visit "/"
click_link "Visual Studio Code"
click_link "Edit Project"
fill_in "Name", with: "VS Code"
click_button "Update Project"
expect(page).to have_content "Project has been updated."
expect(page).to have_content "VS Code"
end
end
If you remember, FactoryBot#create builds you an entire object and lets you
tweak the defaults. In this case, you’re changing the name.
Also, it’s common for tests to take this overall form: arrange, act, assert. (This is also referred to as 'given', 'when', and 'then', to describe the actions that take place in each section.) That’s why the whitespace is there: it clearly splits the test. Your tests won’t always look like this, but it’s good form.
After writing this story, again use the rspec command to run just this one
feature: bundle exec rspec spec/features/editing_projects_spec.rb. The first
couple of lines for this scenario pass because of the work you’ve already
done, but it fails on the line that attempts to find the "Edit Project" link:
1) Users can edit existing projects with valid attributes
Failure/Error: click_link "Edit Project"
Capybara::ElementNotFound:
Unable to find link "Edit Project"
To add this link, open app/views/projects/show.html.erb and add the link
under the heading for the project name:
<h1><%= @project.name %></h1>
<%= link_to "Edit Project", edit_project_path(@project) %>
The edit_project_path method generates a link pointing towards the edit
action of the ProjectsController. This method is provided
to you because of the resources :projects line in config/routes.rb.
If you run bundle exec rspec spec/features/editing_projects_spec.rb again, it now
complains about the missing edit action:
1) Users can edit existing projects with valid attributes
Failure/Error: click_link "Edit Project"
AbstractController::ActionNotFound:
The action 'edit' could not be found for ProjectsController
Define this action in your ProjectsController,
under the show action (but above the private line), as in the following listing.
def edit
@project = Project.find(params[:id])
end
As you can see, this action works in a fashion identical to the show action,
where the ID for the resource is automatically passed as params[:id]. Let’s
work on DRYing[1] this up once you’re done with this controller. When you run the
spec again, you’re told that the edit view is missing:
1) Users can edit existing projects with valid attributes
Failure/Error: click_link "Edit Project"
ActionController::MissingExactTemplate:
ProjectsController#edit is missing a template for request formats: text/html
It looks like you need to create this template. The edit action’s form is
similar to the form in the new action. If only there were a way to extract
out just the form into its own template. Well, in Rails, there is! You can
extract out the form from app/views/projects/new.html.erb into what’s called a
partial. We saw these briefly in Chapter 1.
A partial is a template that contains some code that can be shared between
other templates. To extract the form from the new template into a new partial,
take this code out of app/views/projects/new.html.erb:
<%= form_with(model: @project) do |form| %>
<% if @project.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@project.errors.count, "error") %>
prohibited this project from being saved:</h2>
<ul>
<% @project.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :name %>
<%= form.text_field :name %>
</duv>
<div>
<%= form.label :description %>
<%= form.text_field :description %>
</div>
<%= form.submit %>
<% end %>
This will leave the new template looking pretty bare, with just the heading.
Then create a new file called app/views/projects/_form.html.erb and put into
it the code you just extracted from the new template. While moving it, you
should also change all instances of @project to be project instead.
<%= form_with(model: project) do |form| %>
<% if project.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(project.errors.count, "error") %>
prohibited this project from being saved:</h2>
<ul>
<% project.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div>
<%= form.label :description %>
<%= form.text_area :description %>
</div>
<%= form.submit %>
<% end %>
|
The filenames of partials must always start with an underscore, which is why
we’ve written |
Why have we done this variable renaming? Because to be reusable, partial views
shouldn’t rely on instance variables - they should be totally self-sufficient.
When we render the partial from our main view, we can pass in the data that
the partial needs to render - in this case, we’ll pass in the @project
variable from the new template, which means it will be accessible as a local
variable from within the partial.
To render the partial and pass in the @project instance, modify your new
template in app/views/projects/new.html.erb and add this line where the form
previously was:
<%= render "form", project: @project %>
This will leave your new template very slim indeed:
new template<h1>New Project</h1>
<%= render "form", project: @project %>
|
This is an example of a refactoring - changing the internals of code
without affecting functionality. You can confirm that this refactoring has
not affected the ability to create projects, by running the test for it -
|
Now you need to create the edit
action’s template. Create a new file at app/views/projects/edit.html.erb with this content.
<h1>Edit Project</h1>
<%= render "form", project: @project %>
When you pass a string to the render method, Rails looks up a partial in the
same directory as the current template matching the string and renders that
instead. Using the partial, the next line passes without any further
intervention from you when you run bundle exec rspec
spec/features/editing_projects_spec.rb.
1) Users can edit existing projects with valid attributes
Failure/Error: click_button "Update Project"
AbstractController::ActionNotFound:
The action 'update' could not be found for ProjectsController
The test has filled in the "Name" field successfully; but it fails when the
"Update Project" button is clicked, because it can’t find the update action in
the ProjectsController. To make this work, you’ll need to create that
update action.
The update action
As the following listing shows, you can now define update under the edit action in your controller. Make sure to do this above the private keyword too!
def update
@project = Project.find(params[:id])
@project.update(project_params)
flash[:notice] = "Project has been updated."
redirect_to @project
end
Notice the new method on @project here, update. It
takes a hash of attributes identical to the ones passed to new or create,
updates those specified attributes on the object, and then saves them to the
database if they’re valid. This method, like save, returns true if the
update is valid or false if it isn’t.
Now that you’ve implemented the update action,
let’s see how the test is going by running bundle exec rspec spec/features/editing_projects_spec.rb:
1 example, 0 failures
That was easy! But what happens if somebody fills in the "Name" field with a blank value? The user
should receive an error, just as in the create action, due to the validation in the
Project model. You should write a test to verify this behaviour.
Move the first
four steps from the first scenario in spec/features/editing_projects_spec.rb
into a before block, because when a user is editing a project, the first
four steps will always be the same: a project needs to exist, and then a user
goes to the homepage, finds a project, and clicks "Edit Project". Change
spec/features/editing_projects_spec.rb so it looks like this.
require "rails_helper"
RSpec.feature "Users can edit existing projects" do
before do
FactoryBot.create(:project, name: "Visual Studio Code")
visit "/"
click_link "Visual Studio Code"
click_link "Edit Project"
end
scenario "with valid attributes" do
fill_in "Name", with: "Visual Studio Code Nightly"
click_button "Update Project"
expect(page).to have_content "Project has been updated."
expect(page).to have_content "Visual Studio Code Nightly"
end
end
A before block can help set up state for multiple tests: the block runs
before each test executes. Sometimes, setting up is more than just creating
objects; interacting with an
application is totally legitimate as part of setup.
Defining behaviour for when an update fails
Now you can add a new scenario, shown in the following listing, to test
that the user is shown an error message for when the validations fail during
the update action. Add this new scenario directly under the one currently
in this file.
scenario "when providing invalid attributes" do
fill_in "Name", with: ""
click_button "Update Project"
expect(page).to have_content "Project has not been updated."
end
When you run bundle exec rspec
spec/features/editing_projects_spec.rb, filling in "Name" works, but when the form is
submitted, the test doesn’t see the "Project has not been updated." message:
1) Users can edit existing projects when providing invalid attributes
Failure/Error: expect(page).to have_content "Project has not been
updated."
expected to find text "Project has not been updated." in "Project
has been updated. Visual Studio Code Edit Project"
The test can’t find the message on the page because you haven’t written any
code to test for what to do if the project being updated is now invalid. In
your controller, use the code in the following listing for the update action
so that it shows the error message if the update method returns false.
update action of ProjectsController
def update
@project = Project.find(params[:id])
if @project.update(project_params)
flash[:notice] = "Project has been updated."
redirect_to @project
else
flash.now[:alert] = "Project has not been updated."
render "edit"
end
end
And now you can see that the feature passes when you rerun bundle exec rspec
spec/features/editing_projects_spec.rb:
2 examples, 0 failures
Again, you should ensure that everything else is still working by running
bundle exec rspec. You should see this summary:
5 examples, 0 failures
Looks like a great spot to make a commit and push:
$ git add . $ git commit -m "Projects can now be updated" $ git push
The third part of CRUD, updating, is done now. The fourth and final part is deleting.
Deleting projects
We’ve reached the final stage of CRUD: deletion. This involves implementing
the final action of your controller: the destroy action, which allows you to
delete projects.
Of course, you’ll need a feature to get going: a "Delete Project" link on the
show page that, when clicked, prompts the user for
confirmation that they really want to delete the project.[2]
Put the feature at spec/features/deleting_projects_spec.rb using the code in
the following listing.
require "rails_helper"
RSpec.feature "Users can delete projects" do
scenario "successfully" do
FactoryBot.create(:project, name: "Visual Studio Code")
visit "/"
click_link "Visual Studio Code"
click_button "Delete Project"
expect(page).to have_content "Project has been deleted."
expect(page.current_url).to eq projects_url
expect(page).to have_no_content "Visual Studio Code"
end
end
When you run this test using bundle exec rspec
spec/features/deleting_projects_spec.rb, the first couple of lines pass
because they’re just creating a project using Factory Bot, visiting the
homepage, and then clicking the link to go to the project page. The fourth
line in this scenario fails, however, with this message:
1) Users can delete projects successfully
Failure/Error: click_button "Delete Project"
Capybara::ElementNotFound:
Unable to find button "Delete Project"
To get this to work, you need to add a "Delete Project" button to the show
action’s template, app/views/projects/show.html.erb. Put it on the line
after the "Edit Project" link, using this code:
<%= button_to "Delete Project",
project_path(@project),
method: :delete,
form: { data: { turbo_confirm: 'Are you sure?' } }
%>
|
"Turbo" confirm vs regular confirm
Rails 7 includes a framework called "Turbo" that accelerates page loads and transitions. In order to make this confirmation box work within this new Turbo-powered Rails world, we need to use
However, this will not show the "Are you sure?" confirmation message on newer Rails applications. We include that small example here as just one representation of how Rails has evolved over time. If there are big differences like this further on in the book, we’ll call them out in a similar way simply because there’s no guarantee that every Rails application you’ll work on is using the latest and greatest that Rails can offer. |
Here you pass two new options to the button_to method: :method and :data.
The :method option tells Rails what HTTP method
this link should be using, and here’s where you specify the :delete method.
In the previous chapter, the four HTTP methods were mentioned; the final one
is DELETE. When you developed your first application, chapter 1 explained
why you use the DELETE method, but let’s review the reasons.
If all actions are available by GET requests, then anybody can create a URL that directly
corresponds to the destroy action of your controller. If they send you the
link, and you click it, then it’s bye-bye precious data. By using DELETE, you
protect an important route for your
controller by ensuring that you have to follow the correct link from your
site, to make the proper request to delete this resource.
The :data option containing a
:confirm key brings up a prompt, using JavaScript, that asks users if
they’re sure that’s what they want to do. If you launch a
browser and follow the steps in the feature to get to this "Delete Project"
button, and then you click the button, you see the confirmation prompt. This
prompt is exceptionally helpful for preventing accidental deletions.
Because Capybara doesn’t support JavaScript by default, the prompt is ignored, so you don’t have to tell Capybara to click "OK" in response to the prompt—there is no prompt, because Rails has a built-in fallback for users without JavaScript enabled.
When you run the spec again with bundle exec rspec spec/features/deleting_projects_spec.rb,
it complains of a missing destroy action:
1) Users can delete projects successfully
Failure/Error: click_button "Delete Project"
AbstractController::ActionNotFound:
The action 'destroy' could not be found for ProjectsController
This is the final action you need to implement in your controller; it goes
under the update action. The action is shown in the
following listing.
destroy action from ProjectsController
def destroy
@project = Project.find(params[:id])
@project.destroy
flash[:notice] = "Project has been deleted."
redirect_to projects_path
e
Here you call the destroy method on the @project object you get back from
your find call. No validations are run, so no conditional setup is needed.
Once you call destroy on that object, the relevant database record is gone
for good; but the Ruby object representation of this record still exists until
the end of the request. After the record has been deleted from the database,
you set the flash[:notice] to indicate to the user that their action was
successful, and redirect back to the projects index page by using the
projects_path routing helper in combination with redirect_to.
With this last action in place, your newest feature should pass when you run
bundle exec rspec spec/features/deleting_projects_spec.rb:
1 example, 0 failures
Let’s see if everything else is still passing, with bundle exec rspec:
6 examples, 0 failures
Great! Let’s commit that:
$ git add . $ git commit -m "Projects can now be deleted" $ git push
Done! Now you have full support for CRUD operations in your ProjectsController.
Let’s refine this controller into simpler code before we move on.
What happens when things can’t be found
People sometimes poke around an application looking for things that are no
longer there, or they muck about with the URL. As an example, launch your application’s server
by using rails server, and try to navigate to
http://localhost:3000/projects/not-here. You’ll see the exception shown here:
ActiveRecord::RecordNotFound exceptionThis is Rails' way of displaying exceptions in development mode. Under this error, more information is
displayed, such as the backtrace of the error. Rails will only do this in the development
environment because of the consider_all_requests_local
configuration setting in config/environments/development.rb. This file contains all the custom
settings for your development environment, and the consider_all_requests_local setting is true by
default. This means Rails will show the complete exception information when it runs in the
development environment. When this application is running under the production environment, users would instead see this:
This is not ideal! What we would rather see is a message that the project cannot be found.
Handling the ActiveRecord::RecordNotFound exception
To handle the ActiveRecord::RecordNotFound exception, you can rescue it and, rather
than letting Rails render a 404 page, redirect the user to the index action
with an error message. To test that users are shown an error message rather
than a "Page does not exist" error, you’ll write an RSpec request test
instead of a feature test, because viewing projects that aren’t there is
something a user can do, but not something that should happen over the course
of a normal browsing session. Plus, it’s easier.
The file for this request test,
spec/requests/projects_request_spec.rb, was automatically generated when
you ran the controller generator, because you have the rspec-rails gem in your
Gemfile.[3] Open this request spec file:
require 'rails_helper'
RSpec.describe "Projects", type: :request do
end
In this request spec, you want to test that you’re redirected to the
Projects page if you attempt to access a resource that no longer exists. You also want to
ensure that a flash[:alert] is set.
To do all this, put this it block inside the describe block:
require 'rails_helper'
RSpec.describe "/projects", type: :request do
it "handles a missing project correctly" do
get project_path("not-here")
expect(response).to redirect_to(projects_path)
message = "The project you were looking for could not be found."
expect(flash[:alert]).to eq message
end
end
The first line in this RSpec test tells
RSpec to make a GET request to the route /projects/not-here. This route will then be served by the ProjectsController’s `show action.
In the next line, you tell RSpec that you expect the response to take you
back to the projects_path through a redirect_to call. If it doesn’t, the
test fails, and nothing more in this test is executed: RSpec stops in its
tracks.
The final line tells RSpec that you expect flash[:alert] to contain a useful
message explaining the redirection to the index action.
To run this spec, use the bundle exec rspec
spec/requests/projects_request_spec.rb command. When this runs, you’ll
see this error:
1) Projects handles a missing project correctly
Failure/Error: @project = Project.find(params[:id])
ActiveRecord::RecordNotFound:
Couldn't find Project with 'id'=not-here
This is the same failure you saw when you tried running the application using the
development environment with rails server. Now that you have a failing test, you can fix it.
Open the app/controllers/projects_controller.rb file, and put the code
from the following listing under the private line in the controller.
set_project method in ProjectsController
def set_project
@project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The project you were looking for could not be found."
redirect_to projects_path
end
Because it’s under the private line, the controller doesn’t
respond to this method as an action. To call this method
before every action that looks up a project, use the before_action method.
Place these lines directly under the class ProjectsController definition:
before_action :set_project, only: %i(show edit update destroy)
What does all this mean? Methods referenced
by before_action are run before all the actions in your
controller, unless you specify either the :except or :only option. Here you
have the :only option, defining actions for which you want before_action to
run. The :except option is the opposite of the :only option, specifying
the actions for which you don’t want before_action to run. before_action
calls the set_project method before the specified actions, setting up the
@project variable for you. This means you can remove the following line from
your show, edit, update, and destroy actions:
@project = Project.find(params[:id])
By doing this, you make the show and edit actions empty. If you remove these
actions from your controller and run bundle exec rspec again, all the scenarios
will still pass.
7 examples, 0 failures
Controller actions don’t need to exist in the controllers if there are
templates corresponding to those actions, which you have for these actions.
For readability’s sake though, it’s best to leave these in the controller so anyone
who reads the code knows that the controller can respond to these actions, so put
the empty show and edit actions back in. Future you will thank you.
Back to the spec now. If you run bundle exec rspec
spec/controllers/projects_controller_spec.rb once more, the test now passes:
1 example, 0 failures
Let’s check to see if everything else is still working by running
bundle exec rspec. You should see this:
7 examples, 0 failures
Red-green-refactor! With that out of the way, let’s commit and push:
$ git add . $ git commit -m "Redirect the users back to the projects page if they try going to a project that does not exist" $ git push
This completes the basic CRUD implementation for your projects resource. Now you can create, read, update, and delete projects to your heart’s content.
Summary
This chapter covered developing the first part of your application using
test-first practices with RSpec and Capybara, building it one step at a time. Now
you have an application that is truly maintainable. If you want to know if
these specs are working later in the project, you can run bundle exec rspec; if
something is broken that you’ve written a test for, you’ll know about it.
Doesn’t that beat manual testing? Just think of all the time you’ll save in
the long run.
You learned firsthand how rapidly you can develop the CRUD interface for a resource in Rails. There are even faster ways to do it (such as by using scaffolding, discussed in Chapter 1); but to absorb how this process works, it’s best to go through it yourself, step by step, as you did in these last two chapters.
So far, you’ve been developing your application using test-first techniques, and as your application grows, it will become more evident how useful these techniques are. The main thing they’ll provide is assurance that what you’ve coded so far still works exactly as it did when you first wrote it. Without these tests, you may accidentally break functionality and not know about it until a user — or worse, a client — reports it. It’s best that you spend some time implementing tests for this functionality now so you don’t spend even more time later apologizing for whatever’s broken and fixing it.
With the basic projects functionality done, you’re ready for the next step. Because you’re building a ticket-tracking application, it makes sense to implement functionality that lets you track tickets, right? That’s precisely what you’ll do in the next chapter. We’ll also cover nested routing and association methods for models. Let’s go!