Rails Deep Dive: Loccasions, Spork, Events and Authorization

Glenn Goodrich
Ruby Editor
This entry is part 8 of 15 in the series Loccasions

Loccasions

In our last post, we ended with very basic authentication working. However, we are faking out the events_path in our sign_in spec, which is where we’ll start. A successful sign-in redirects to the user events page which, presumably, has a list of the events owned by that user. Let’s go back to Mockbuilder and crank out a layout for our events page.

Events Page Mockup

 

The page has a different layout than the home page, with the main content being a map. The latest occasions will be visible on the map, and the users events will be listed below the map. Another very simple layout, helping us drive the implementation of the site. At this point, we can identify Events as a resource and start developing the items that will represent this resource. Of course, we will need an Event model and an Event controller.

Event Model

At this point, it might be a good idea to solidify what the client wants to track on each Event. This calls for the addition of some more user stories:

As a registered user, I would like to see my Events by Name, Description, and last occurrence.

That story lists our attributes explicitly, so we have enough to generate our model. First, let’s make a git branch:

git checkout -b adding_events

Before we generate the model, a quick thought on the “last occurrence” attribute. Knowing that Events will have Occurrences, it seems to me that we won’t actually store a value for last_occurrence, but will grab the latest date from the Occurances. This makes last occurrence a “virtual attribute”, as it’s generated when you ask for it. However, part of me worries about the need to query on this attribute. Another design decision: Create a Virtual Attribute or a Real Attribute that has to be updated whenever an occurrence is created. Well, premature optimization is the root of all evil. We’ll go with a virtual attribute…for now.

rails g model Event name:string description:string user:references

which generates a model and spec. We have a couple of tests to write for Event. Events belong to users (thus, the user:references in our generator) and can’t exist without a Name. I went ahead and added a Factory for events:

factory :event do
name "Test Event"
user
end

Here is the test for user:

describe Event do
  it "should belong to a user" do
    event = Factory.build(:event, :user=>nil)
    event.valid?.should be_false
    event.errors[:user].should include("can't be blank")
    event.user = User.new
    event.valid?.should be_true
  end
end<

which fails, until we add this to our Event model:

validates :user, :presence => true

Before we continue with the Event specs, I am getting frustrated with how long it is taking each time I run my specs. The spin-up time is too long, so I want to do something to speed this up. My instincts tell me that the specs have to load the Rails application environment, and that is what is taking the most time. I tried just running the model specs (rake spec:models), but the startup time is still bothering me. After much googling, I am going to try adding Spork to our test environment.

Adding Spork

Add gem 'spork', '~> 0.9.0.rc' to the development portion of your Gemfile and bundle install. Now, we need to setup the spec environment for Spork. Thankfully, Spork has some helpers for this. From your project root, type spork --bootstrap, which will add some instructions into your spec/spec_helper.rb file, so open that file. Basically, we need to split our spec_helper into two blocks, prefork and each_run.
Anything we only need to run once for each spec run goes in the prefork block. For now, I am putting everything in the prefork block, so my spec/spec_helper.rb file looks like

require 'rubygems'
require 'spork'
Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However,
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'capybara/rspec'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
  RSpec.configure do |config|
    # == Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr
    config.mock_with :rspec
    # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
    # config.fixture_path = "#{::Rails.root}/spec/fixtures"

    # If you're not using ActiveRecord, or you'd prefer not to run each of your
    # examples within a transaction, remove the following line or assign false
    # instead of true.
    # config.use_transactional_fixtures = true
    # Clean up the database
    require 'database_cleaner'
    config.before(:suite) do
      DatabaseCleaner.strategy = :truncation
      DatabaseCleaner.orm = "mongoid"
    end
    config.before(:each) do
      DatabaseCleaner.clean
    end
  end
end
Spork.each_run do
  # This code will be run each time you run your specs.

end

We may run into issues having to restart Spork to pick up changes, but we’ll deal with that if it happens. Running a quick time check on before and after (time rake vs time rspec --drb spec/), my spec run time dropped by 20 SECONDS! Wow…that is a worthy of a nerdgasm. To finish our Spork changes, add --drb to your .rspec file so that it uses Spork by default. By the way, since Spork loads up the Rails environment, it will be necessary to restart Spork when that environment changes (new route added, etc.) Something to bear in mind.

Back to Testing

OK, now we can finish our Event specs. Let’s add a quick test making sure ‘name’ is a required attribute.

it "should require a name" do
  event = Factory.build(:event, :name=>nil)
  event.valid?.should be_false
  event.errors[:name].should include("can't be blank")
  event.name ="Event Name"
  event.valid?.should be_true
end

This fails, because we are not validating the name. Change the line we added to validate the user to:

validates :user, :name, :presence => true

and the spec passes.

Testing That a User Has Events

Since we want to be able to build events for Users, let’s put in some tests to make sure that works:

describe "User Event" do
  it "can be built for a user"  do
    lambda {
     @user.events.build(:name=>"A new event")
    }.should change(@user.events, :length).by(1)
  end
  it "can be removed from a user" do
    @user.events.build(:name => "A short event")
    lambda {
      @user.events.first.destroy
    }.should change(@user.events, :length).by(-1)  end
end

These specs pass without further coding, so, um, yay? (NOTE: This is not true…Alert Reader Nicolas (see comments) rightly points out that we still have 2 things to do:

1) Add embeds_many :events to the User model.
2) Either restart spork or add ActiveSupport::Dependencies.clear to our Spork.each section in spec_helper.rb.
)

Events Controller

Now we need to display our events on the user events page. We’ll need an EventsController and an index action/view. Generators, ho!

rails g controller events index


Let’s undo our route cheat from the last post, and point the events_path to our events#index action. Delete get "events#index" and change the ‘events’ route to match 'events' => 'events#index', :as => :events and run the specs. HMMM…they fail.

Events spec fails!

I wasn’t expecting that to fail. Wait a sec, I didn’t write that events_controller_spec. Stupid generators…I don’t want that spec. Delete the spec/controllers directory with extreme prejudice. There, our specs are passing again. As I mentioned in the setup of this series, we are, as much as possible, driving the testing through accpetance tests. Because of that, we won’t have specific controller/view specs.

Create the file specs/acceptance/user_events_spec.rb to hold the specs for our user events page. In order to see the user events page, we’ll have to sign in from our spec. Since we are using request specs, we need to, basically, simulate the posting of credentials to the server. We can do that with a quick mixin, which we’ll include in our feature. Add this to spec/support/request_helpers.rb

def login_user(user)
  visit new_user_session_path
  fill_in "Email", :with => "testy@test.com"
  fill_in "Password", :with => "password"
  click_button "Sign in"
end

As you can see, we are literally signing into the application.

Now, let’s create the spec/acceptance/user_events_spec.rb file with

require 'spec_helper'

feature 'User Events Page', %q{
  As a signed in user
  I want to see my events
  on my events page
} do
  background do
    @user = Factory(:user)
    @event = Factory(:event, :user => @user)
  end
  scenario "User is not signed in" do
    visit events_path
    current_path.should == new_user_session_path
  end
end

We are going to test the negative path first, meaning, what happens when we don’t sign in and try to go to the user events page. In this case, the app should redirect to the Sign In page (which is the new_user_session_path) Run this spec, and the path points to a (rather ugly, make a note to fix that) events URL, so it’s not redirecting. We need to tell the EventsController to authenticate the user. Thanks to Devise, all we have to do is add this (put it right under the class statement)

before_filter :authenticate_user!

and the spec passes. Now, let’s create the positive path

feature 'Signed In User Events Page', %q{
  As a signed in user
  I want to see my events
  on my events page
} do
  background do
    @user = Factory(:user)
    @event = Factory(:event, :user => @user)
    login_user(@user)
  end
  scenario "User is signed in" do
    visit events_path
    page.should have_content(@user.name)
    page.should have_content(@event.name)
  end
end

We are measuring success here by simply making sure the user name and event name are on the page. It’s likely that we’ll have to strengthen this test later.

Run the spec, and it will complain that the user’s name was not found on the page. Let’s open up the app/views/events/index.html.haml file and see what’s up. That view is still the basic, generated view, so we need to match it to our mockup as much as possible. First off, the “sign in area” in the mockup has a greeting and the user name. That bit is in the app/views/layouts/application.html.haml file. I changed the #sign_in div to

