Chapter 4. Oh CRUD!

This chapter covers

  • Expanding on the functionality of your app
  • Building a RESTful resource
  • Creating, updating, and deleting a resource

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 just 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 be doing this using behavior-driven development the whole way through.

4.1. 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 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.

4.1.1. Writing a feature

Create a new file in the features directory called viewing_projects.feature, shown in the following listing.

Listing 4.1. features/viewing_projects.feature
Feature: Viewing projects
  In order to assign tickets to a project
  As a user
  I want to be able to see a list of available projects

  Scenario: Listing all projects
    Given there is a project called "TextMate 2"
    And I am on the homepage
    When I follow "TextMate 2"
    Then I should be on the project page for "TextMate 2"

If you run rake cucumber:ok here, all the features will run. Instead, you want to run just the feature you’re working on because you don’t want the other feature to be altered by what you do here. When you’re done, you’ll run rake cucumber:ok to ensure that everything is still working.

To run just this one feature, use the bin/cucumber executable, which was added to your project when you ran bundle install --binstubs. If you didn’t use the --binstubs option, you would have to use bin/cucumber instead, and typing all of that gets a bit boring after a while.

Now, to run this single feature, you run the following command:

bin/cucumber features/viewing_projects.feature

You should always use bin/cucumber rather than straight cucumber to run the bundled version of your gems because you could be using different versions of the same gem across separate projects. By running the bin/cucumber command, you ensure that you’re loading the version of the gem specified by your bundle rather than the system version of that gem.

The first line of the only scenario in the feature fails, all because you haven’t defined the “there is a project” step yet, as Cucumber informs you in the output:

3 scenarios (1 undefined, 2 passed)
15 steps (3 skipped, 1 undefined, 11 passed)

You can implement step definitions for
undefined steps with these snippets:

Given /^there is a project called "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

As you can see in the step output, you’ve got one undefined step. Underneath that, Cucumber very handily gives you a step definition you can use to define this step. In this step, you need to create a new Project object. Rather than doing it manually by calling Project.create everywhere you need it, you can set up a little thing called factories.

Factories allow you to create new example objects for all of your models in a simple and elegant syntax. This functionality doesn’t come with Rails, unfortunately, so you must rely on a gem for it: factory_girl.

4.1.2. The Factory Girl

The factory_girl, created by thoughtbot,[1] 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 have easily craftable objects you can use to run your tests on.

1 Thoughtbot’s website: http://thoughtbot.com.

Before you can use this gem, you need to add it to the :test group in your Gemfile. Now the whole group looks like this:

group :test do
  gem 'cucumber-rails'
  gem 'capybara'
  gem 'database_cleaner'
  gem 'factory_girl'
end

To install, run bundle. You’ll now use Factory Girl in your new step definition.

Create a new file at features/step_definitions/project_steps.rb, and add this small chunk of code:

Given /^there is a project called "([^"]*)"$/ do |name|
  Factory(:project, :name => name)
end

The Factory method[2] looks for the :project factory and generates a new object based on the details it contains. You don’t have a factory defined yet, but you will shortly.

2 Yes, methods can begin with a capital letter.

When you define the factory, you give it a default name. The :name => name part of this method call changes the default name to the one passed in from your feature. You use factories here because you needn’t be concerned about any other attribute on the Project object. If you weren’t using factories, you’d have to use this method to create the object instead:

Project.create(:name => name)

Although this code is about the same length as its Factory 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 to contain this new field. When you use factories, 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—now how about some practice? Let’s see what happens when you run bin/cucumber features/viewing_projects.feature:

Not registered: project (ArgumentError)

Aha! You’re now told there’s no such factory! Then you’d better get around to creating one. You use these factories not only in your Cucumber features but also later in your RSpec tests. Placing the factories inside the features directory isn’t fair to the RSpec tests, and placing them inside the spec isn’t fair to the Cucumber tests. So where do they go? Create a new folder at the root of your application for them, and name it factories. Inside this directory, create your Project factory by creating a new file called project_factory.rb and filling it with the following content:

Factory.define :project do |project|
  project.name 'Ticketee'
end

This small snippet defines your project factory, which creates a new instance of the Project class, defaulting the name attribute to Ticketee. These files aren’t going to load themselves, so you must create a new file at features/support/factories.rb and put this content in it:

