How to Install Capybara with RSpec Rails

Leave a comment

Gemfile

group :development, :test do
  gem 'sqlite3'
  gem 'rspec-rails', '2.13.1'
end

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
end

> bundle install
> rails generate rspec:install

spec/spec_helper.rb

RSpec.configure do |config|
  config.include Capybara::DSL
end

Note: this is different than plan rspec gem. It’s an rspec gem specially designed for rails that creates it’s own spec_helper.

Advertisements

How to setup RSpec from scratch

Leave a comment

> mkdir foo
> cd foo
> gem install bundler

Gemfile

source 'https://rubygems.org'
gem 'minitest', '~> 5.3.4'
gem 'rspec', '2.14.1'
gem 'capybara', '2.3.0'
gem 'selenium-webdriver'
> bundle install
> rspec --init

lib/zombie.rb

class Zombie
  attr_accessor :name
 
  def initialize
    @name = 'Ash'
  end
 
end

spec/lib/zombie_spec.rb

require 'zombie'

describe Zombie do

  it 'is named Ash' do
    zombie = Zombie.new
    expect(zombie.name).to eq('Ash')
  end

end

> rspec spec/lib/zombie_spec.rb

Trouble shooting

Use this is having trouble with named routes and rspec.

# Added below
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'

RSpec.configure do |config|
  config.treat_symbols_as_metadata_keys_with_true_values = true
  config.run_all_when_everything_filtered = true
  config.filter_run :focus
  config.order = 'random'
  config.use_transactional_fixtures = true


  config.include Capybara::DSL # JR - added to get paths working
end

How to pass parameters as part of the URL in Rails

3 Comments

While building my latest rails app I wanted to pass the id of a video episode to my subscription service. I wanted to build a URL that looked something like this:

http://localhost:3000/subscriptions/new?episode=2

But I didn’t want to just hard code it. I wanted to do it the rails way (using the named paths). Here’s I did it:

new_subscription_path(:episode => {:episode_id => episode.id}) }

or by hand

href=”/subscriptions/new?episode[episode_id]=”

Which both produce this URL:

http://localhost:3000/subscriptions/new?episode%5Bepisode_id%5D=2

or base64 encoded

http://localhost:3000/subscriptions/new?episode%5Bepisode_id%5D=2

And this is how I can pull the episode id from the URL in my controller:

@episode = Episode.find(params[:subscription][:episode_id])

Here is a full code example with tests:

subscription_pages_spec

require 'spec_helper'

describe "Subscription" do

  subject { page }

  describe "new subscription page" do
    let!(:user) { FactoryGirl.create(:user) }
    let!(:episode) { FactoryGirl.create(:episode, title: "This episode" ) }

    describe "not signed in" do
      before { visit new_subscription_path(:episode => {:episode_id => episode.id}) }
      it { should have_title "Sign in" }
    end

    describe "should return to New Subscription after signing in" do
      before do
        visit new_subscription_path(:episode => {:episode_id => episode.id})
        sign_in user
      end
      it { should have_title "My Cart"}
      it { should have_content('Nice choice!') }
    end

    describe "signed in" do
      before { sign_in user }

      describe "should display new subscription page" do
        before { visit new_subscription_path(:episode => { :episode_id => episode.id}) }
        it { should have_title "My Cart"}
        it { should have_content('Nice choice!') }
      end
    end

    describe "signed in and subscribed" do
      before do
        user.subscribe!(episode)
        sign_in user
      end

      describe "should show video" do
        before { visit new_subscription_path(:episode => { :episode_id => episode.id}) }
        it { should have_title "This episode"}
        it { should have_content('Already subscribed') }
      end

    end

  end
end

View.erb

      <div class="text-centered">
        <a id="Watch now" class="btn btn-large btn-primary" href="/subscriptions/new?episode[episode_id]=<%= @episode.id %>">Watch Now
          <span class="subprice">Only $<%= @episode.price %></span>
        </a>
      </div>

Controller.rb

  def new

    @episode = Episode.find(params[:episode][:episode_id])

    if signed_in? && current_user.subscribed?(@episode)
      redirect_to @episode, notice: 'Already subscribed. Enjoy the video!'
    else
      flash[:notice] = "Nice choice! Project management is super important and this is a great video."
    end

  end

Links that help

http://stackoverflow.com/questions/10773695/rails-passing-parameters-in-link-to
http://stackoverflow.com/questions/1898737/pass-parameter-by-link-to-ruby-on-rails

How to add a drop down resource to an existing rails model

3 Comments

For my pet rails project I need to add a category (drop down) to a listings page.

Step 1: Create the category resource.

Using the rails scaffolding, I create a brand new resources and it’s respectful Restful services like this:

$ rails generate scaffold Category name:string

This gives me a simple categories table with an id and name lookup.

Step 2: Create the migration

I now want to connect the listing to the category through the category_id.

$ rails generate migration AddCategoryToListings category_id:int
class AddCategoryToListings < ActiveRecord::Migration
  def change
    add_column :listings, :category_id, :int
  end
end

> rake db:migrate
> rake db:test:prepare

Note: this is not a database enforced foreign key constraint. Just a rails active record connection.

Step 3: Update the models

Now I need to make it clear that a listing belongs_to a category, and that a category has_many listings a the model level.

I also need to make the new category_id variable available for mass assignment by adding it to the attr_accessible.

