Rails Deep Dive: Loccasions, Spork, Events and Authorization
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.