Dir[Rails.root + "factories/*.rb"].each do |file|
  require file
end

All .rb files in features/support are loaded automatically before Cucumber starts, so all the files in the factories are required, and they define all the factories in the files.

With this factory defined, your feature should have nothing to whine about. Let’s look at the following listing to see what happens now when you run bin/cucumber features/viewing_projects.feature

Listing 4.2. features/viewing_projects.feature failure
Given there is a project called "TextMate 2"
And I am on the homepage
When I follow "TextMate 2"
  no link with title, id or text 'TextMate 2' found ...

A link appears to be missing. You’ll add that right now.

4.1.3. Adding a link to a project

Capybara is expecting a link on the page with the words “TextMate 2” 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’re going to do now. Open app/views/projects/index.html.erb, and add the contents of the following listing underneath the first link.

Listing 4.3. app/views/projects/index.html.erb
<h2>Projects</h2>
<ul>
  <% @projects.each do |project| %>
    <li><%= link_to project.name, project %></li>
  <% end %>
</ul>

If you run the Cucumber feature again, you get this error, which isn’t helpful at first glance:

Showing /[path to ticketee]/app/views/projects/index.html.erb
 where line #5
 raised:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.

This error points at line 5 of your app/views/projects/index.html.erb file. From this you can determine that the error has something to do with the @projects variable. This variable isn’t yet been defined, and because there’s no each method on nil, you get this error. As mentioned in chapter 3, instance variables in Ruby return nil rather than raise an exception if they’re undefined. Watch out for this in Ruby—as seen here, it can sting you hard.

To define this variable, open ProjectsController at app/controllers/projects_controller.rb and change the index method definition to look like the following listing.

Listing 4.4. app/controllers/projects_controller.rb
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 Array-like object. Now that you’ve put all the pieces in place, you can run the feature with bin/cucumber features/viewing_projects.feature, and all the steps should pass:

1 scenario (1 passed)
4 steps (4 passed)

The feature now passes. Is everything else still working, though? You can check by running rake cucumber:ok. At the bottom, you should see this:

3 scenarios (3 passed)
16 steps (16 passed)

All of your scenarios and their steps are passing, meaning all of the functionality you’ve written so far is working as it should. Commit and push this using

git add .
git commit -m "Added 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.

4.2. 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.

4.2.1. 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 feature from the following listing into features/editing_projects.feature.

Listing 4.5. features/editing_projects.feature
Feature: Editing Projects
  In order to update project information
  As a user
  I want to be able to do that through an interface

  Scenario: Updating a project
    Given there is a project called "TextMate 2"
    And I am on the homepage
    When I follow "TextMate 2"
    And I follow "Edit Project"
    And I fill in "Name" with "TextMate 2 beta"
    And I press "Update Project"
    Then I should see "Project has been updated."
    Then I should be on the project page for "TextMate 2 beta"

In this story, you again use the bin/cucumber command to run just this one feature: bin/cucumber features/editing_projects.feature.

The first three steps pass for this feature because of the work you’ve already done, but it fails on the fourth step when it tries to follow the as-yet nonexistent Edit Project link on the show page:

no link with title, id or text 'Edit Project' found (Capybara::ElementNotFound)

To add this link, open app/views/projects/show.html.erb and add the link from the following listing underneath the code currently in that file.

Listing 4.6. app/views/projects/show.html.erb
<%= link_to "Edit Project", edit_project_path(@project) %>

The edit_project_path method generates the link to the Project object, pointing at the ProjectsController’s edit action. This method is provided to you because of the resources :projects line in config/routes.rb.

If you run bin/cucumber features/editing_projects.feature again, it now complains about the missing edit action:

The action 'edit' could not be found for ProjectsController

You should now define this action in your ProjectsController, underneath the show action, as in the following listing.

Listing 4.7. app/controllers/projects_controller.rb
def edit
  @project = Project.find(params[:id])
end

As you can see, this action works in an identical fashion to the show action, where the ID for the resource is automatically passed as params[:id]. Let’s work on DRYing[3] this up once you’re done with this controller. When you run feature again, you’re told that the edit view is missing:

3 As a reminder: DRY = Don’t Repeat Yourself!