/models/listing.rb

class Listing < ActiveRecord::Base
  attr_accessible :title, :description, :category_id

  belongs_to  :category

/models/category.rb

class Category < ActiveRecord::Base
  has_many :listings
end

Step 4: Setup some test factories.

To make sure things are working at database level, I need some factories to give me some test data.

Here I define a factory for a user, listing, and category.

/spec/factories.rb

Factory.define :user do |user|
  user.name                  "Michael Hartl"
  user.email                 "mhartl@example.com"
  user.password              "foobar"
end

Factory.define :listing do |listing|
  listing.title "Title"
  listing.description "Description"
  listing.association :category
  listing.association :user
end

Factory.sequence :category_name do |n|
  "#{n}"
end

Factory.define :category do |c|
  c.name  Factory.next :category_name
end

Step 5: Test the model.

/spec/models/listing_spec.rb

require 'spec_helper'

describe Listing do

  before(:each) do
    @user = Factory(:user)
    @category = Factory(:category)
    @attr = { :title => "some title",
              :description => "some description",
              :category_id => @category.id }
  end

  it "should create a new instance given valid attributes" do
    @user.listings.create!(@attr)
  end

  describe "associations" do
    
    it "should have a category attribute" do
      @category =  @listing.should respond_to(:category)
    end

  end

  describe "validations" do

    before(:each) do
      @attr = { :title => "some title",
                :description => "some description",
                :category_id => @category.id }
    end

    it "should build a listing" do
      @user.listings.build(@attr).should be_valid
    end

    it "should require a category" do
      @user.listings.build(@attr.merge(:category_id => nil)).should_not be_valid
    end

  end

end

A few words about the controller and the view

Before we can test the controller and the model, it’s important to understand what’s going on.

The controller set’s things up for the view. That is, it sets up the the @listing attributes, so the view knows how to render the html necessary to create the view. And it populates the @categories object so the collection_select has all the category information it needs to populate a fully loaded category drop down.

/controllers/listings_controller.rb

class ListingsController < ApplicationController

  before_filter :authenticate, :only => [:create, :destroy, :new]
  before_filter :prepare_categories

  def new
    @listing = Listing.new
    @user = current_user
  end

  def create
    #raise params.inspect

    @listing  = current_user.listings.build(params[:listing])

    begin
      @listing.save!
      flash[:success] = "Listing created!"
      redirect_to root_path
    rescue Exception => e
      render :action => 'new'
    end
  end

  def destroy
  end

  # add the @categories = Category.All to the before action so avail for all actions

  private
    def prepare_categories
      @categories = Category.all
    end

end

Once the controller does this, the view is good to go.

/view/listings/new.html.erb

<%= form_for @listing do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title, :size => 57 %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description, :class => 'description' %>
  </div>
  <div class="field">
    <%= f.label :category %><br />
    <%= f.collection_select(:category_id, @categories, :id, :name, :include_blank => "Please select") %>
  </div>
  <div class="actions">
    <%= f.submit "Submit" %>
  </div>
<% end %>

Step 6: Test the controller

/spec/controllers/listings_controller_spec

require 'spec_helper'

describe ListingsController do
  render_views
  
  describe "GET 'new'" do

    it "should be successful" do
      get :new
      response.should be_success
    end

    it "should prepare categories" do
      get :new
      assigns(:categories).should_not be_nil
    end
  end


  describe "POST 'create'" do

     describe "failure" do

       before(:each) do
         @attr = { :title => "", :description => "", :category_id => "" }
       end

       it "should not create a listing" do
         lambda do
           post :create, :listing => @attr
         end.should_not change(Listing, :count)
       end

       it "should render the home page" do
         post :create, :listing => @attr
         response.should render_template('listings/new')
       end
     end

     describe "success" do

       before(:each) do
         @attr = { :title => "Lorem ipsum",
                   :description => "Lorem ipsum",
                   :category_id => '1' }
       end

       it "should create a listing" do
         lambda do
           post :create, :listing => @attr
         end.should change(Listing, :count).by(1)
       end

       it "should redirect to the home page" do
         post :create, :listing => @attr
         response.should redirect_to(root_path)
       end

       it "should have a flash message" do
         post :create, :listing => @attr
         flash[:success].should =~ /listing created/i
       end
     end
  end
end

Step 7: Test the view.

/spec/requests/listing_spec.rb

require 'spec_helper'

describe "When creating a new listing" do

  it "should create" do

    # signin
    user = Factory(:user)
    visit signin_path
    fill_in "Email",    :with => user.email
    fill_in "Password", :with => user.password
    click_button

    lambda do
      visit '/listings/new'
      fill_in "Title",          :with => "SomeTitle"
      fill_in "Description",    :with => "SomeDescription"
      select "books",            :from => "listing_category_id"
      click_button
    end.should change(Listing, :count).by(1)
  end

end

Note: This test used webrat, and had some test fixture data loaded into the test database via

test/fixtures/categories.yml

one:
  name: books

two:
  name: computers
$ rake db:fixtures:load RAILS_ENV=test

Just a heads up – this example may not work out of the box. It’s just something I through up to remind myself what the steps were for the next time I did this.

To learn more about what’s going on behind the scenes I recommend Michael Hartl’s Ruby Tutorial.

Also apologies for any spelling/typos.

%d bloggers like this: