rails 4.1: creating a CRUD resource feature

Create the test first, inside your specs/feature/ folder:
spec/features/your_feature_name_spec.rb

Sample content for that file:

require "rails_helper"

RSpec.feature "Users can create a project" do
  scenario "with valid attributes" do
    visit "/"

    click_link "New Project"

    fill_in "Name", with: "Some Project name"
    fill_in "Description", with: "Some project description"
    click_button "Create"

    expect(page).to have_content "Project has been created."
  end
end

The test will fail when you run:

bundle exec rspec

If you want to run only one:

bundle exec rspec spec/features/creating_projects_spec.rb

You need to create the db in order for them to pass

bundle exec rake db:create

To make the test pass, you need to build a route from the “/” request

Inside config/routes.rb, add the following line:

root "projects#index"

Create your controller (use plurals! singular names are for models)

rails g controller projects

And, your models (by default, the columns will be string)

rails g model project name description
bundle exec rake db:migrate

Now, inside your src/app/controllers/projects_controller.rb, create your controller methods:

class ProjectsController < ApplicationController
  def index
  end

  def new
    @project = Project.new
  end

  def create
    @project = Project.new(project_params)
  
    if @project.save
      flash[:notice] = "Project has been created."
      redirect_to @project
    else
      flash.now[:alert] = "Project has not been created."
      render "new"
    end
  end
  def show
    @project = Project.find(params[:id])
  end

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

  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

  def destroy
     @project = Project.find(params[:id])
     @project.destroy
     flash[:notice] = "Project has been deleted."
     redirect_to projects_path
   end

  private
  def project_params
    params.require(:project).permit(:name)
  end
end

Also, build your view templates:

/src/app/views/projects/index.html.erb

/src/app/views/projects/new.html.erb

/src/app/views/projects/show.html.erb

Example of the way you display model information inside any of the ERBs above:

<h1><%= @project.name %></h1>
<p><%= @project.description %></p>

Create the form, usually inside a partial, so you can reuse the code for Create and Edit views (inside app/views/projects/_form.html.erb):

<%= form_for(project) do |f| %>
  <% 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 %>

  <p>
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </p>

  <p>
    <%= f.label :description %><br>
    <%= f.text_field :description %>
  </p>

  <%= f.submit %>
<% end %>

Inside your view, since you were looking for a link in this particular test, you can put the following content to make the test pass:

<%= link_to “New Project”, new_project_path %>

But now we need to define a route for that link (inside your config/routes.rb file:

resources :projects

By adding that resource, you are basically accessing seven potential actions on your page:

GET/projectsindex
POST/projectscreate
GET/projects/newnew
GET/projects/:idshow
PATCH/PUT/projects/:idupdate
DELETE/projects/:iddestroy
GET/projects/:id/editedit

If you want Flash messages to display in the page, you need to include this on your views/layout/application.hml.erb, or in the page where you need them:

<% flash.each do |key, message| %>
  <div><%= message %></div>
<% end %>

Basic validation

Add the following to your model:

class Project < ActiveRecord::Base
  validates :name, presence: true
end

Index page

The rspec (spec/features/viewing_projects_spec.rb) will look like this:

require "rails_helper"

RSpec.feature "Users can view projects" do
  scenario "with the project details" do
    project = FactoryGirl.create(:project, name: "Sublime Text 3")

    visit "/"
    click_link "Sublime Text 3"
    expect(page.current_url).to eq project_url(project)
  end
end

Before you run you new test, make sure you add the project factory, inside spec/factories/project_factory.rb:

FactoryGirl.define do
  factory :project do
    name "Example project"
  end
end

A typical index.html.erb view page will look like this:

<h1>Projects</h1>

<%= link_to “New Project”, new_project_path %>

<div id=“projects”>

  <% @projects.each do |project| %>

    <h2><%= link_to project.name, project %></h2>

    <p><%= project.description %></p>

  <% end %>

</div>

Updating section

Add the following test in spec/features/editing_projects_spec.rb

require "rails_helper"

RSpec.feature "Users can edit existing projects" do
  before do
    FactoryGirl.create(:project, name: "Sublime Text 3")

    visit "/"
    click_link "Sublime Text 3"
    click_link "Edit Project"
  end

  scenario "with valid attributes" do
    fill_in "Name", with: "Sublime Text 4 beta"
    click_button "Update Project"

    expect(page).to have_content "Project has been updated."
    expect(page).to have_content "Sublime Text 4 beta"
  end
  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
end

Then, add a link to the edit page on your show.html.erb page:
<%= link_to “Edit Project”, edit_project_path(@project) %>

And the actual template (app/views/projects/edit.html.erb):

<h1>Edit Project</h1>
<%= render "form", project: @project %>

Delete section

Create your test at: spec/features/deleting_projects_spec.rb

require "rails_helper"

RSpec.feature "Users can delete projects" do
  scenario "successfully" do
    FactoryGirl.create(:project, name: "Sublime Text 3")

    visit "/"
    click_link "Sublime Text 3"
    click_link "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 "Sublime Text 3"
  end
end

In your show.html.erb template, you need to add the delete action:

<%= link_to "Delete Project",
    project_path(@project),
    method: :delete,
    data: { confirm: "Are you sure you want to delete this project?" }
%>

Leave a Reply

Your email address will not be published. Required fields are marked *