Creating a Rails 4 app from scratch following TDD, using Rspec

I am going ahead to create a simple Product Catalog type rails project. I will follow test driven development methodology to build up this application. By default Rails use test unit as testing framework, but like most of rails developer I prefer Rspec.

I am going to use mongoDB with this project (I love mongo, that’s why !). To use mongoDB, most of the configuration comes down by making sure that I am not loading ActiveRecord. One way to avoid loading ActiveRecord at time of creating rails project using following switch

—skip-active-record

The Rails command that generate application skeleton now has an option -O , which commands rails to skip active record.

So, the final app skeleton will look like so:

rails new product-catalog -T -O

Now open the Gemfile and specify required gem for this project. I am going to use rspec-rails for this project to write test. To automate the testing I am going to use Guard. Guard handle events on file system modifications. I am also going to use Capybara gem, which simulates the user, open up a page and test automatically. mongoid and bson_ext as I am going to use mongoDB. To get the latest mongoid gem, i am declaring GitHub as an another Rubygems sources (they have stopped building gems now), and bundler will look for mongoid gems in the github the sources.

So, the final modified part of Gemfile will look like so:

source 'http://gems.github.com
gem 'mongoid', github: 'mongoid/mongoid'
gem 'bson_ext'

group :development, :test do
    gem 'rspec-rails'
    gem 'guard-rspec'
    gem 'capybara'
end

Now I need to run bundle install or only

bundle

Before I start building the application, I need rspec ready. To do so, install rspec

rails g rspec:install

Create an integration test for test driven development:

rails g integration-test product-catalog

Next, i am going ahead to initialize guard, which will automate my testing.

guard init rspec

Lets test it now

guard

Issues !

When I started using Rspec with mongoid in rails 4, I started having problems. The first was the following error message:

undefined method `fixture_path=‘ for # (NoMethodError)

just needed to comment out the line in spec_helper.rb:

config.fixture_path = "#{::Rails.root}/spec/fixtures"

Another error:

__ undefined method `use_transactional_fixtures=’ for # (NoMethodError)__

just needed to comment out the line spec_helper.rb:

config.use_transactional_fixtures = true

Now run

guard

It will automatically run test and it is failing the tests ….. WOW (we have’t write any thing yet, those tests are by default generated when we generate integration tests for our app)

Let’s build the app #

Listing Product #


I am going to write test as a sort of guideline for what to be build. In spec/requests/product_catelog_spec.rb write:

describe "GET /products" do
    it "display some products list" do
                visit products_path
        page.should have_content "Nexus 5"
    end
 end

Issues !

This time Capybara creates an issue:

undefined method 'visit'

As per the Capybara Documentation

Capybara is no longer supported in
request specs as of Capybara 2.0.0. The recommended way to use Capybara is with feature specs.

To solve this issue we could move tests to spec/features folder or include Capybara::DSL for request specs.

#spec_helper.rb
RSpec.configure do |config|
      ...
      config.include Capybara::DSL
      ...
end  

now guard will run and I got

Failures:

  1) ProductsCatalog GET /products display some products list
     Failure/Error: visit products_path
     NameError:
       undefined local variable or method `products_path' 

Don’t worry its fine, this path will only be created if we create necessary routes. If I do rake routes it will be empty, we have’t created any restful routes yet. We know for a Product in a Product Catalog we are going to show ‘em, add 'em edit 'em, delete 'them and update 'em. we can do this Restful Routes by using resources in config/routes.rb.

resources :products

By adding this single line in to routes.rb file if will generate 9 restful routes for our application.

Prefix Verb URI Pattern ControllerAction
products GET /products(.:format) products#index
POST /products(.:format) products#create
new_product GET /products/new(.:format) products#new
edit_product GET /products/:id/edit(.:format) products#edit
product GET /products/:id(.:format) products#show
PATCH /products/:id(.:format) products#update
PUT /products/:id(.:format) products#update
DELETE /products/:id(.:format) products#destroy

Let’s go back and see what guard is saying:

Failures:

  1) ExploreRacks GET /products display some products list
     Failure/Error: visit products_path
     ActionController::RoutingError:
       uninitialized constant ProductsController

Because I have’t created any controller yet. As MVC guide line controller name will be plural. I am going to name my controller as Products and create index action. lets do that:

rails g controller products index

Let’s check guard:

Failures:

  1) ExploreRacks GET /products display some products list
     Failure/Error: page.should have_content "Nexus 5"
       expected #has_content?("Nexus 5") to return true, got false

Guard is expecting to see “Nexus 5” product on the product list page. To achieve the same I need to set up a data base. I will do that by saying:

rails g model product name:string price:integer

As I am using mongoid, generator will invoke mongoid and generate a model that looks like:

class Product
  include Mongoid::Document
  field :name, type: String
  field :price, type: Integer
end

Now we need to generate the Mongoid configuration file. We’ll create the mongoid file by running the following command:

rails g mongoid:config

It will configure available database sessions, and defines the name of the default database that Mongoid can connect to.

To automate our testing, let’s create a test Product in product_spec file, final modified spec looks like so:

Guard is expecting to see product in index.html.erb but it did’t find anything there. So let’s open Products controller and fetch all product from the data base and store 'em into an instance variable, to access it from view.

class ProductsController < ApplicationController
  def index
    @products = Product.all
  end
end

Now we have products object and we are going to display each of them in view:

<ul>
<% @products.each do |product| %>
    <li>
        <%= product.name %>
        <%= product.price %>
    </li>   
<% end %>
</ul>

And finally guard stop complaining

1 example, 0 failures

Create Product #


Next we are going to add a new product into our product catalog
Just like wire frame sketch I designed the activity like so:

it "add a product into product catalog" do
        visit products_path
        click_button 'New Product'
        current_path.should == new_product_path
        fill_in :Name, :with =>  'Nexus 5'
        fill_in :Price, :with => '30000'
        click_button 'Save Product'
        current_path.should == products_path
   end

To make happy guard first I add a link 'New Product’ into index page.

<%= link_to 'New Product', new_product_path%>

The link_to method is Rail’s built-in view helper, it creates a hyperlink and with a path to new product path

Now guard saying that:

AbstractController::ActionNotFound:
       The action 'new' could not be found for ProductsController

We have’t created suitable action in controller, lets do that, define a new method like this:

def new
end

Now guard saying that

ActionView::MissingTemplate:
       Missing template products/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}.

It indicates that template is missing. In this case Rails will first look for this template, if not found, then it will attempt to load a template called application/new. It looks for this template here because the ProductsController inherits from ApplicationController.

Now I am going to create a new file at app/views/posts/new.html.erb and writing the form for creating new product in it. To create a form for Products in this page we will going use rails form builder, provided by a helper method called form_for

<%= form_for :product, url: products_path do |f|%>
    <%= f.label :Name %>
    <%= f.text_field :name  %>

    <%= f.label :Price %>
    <%= f.text_field :price %>

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

the products_path helper with the :url option point the form to the create action of the current controller, the ProductsController, and will send a POST request to that route

Now guard reporting that:

Failure/Error: click_button 'Save Product'
     AbstractController::ActionNotFound:
       The action 'create' could not be found for ProductsController

Lets deal with that:

The params method returns an ActiveSupport::HashWithIndifferentAccess object, which allows you to access the keys of the hash using either strings or symbols. this class is intended for use cases where strings or symbols are the expected keys and it is convenient to understand both as the same.

strong_parameter

It provides an interface for protecting attributes from end-user assignment. This makes Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted.

ActionController::StrongParameters module has an public method params, which returns a new ActionController::Parameters object that has been instantiated with the request.parameters and the class ActionController::Parameters is a subclass of class ActiveSupport::HashWithIndifferentAccess which is a subclass of Hash ….:P

Action Controller Parameters

Allows to choose which attributes should be white-listed for mass updating and thus prevent accidentally exposing that which shouldn’t be exposed. Provides two methods for this purpose: require and permit. The former is used to mark parameters as required. The latter is used to set the parameter as permitted and limit which attributes should be allowed for mass updating.

require

Ensures that a parameter is present. If it’s present, returns the parameter at the given key, otherwise raises an ActionController::ParameterMissing error.

rb> ActionController::Parameters.new(product: {name: 'Moto G'}).require(:product)
=> {"name"=>"Moto G"
irb> ActionController::Parameters.new(product: {}).require(:product)
ActionController::ParameterMissing: param is missing or the value is empty: product

permit(*filters)

Returns a new ActionController::Parameters instance that includes only the given filters and sets the permitted attribute for the object to true. This is useful for limiting which attributes should be allowed for mass updating

irb> params = ActionController::Parameters.new(products: {name: 'Moto G', price: '14000'})
=> {"products"=>{"name"=>"Moto G", "price"=>"14000"}}
irb> permitted = params.require(:products).permit(:name)
=> {"name"=>"Moto G"}
irb> permitted.has_key?(:name)
=> true
irb> permitted.has_key?(:price)
=> false

We are going far from the app, let’s get back to work.
When submit button is pressed, along with other parameter this
"product"=>{"name"=>"Sim Adopter", "price"=>"12"}
form data is submitted to controller. We are going to save it into data base.
To do so we need to write in product controller:

 def create
    @product = Product.new(product_params)
    @product.save
    redirect_to @product
  end

  private
    def product_params
        params.require(:product).permit(:name, :price)
    end

Showing Product #


we can add a show action to display details of a product.
first write the test for it:

it "show details of a product" do
        @product = Product.create :name => 'Nexus 4', :price => 25000
        visit product_path(@product)
        page.should have_content 'Nexus 4'
        page.should have_content '25000'    
end

Now satisfy guard:

def show
    @product = Product.find(params[:id])
  end

add a corresponding view show.html.erb

<h1>Product Details</h1>
<p>
     <strong>Name:</strong>
     <%= @product.name %>
</p>

<p>
      <strong>Price:</strong>
      <%= @product.price %>
</p>

Updating Product #


As we need @product object multiple time, lets move it to a before block and clean all after test .

before do
        @product = Product.create :name => 'Nexus 5', :price => 30000
end

 after  do
    Product.delete_all 
 end

Draw a out line of edit page:

it "edit a Product details" do
        visit products_path
        find("#product_#{@product.id}").click_link 'Edit'
        current_path.should == edit_product_path(@product)
        find_field('Name').value.should eq 'Nexus 5'
        find_field('Price').value.should eq '30000'
        fill_in 'Price', :with => '25000'
        click_button 'Save Product'
        page.should have_content '25000'
        visit products_path
        page.should have_no_content '30000'
  end 

Now guard is saying that it can’t see product with id product_product.id. Lets change the index.html and add an id field with each product
so the final index.html.erb will look like so:

<% @products.each do |product| %>
    <tr id="product_<%= product.id %>">
        <td><%= product.name %></td>
        <td><%= product.price %></td>
    </tr>   
<% end %>

Now guard saying that it can’t find the link ‘Edit’, lets add a link to each of the product in index.html.erb

<td><%= link_to 'Edit', edit_product_path(product) %></td>

Guard not able to found edit action in ProductsController. lets do it

def edit
      @product  = Product.find(params[:id])
end

My edit.html.erb template look like so:

<%= form_for :product, url: product_path(@product), method: :patch do |f|%>

    <%= f.label :name %>
    <%= f.text_field :name  %>

    <%= f.label :price %>
    <%= f.text_field :price %>

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

we are pointing the form to the update action.
The method: :patch option tells Rails that we want this form to be submitted via the PATCH HTTP method which is the HTTP method you’re expected to use to update resources according to the REST protocol.

Now guard saying that The action ‘update’ could not be found for ProductsController, lets add that:

def update
    product = Product.find(params[:id])
    if product.update(product_params)
      redirect_to products_path
    else
      render 'edit'
    end
  end

Deleting Product #


now we are going to delete product from catalog

it "delete a product" do
        visit products_path
        find("#product_#{@product.id}").click_link 'Delete'
        page.should have_no_content 'Nexus 5'
        page.should have_no_content 'Nexus 5'
        current_path.should == products_path
end

guard is now asking for a link in index page lets add that and define destory method in controller.

in index.html.erb

<td><%= link_to 'Delete', product_path(product), method: :delete, data: {confirm: 'Are you sure?'} %></td>

in product controller

def destroy
    product = Product.find(params[:id])
    product.destroy
    redirect_to products_path
  end
 
125
Kudos
 
125
Kudos

Now read this

Devise and Mongoid in Rails 4

To use MongoDB as a database in rails application we need to use mongoid. App source Code mongoid # Mongoid is an Object-Document-Mapper (ODM) for MongoDB written in Ruby In a Rails application mongoid provide functionality like... Continue →