Given I am on the homepage
Missing template projects/edit, application/edit
  with {:handlers=>[:erb, :builder],
        :formats=>[:html],
        :locale=>[:en, :en]}.

  Searched in:
    * ".../ticketee/app/views"

It looks like you need to create this template. For this edit action’s template, you can re-use the form partial (app/views/projects/_form.html.erb) you created in chapter 3 by putting the code from the following listing into app/views/projects/edit.html.erb.

Listing 4.8. app/views/projects/edit.html.erb
<h2>Edit project</h2>
 <%= render "form" %>

When you pass a string to the render method, Rails looks up a partial in the current directory matching the string and renders that instead. Using the partial, the next step passes without any further intervention from you when you run bin/cucumber features/editing_projects.feature, but the output now says it can’t find the update action:

And I fill in "Name" with "TextMate 2 beta"
    And I press "Update Project"
      The action 'update' could not be found for ProjectsController

Great! It looks like the edit action is working fine, so your next step is to define the update action.

4.2.2. The update action

As the following listing shows, you can now define this update action underneath the edit action in your controller.

Listing 4.9. app/controllers/projects_controller.rb
def update
  @project = Project.find(params[:id])
  @project.update_attributes(params[:project])
  flash[:notice] = "Project has been updated."
  redirect_to @project
end

Notice the new method here, update_attributes. 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 are valid. This method, like save, returns true if the update is valid or false if it is not.

Now that you’ve implemented the update action, let’s see how the feature is going by running bin/cucumber features/editing_projects.feature:

1 scenario (1 passed)
8 steps (8 passed)

What happens if somebody fills in the name field with a blank value? The user receives an error, just as in the create action. You should move the first four steps from the first scenario in features/editing_projects.feature into a Background so the Feature now looks like the following listing.

Listing 4.10. features/editing_projects.feature
Feature: Editing Projects
  In order to update project information
  As a user
  I want to be able to do that through an interface

  Background:
    Given there is a project called "TextMate 2"
    And I am on the homepage
    When I follow "TextMate 2"
    And I follow "Edit Project"

  Scenario: Updating a project
    And I fill in "Name" with "TextMate 2 beta"
    And I press "Update Project"
    Then I should see "Project has been updated."
    Then I should be on the project page for "TextMate 2 beta"

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 on update directly under the other scenario in this file.

Listing 4.11. features/editing_projects.feature
Scenario: Updating a project with invalid attributes is bad
   And I fill in "Name" with ""
   And I press "Update Project"
   Then I should see "Project has not been updated."

When you run bin/cucumber features/editing_projects.feature, the first step passes but the second doesn’t:

expected there to be content "Project has not been updated." in "[text]"

Again, this error means that it was unable to find the text “Project has not been updated.” on the page. This is 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.

Listing 4.12. app/controllers/projects_controller.rb
def update
  @project = Project.find(params[:id])
  if @project.update_attributes(params[:project])
    flash[:notice] = "Project has been updated."
    redirect_to @project
  else
    flash[:alert] = "Project has not been updated."
    render :action => "edit"
  end
end

And now you can see that the feature passes when you rerun it:

2 scenarios (2 passed)
15 steps (15 passed)

Again, you should ensure everything else is still working by running rake cucumber :ok; you should see this summary:

5 scenarios (5 passed)
31 steps (31 passed)

Let’s make a commit and push now:

git add .
git commit -m "Added updating projects functionality"
git push

The third part of CRUD, updating, is done. The fourth and final part is deleting.

4.3. 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.

4.3.1. Writing a feature

You’re going to need a feature to get going: a Delete Project link on the show page that, when clicked, prompts the user for confirmation. You put the feature at features/deleting_projects.feature using the following listing.

Listing 4.13. features/deleting_projects.feature
Feature: Deleting projects
  In order to remove needless projects
  As a project manager
  I want to make them disappear

  Scenario: Deleting a project
    Given there is a project called "TextMate 2"
    And I am on the homepage
    When I follow "TextMate 2"
    And I follow "Delete Project"
    Then I should see "Project has been deleted."
    Then I should not see "TextMate 2"

When you run this feature using bin/cucumber features/deleting_projects.feature, the first three steps pass, and the fourth fails:

And I follow "Delete Project"
  no link with title, id or text 'Delete Project' found ...

4.3.2. Adding a destroy action

Of course, you need to create a Delete Project link for the show action’s template, app/views/projects/show.html.erb. You put this on the line after the Edit Project link using the following listing.

Listing 4.14. app/views/projects/show.html.erb
<%= link_to "Delete Project", @project, :method => :delete,
  :confirm => "Are you sure you want to delete this project?" %>

Here you pass two new options to the link_to method, :method and :confirm.

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 why. If all actions are available by GET requests, then anybody can send you a link to, say, the destroy action for one of your controllers, and if you click that, 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 link from the site to make the proper request to delete this resource.

The :confirm option brings up a prompt, using JavaScript, that asks users if they’re sure of what they clicked. Because Capybara doesn’t support JavaScript by default, this prompt is ignored, so you don’t have to tell Capybara to click OK on the prompt—there is no prompt because Rails has a built-in fallback for users without JavaScript enabled. If you launch a browser and follow the steps in the feature to get to this Delete Project link, and then click the link, you see the confirmation prompt. This prompt is exceptionally helpful for preventing accidental deletions.

When you run the feature again, it complains of a missing destroy action:

And I follow "Delete Project"
  The action 'destroy' could not be found for ProjectsController

The final action you need to implement is in your controller, and you’ll put it underneath the update action. This action is shown in the following listing.

Listing 4.15. app/controllers/projects_controller.rb
def destroy
  @project = Project.find(params[:id])
  @project.destroy
  flash[:notice] = "Project has been deleted."
  redirect_to projects_path
end

Here you call the destroy method on the @project object you get back from your find. No validations are run here, so no conditional setup is needed. Once you call destroy on that object, the relevant database record is gone for good but the object still exists. When it’s gone, you set the flash[:notice] and redirect back to the project’s index page by using the projects_path routing helper.

With this last action in place, your newest feature should pass when you run bin/cucumber features/deleting_projects.feature:

1 scenario (1 passed)
6 steps (6 passed)

Let’s see if everything else is running with rake cucumber:ok:

6 scenarios (6 passed)
37 steps (37 passed)

Great! Let’s commit that:

git add .
git commit -m "Implemented delete functionality for projects"
git push

Done! Now you have the full support for CRUD operations in your ProjectsController. Let’s refine this controller into simpler code before we move on.

4.3.3. Looking for what isn’t there

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 in figure 4.1.

Figure 4.1. ActiveRecord::RecordNotFound exception

The ActiveRecord::RecordNotFound exception is Rails’ way of displaying exceptions in development mode. Underneath this error, more information is displayed, such as the backtrace of the error.

If you were running in the production environment, you would see a different error. Stop the server that is currently running, and run these commands to start in production mode:

rake db:migrate RAILS_ENV=production
rails server -e production

Here you must specify the RAILS_ENV environment variable to tell Rails you want to migrate your production database. By default in Rails, the development and production databases are kept separate so you don’t make the mistake of working with production data and deleting something you shouldn’t. This problem is also solved by placing the production version of the code on a different server from the one you’re developing on. You only have to run the migration command when migrations need to be run, not every time you need to start your server.

You also pass the -e production option to the rails server command, which tells it to use the production environment. Next, navigate to http://[our-local-ip]:3000/project/not-here, where [our-local-ip] is whatever the IP of your computer is on the local network, like 10.0.0.2 or 192.168.0.3. When you do this, you get the standard Rails 404 page (see figure 4.2), which, to your users, is unhelpful.

Figure 4.2. “Page does not exist” error

It’s not the page that’s gone missing, but rather the resource you’re looking for isn’t found. If users see this error, they’ll probably have to click the Back button and then refresh the page. You could give users a much better experience by dealing with the error message yourself and redirecting them back to the home page.

To do so, you capture the exception and, rather than letting Rails render a 404 page, you 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 controller test rather than a Cucumber feature, because viewing projects that aren’t there is something a users can do, but not something they should do. Plus, it’s easier.

The file for this controller test, spec/controllers/projects_controller_spec.rb, was automatically generated when you ran the controller generator because you have the rspec-rails gem in your Gemfile.[4] Open this controller spec file and take a look. It should look like the following listing.