#sign_in.sixteen.columns
  %span
    -if user_signed_in?
      Hullo #{current_user.name}
      |
      = link_to "Sign Out", destroy_user_session_path, :method => :delete
    - else
      = link_to "Sign In", new_user_session_path

Run the spec, and now it complains about the event name. Progress. Here, we’ll open up the app/views/events/index.html.haml view and change it to

%ul#events
  - for event in @events
    %li= event.name

Which leads to the spec complaining about You have a nil object when you didn't expect it! (I hate that error, it has caused my hours of frustration) because we are looping over an @events object that does not exist. To the controller!

class EventsController < ApplicationController
  before_filter :authenticate_user!
  def index
    @events = current_user.events
  end
end

We are back to passing specs.

Wrap Up

We are still just coming out of the gate with Loccasions, but we’re picking up speed. The next post will, hopefully, allows us to flush out the rest of the events page and allow us to create and modify events.

Loccasions

<< Rails Deep Dive: Loccasions, AuthenticationRails Deep Dive: Loccasions, Making Events >>Loccasions: Pair Programming >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://blog.firsthand.ca Nicholas Henry

    “Testing That a User Has Events” I found three issues with this:

    1. The Spec itself is not valid, you probably want to add this before block since @user has not been assigned:

    before do
    @user = Factory(:user)
    end

    2. “These specs pass without further coding, so, um, yay?”: the step missing here is adding the following to the user model:

    embeds_many :events

    3. But this still won’t pass because the new class definition has not be loaded when running spork. Adding the following to each_run in the spec_helper.rb resolves this issue:

    ActiveSupport::Dependencies.clear

    Ref: http://outofti.me/post/2639558113/spork-mongoid-2-rails-3?74269840

    There’s also this strategy, but it did not work for me:
    https://github.com/timcharper/spork/wiki/Spork.trap_method-Jujutsu

    • http://www.ruprict.net/ Glenn Goodrich

      1. I put that before block in the higher level ‘describe User do’ block, so it gets picked up from there.

      2. Yup…totally skipped that step in the article. Nice catch….will fix.

      3. Or restart spork. I like your solution, though, so I’ll add it.

      Thanks Nicolas!

      • http://blog.firsthand.ca Nicholas Henry

        1. Yes, quite right. I think a clarification would be good to add the code to the user_spec.rb since the last spec that was worked on was the event_spec.rb I had put the spec in a new file user_event_spec.rb which got me into trouble hence the addition of the before block.

  • http://blog.firsthand.ca Nicholas Henry

    “Delete get “events#index” and change the ‘events’ route to match ‘events’ => ‘events#index’, :as => :events and run the specs.”

    You need to leave :as => :user_root devise correct? Looking at your Github repo I think you mean to say is add a resource route for events.

    • http://www.ruprict.net/ Glenn Goodrich

      Hey Nicolas,

      So, the repo is well ahead of this post, and you are spot on. The series is attempting (how well is another question) to build the app and each article shows it at a point in time. So, later posts end up where you are.

      Thanks for the comment.

      • http://blog.firsthand.ca Nicholas Henry

        Re: “Delete get “events#index” and change the ‘events’ route to match ‘events’ => ‘events#index’, :as => :events and run the specs.” Yes, I’m sure when I when I followed the instructions outlined in the post that making the change suggested made my specs fail. I can double check to confirm to make sure I’m not talking a load of rubbish :)

      • http://blog.firsthand.ca Nicholas Henry

        OK, I can confirm that if follow the routing instructions that it will break the sign_in_spec.rb as the user_root_path has been removed:

        Failures:

        1) Sign In As an administrator
        I want to sign in to Loccasions Successful Sign In
        Failure/Error: current_path.should == user_root_path # this path is used by Devise
        NameError:
        undefined local variable or method `user_root_path’ for #
        # ./spec/acceptance/sign_in_spec.rb:25:in `block (2 levels) in ‘

        • http://www.ruprict.net/ Glenn Goodrich

          Yup, :as => :user_root has to stay.

  • http://blog.firsthand.ca Nicholas Henry

    An issue with the visit events_path(@user) in the user_events_spec.rb. The user not should be passed in the parameters as you are scoping in the controller with current_user. The spec should simply read visit events_path.

    • http://www.ruprict.net/ Glenn Goodrich

      Another great catch…fixed.