4 The rspec-rails gem automatically generates the file using a Railtie, the code of which can be found at https://github.com/rspec/rspec-rails/blob/master/lib/rspec-rails.rb.

Listing 4.16. spec/controllers/projects_controller_spec.rb
require 'spec_helper'

describe ProjectsController do

end

The spec_helper.rb file it references is located at spec/spec_helper.rb and it, like the previous examples of spec/spec_helper.rb (in chapter 2), is responsible for setting up the environment for your tests. This time, however, it already has code, which includes the Rails environment and the Rails-associated RSpec helpers as well as any file inside the spec/support directory or its subdirectories.

In this controller spec, you want to test that you get 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, you put the following code inside the describe block:

it "displays an error for a missing project" do
  get :show, :id => "not-here"
  response.should redirect_to(projects_path)
  message = "The project you were looking for could not be found."
  flash[:alert].should eql(message)
end

The first line inside this RSpec test—more commonly called an example—tells RSpec to make a GET request to the show action for the ProjectsController. How does it know which controller should receive the GET request? RSpec infers it from the class used for the describe block.

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 the flash[:alert] to contain a useful messaging explaining the redirection to the index action.[5]

5 The lines for the flash[:alert] are separated into two lines to accommodate the page width of this book.

To run this spec, use the bin/rspec spec/controllers/projects_controller_spec.rb command.

It may seem like nothing is happening at first, because RSpec must load the Rails environment and your application, and loading takes time. The same delay occurs when you start running a Rails server.

You can put it on one line if you like. We won’t yell at you.

When the test runs, you get a failure:

F

 1) ProjectsController displays an error
   message when asked for a missing project
    Failure/Error: get :show, :id => "not-here"
    Couldn't find Project with ID=not-here

This is the same failure you saw when you tried running the application using 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 underneath the last action in the controller but before the end of the class.

Listing 4.17. app/controllers/projects_controller.rb
private
  def find_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

This method has the private method before it, so the controller doesn’t respond to this method as an action. To call this method before every action, use the before_filter method. Place these lines directly under the class ProjectsController definition:

before_filter :find_project, :only => [:show,
                                       :edit,
                                       :update,
                                       :destroy]

What does all this mean? Let’s start with the before_filter. before_filters 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 you want the before_filter to run for. The :except option is the opposite of the :only option, specifying the actions you do not want the before_filter to run for. The before_filter calls the find_project method before the specified actions, setting up the @project variable for you. This means you can remove the following line from all four of your actions (show, edit, update, and destroy):

@project = Project.find(params[:id])

By doing this, you make the show and edit actions empty. If you remove these actions and run rake cucumber:ok again, all the scenarios still pass. 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, it’s best to leave these in the controller so anyone who reads the code knows that the controller responds to these actions.

Back to the spec now: if you run bin/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 rake cucumber:ok and then rake spec. You should see these outputs:

6 scenarios (6 passed)
37 steps (37 passed)
# and
.
3 examples, 0 failures, 2 pending

The RSpec output shows two pending examples. These come from the spec files for ProjectsHelper (spec/helpers/projects_helper_spec.rb) and Project model (spec/models/project_spec.rb) respectively. You can delete these files or remove the pending lines from them to make your RSpec output green instead of yellow when it passes. Other than that, these two specs have no effect on your test output.

It’s great to see that everything is still going! Let’s commit and push that!

git add .
git commit -m "Users should be redirected back to the projects page
                if they try going to a project that doesn't exist."
git push

This completes the basic CRUD implementation for your project’s resource. Now you can create, read, update, and delete projects to your heart’s content, and these features are all well covered with tests, which leads to greater maintainability.

4.4. Summary

This chapter covered developing the first part of your application using BDD practices and Cucumber and then making each step pass. Now you have an application that is truly maintainable. If you want to know if these features or specs are working later in the project, you can run rake cucumber:ok or rake spec and if something is broken, 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 whole process works, it’s best to go through it yourself, step by step, as you did in this chapter.

So far you’ve been developing your application using BDD 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 is still working exactly as it was 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 that you don’t spend even more time later apologizing for whatever’s broken and fixing it.

With the basic project 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 do in the next chapter. We also cover nested routing and association methods for models. Let’s go!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.126.199