I got the inspiration from Ben Mabey at ‘ben.send :blog’ which illustrates an approach to stories that is unbelievably easy to pick up. He also provides source code to the example that he’s built so that you can play with it. Very cool!
I snagged that source myself and started playing with it, and added a lot of functionality of my own that I found useful. The real cool thing is that the ONLY application specific code I have is this:
# user_steps.rb
steps_for(:user) do
Given("that $actor is not logged in") do |actor|
get "/sessions/destroy"
response.should be_redirect
flash[:notice].should == "You have been logged out."
#fails here even though in the next request it will pass
#controller.should_not be_logged_in
end
Then("$actor $should be logged in") do |actor, should|
controller.send(shouldify(should), be_logged_in)
end
end
# navigation_steps.rb
def path_from_descriptive_name(page_name)
case page_name
when "home"
""
when "signup"
"users/new"
when "login"
"sessions/new"
else
"#{page_name}"
end
end
With that, and the generic application-independent helper methods that also live in my steps directory, I can write the following story:
Story: Making posts
As a visitor
I want to be a member
So that I can make posts
Scenario: Glenn creates a new account
Given that Glenn is not logged in
And there are some number of users
And no user with login 'Glenn' exists
When he goes to the signup page
And fills in Login with 'Glenn'
And fills in Email with 'glenn@glennfu.com'
And fills in Password with 'blah'
And fills in Confirm Password with 'blah'
And clicks the 'Sign up' button
Then there should be 1 more user
And a user with login 'Glenn' should exist
Scenario: Glenn logs in
GivenScenario Glenn creates a new account
When Glenn goes to the login page
And fills in Login with 'Glenn'
And fills in Password with 'blah'
Then he should be logged in
Scenario: A member makes a post while logged in
GivenScenario Glenn logs in
And there are some number of posts
When he goes to the posts page
And clicks on 'New post'
And fills in Title with 'Woo'
And fills in body with 'Stuff here'
And clicks the 'Create' button
Then there should be 1 more post
And a post with title 'Woo' should exist
Scenario: A nice lady makes a post while not logged in
Given that she is not logged in
Then she should not be logged in
Given there are some number of posts
When she goes to the posts page
And clicks on 'New post'
Then she should see the login page
Scenario: An angry visitor should be able to see posts
Given that he is not logged in
When he goes to the posts page
Then he better not f'ing see the login page
Scenario: A visitor should not be able to edit posts
GivenScenario A member makes a post while logged in
And that she is not logged in
When she goes to the posts page
And clicks on 'Edit'
Then she should see the login page
Pretty cool, huh? I was even able to add some personality with the angry visitor Scenario. Notice that I don’t have any steps written about Posts or my PostsController. I don’t need it because I have generic model steps that parse the model from the text in my PlainText instructions. It has all the basics that you need. You interact with forms and links on the top level by name, and then check the results either by text on the page or by finding models.
If you’d like to play with it yourself, I’ve zipped up my stories folder for you to play with.
It also has the old user_story.rb from the previous example. If you have any suggestions for improvements to this or ideas for something else I should do with it, please leave a comment!
My only quirk with this is that if you check the first step in the steps_for(:user) above you’ll see I have a line commented out. It checks for controller.should_not be_logged_in and if I uncomment it, the check fails even though the two above it pass. As you can see in the PlainText, on the very next line I call the very same check and it passes. If you could help me figure out my mistake here I’d really appreciate you leaving a comment with your thoughts!
I hope you find this useful, and if so, be sure to go toss a thank you over to Ben for the inspiration.
Update: I’ve made some improvements upon this using WebRat that you can read all about here.
I’m having a great time with RSpec‘s new StoryRunner, and wanted to share a bit of what you can do with it. I learn very well from examples and I know many others do as well so I hope that this provides as a useful resource for getting your ideas flowing on how to use StoryRunner for your own projects.
To start, I set up a simple project using the following
# Create the project
rails blog
cd blog
# Install Restful Authentication
script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/
script/generate authenticated user sessions
# Create a Post model
script/generate scaffold post title:string body:text published:boolean
Now I have the essentials for a simple app to play with. Of course you’ll also want to install RSpec.
Now to try out StoryRunner! I’ll write a story to cover the login system. No need to write Spec’s for it, it’s already well tested. However in writing this story we get a reusable Scenario that will be nice to have later, as well as give us a nice starting point for stories.
require File.join(File.dirname(__FILE__), "helper")
Story "User Stuff", %{
As a writer
I want to be a member
So that I can make posts
}, :type => RailsStory do
Scenario "I log in as a new user" do
Given("that I am not logged in") do
delete "/sessions/destroy"
controller.should_not be_logged_in
@user_count = User.count
end
When("I create a new user", "Glenn") do |name|
@user = User.create(:login => name,
:email => "#{name}@glennfu.com", :password => name,
:password_confirmation => name)
end
Then("there should be 1 more user stored") do
User.count.should == @user_count + 1
end
When("I login as", "Glenn") do |name|
post "/sessions/create", :login => name, :password => name
end
Then("I should be logged in") do
controller.should be_logged_in
response.should be_redirect
end
end
end
This should be pretty easy to follow. I start by logging out, then creating a user and logging in with it. This is runnable with ruby stories/user_story.rb
Now that this is already written, we can easily add similar stories. Notice that we don’t have to re-define our steps here:
Scenario "I log in with the wrong user/password" do
Given "that I am not logged in"
When "I create a new user", "Glenn"
Then "there should be 1 more user stored"
When "I login as", "JoeBob"
Then "I should not be logged in" do
controller.should_not be_logged_in
response.should_not be_redirect
end
end
Now I’ll write a story for our Posts. Notice the usage of “GivenScenario”, very cool!
Scenario "Create a new post while logged in" do
GivenScenario "I log in as a new user"
Then "I should be able to make a post" do
post "/posts/create", :post => {:title => "the title",
:body => "The body!"}
@post = Post.find_by_title("the title")
flash[:notice].should == "Post was successfully created."
@post.should_not be_nil
response.should be_redirect
end
end
What if I want to also test posts failing? I’ll modify the previous step to be a bit more robust (and also more useful).
Then "I $should be able to make a post", "should" do |should|
post "/posts/create", :post => {:title => "my title",
:body => "The body!"}
@post = Post.find_by_title("my title")
if should == "should"
flash[:notice].should == "Post was successfully created."
@post.should_not be_nil
else
flash[:notice].should be_nil
@post.should be_nil
end
response.should be_redirect
end
With this change, I can easily add a new Scenario to test when making posts should fail:
Scenario "Create a new post while not logged in" do
Given "that I am not logged in"
Then "I $should be able to make a post", "should not"
end
I just recently worked on a project in which I needed to modify what time the Ruby on Rails server thought it was. I was able to change this very easily with the help of court3nay while on #rubyonrails.
def Time.now
Time.parse("2010-02-30", nil)
end
With this I am able to change the server time whenever I want! The system was one in which user interface capabilities changed at different times of the month. Obviously I didn’t want to wait 20 days to see something different!
I placed this code snippet in my config/environments/development.rb file so that everything in my application could access it, and it wouldn’t affect the test or production environments. The only downside to this is that you have to restart the server in order to change the time. Of course, in RoR this takes like 5 seconds so it was no big deal for me.
In my specs I used stuff like
date = "2008-01-01"
Time.stub!(:now).and_return(Time.parse(date))
which of course made it very easy to write effective specs that covered all of my functionality. Thank you RSpec!
Voila, now you too can control time!!
Comments Off on Overriding Time.now to Control the Server Time
I just recently built a rake task in my project where I wanted to loop through a list of Users, queue up some data for each one, then loop back through them again to process the data that had been queued. I didn’t need the data to live for very long, but I needed it for 2 separate requests.
The problem with this is that in Ruby on Rails, instance variables are not persistent across requests. This means their scope is limited to one usage of an object and the next time you call for that object, the variable is gone.
Unfortunately, the only solution I could imagine for my problem without persistent instance variables was to create a massive join table to connect my generated data between Users. Not only would this have been a lot of work, it would have been a big waste considering the data would have only lived for about a minute as the rake task was executed.
I wanted to improve/increase the scope of instance variables, and so my solution was to take advantage of Class Variables.
At the bottom of my User class, I now have the following:
protected
def late_employees
@@late_employees ||= {}
@@late_employees[self] ||= {}
@@late_employees[self]
end
def late_team_members
@@late_team_members ||= {}
@@late_team_members[self] ||= {}
@@late_team_members[self]
end
def clear_late_emails
@@late_employees[self] = {}
@@late_team_members[self] = {}
end
As you can see, each element of the Class Variable hashes are referenced by the current instance. So when I call u.late_employees, it’s always the same across requests until either I call u.clear_late_emails or the server dies.
To use them, just treat them like any normal instance method/variable. Here is an example of how you can do that:
def employee_late(user, days)
if user.manager == self
late_team_members[user] = days
else
late_employees[user] = days
end
end
def has_late_employees?
!(late_employees.empty? and late_team_members.empty?)
end
def send_late_employee_emails
if has_late_employees?
EvaluationNotifier.deliver_late_employee_notification(self)
end
length = late_employees.length + late_team_members.length
clear_late_emails
length
end
Voila, instance variables that are persistent across requests!
When I set up my latest application on DreamHost, I had a real pain of a time. Most of the references I found for help were written in 2006 and referred to outdated commands. Since top Google results for “dreamhost capistrano ruby on rails” are mostly no longer adequate, I hope to help out anyone else who finds themselves in my shoes!
# The host where people will access my site
set :application, "test.gamelizard.com"
set :user, "my dreamhost username set to access this project"
set :admin_login, "my admin login name"
set :repository, "http://#{user}@svn.gamelizard.com/rgamelizard/trunk"
# If you aren't deploying to /u/apps/#{application} on the target
# servers (which is the default), you can specify the actual location
# via the :deploy_to variable:
set :deploy_to, "/home/#{admin_login}/#{application}"
# My DreamHost-assigned server
set :domain, "#{admin_login}@ajax.dreamhost.com"
role :app, domain
role :web, domain
role :db, domain, :primary => true
desc "Link shared files"
task :before_symlink do
run "rm -drf #{release_path}/public/bin"
run "ln -s #{shared_path}/bin #{release_path}/public/bin"
end
set :use_sudo, false
set :checkout, "export"
# I used the handy quick tool to set up an SVN repository on DreamHost and this is where it lives
set :svn, "/usr/bin/svn"
set :svn_user, 'my svn username'
set :svn_password, 'my svn password'
set :repository,
Proc.new { "--username #{svn_user} " +
"--password #{svn_password} " +
"http://svn.gamelizard.com/rgamelizard/trunk/" }
desc "Restarting after deployment"
task :after_deploy, :roles => [:app, :db, :web] do
run "touch #{deploy_to}/current/public/dispatch.fcgi"
run "sed 's/# ENV\\[/ENV\\[/g' #{deploy_to}/current/config/environment.rb > #{deploy_to}/current/config/environment.temp"
run "mv #{deploy_to}/current/config/environment.temp #{deploy_to}/current/config/environment.rb"
end
desc "Restarting after rollback"
task :after_rollback, :roles => [:app, :db, :web] do
run "touch #{deploy_to}/current/public/dispatch.fcgi"
end
The permissions in the script/process folder should look like:
-rwxr-xr-x 1 Malohkan staff 108 Jan 7 19:06 reaper
-rwxr-xr-x 1 Malohkan staff 109 Jan 7 19:06 spawner
If they look like “-rwxr-xr-x@” as can happen on a Mac you’ll want to kill the file and re-create it with the same content. I think this happens due to copying files and is representative of the notion that only the current logged-in user can access that file. As a result, when Capistrano deploys the file, it will not have permission to run it.
If you have other issues or concerns that are not addressed here, please leave a comment and I’ll do my best to help further!
If you don’t like the trouble caused by dealing with Ruby on Rails on DreamHost, check out this article for some ideas of good alternatives!
After updating to Ruby on Rails 2.0.2 I ran into a problem where old code stopped working. The Ruby on Rails Weblog talks about new functionality to allow for us to use .html.erb instead of .rhtml and .js.erb instead of .rjs.
However it appears they may have broken something. In my project I have a controller with a ‘join’ method ending with this code:
respond_to do |format|
format.html { render :layout => 'join' }
format.js
end
My problem was that the join.rjs file was not called at all when the join action was called with text/javascript in the header (an AJAX call). Instead, the .html layout would always return.
Interestingly enough, renaming join.rjs to join.js.erbdid allow for the file to be returned when I made an AJAX call, but RoR did not format it into Javascript.
My solution was to rename the join.rhtml to join.html.erb. Now the Javascript file gets properly executed with both .rjs and .js.erb.
I’m not sure under what exact conditions this bug pops up, but I know I have many other AJAX calls and many other .rhtml and .rjs files working just fine in other areas of my project. However, until this bug (if it really is a bug) is adequately dealt with, this workaround seems to do just fine.
Edit: According to this forum post, while the original documentation is misleading, the desired format is .js.rjs. This should also solve this problem.
Comments Off on Javascript (RJS) Not Called In Ruby on Rails 2.0.2
Recently in a project that my brother and I were working on we ran across a problem with the Ajax.InPlaceEditor resulting in the number of rows for the editor being incorrectly determined. Here you can see the area before it’s used:
When I clicked on the edit icon I got:
Clearly not what I wanted. After fiddling with all the possible pieces and configurable parameters I finally traced it down to the source and realized there was a bug in the original code:
createEditField: function() {
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
Looking in the 2nd check of the if statement you see it’s checking for line breaks in “this.getText()”. It should have been checking the “text” variable it had just created above it. Changing that code to:
createEditField: function() {
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
if (1 >= this.options.rows && !/\r|\n/.test(text)) {
…fixed the problem! Now when I click my InPlaceEditor I get:
To share my fix I went to the Script.aculo.us page on Submitting Patches but unfortunately their instructions didn’t work right away for me.
Luckily Ryan Bates posted a Railscast called Contributing to Rails that had the right information I needed. Once I had successfully produced my diff file I submitted the patch to the Rails Trac.
Hooray! My first ever patch!
Comments Off on Script.aculo.us Patch – InPlaceEditor Incorrectly Determining Row Count
Here you’ll find a really handy tool for checking everywhere in your code that you use any paths and allows you to fairly quickly create a hash linking from your old routes to the new correct routes, then run a script to update all of your code with the changes.
Unfortunately for me I had a LOT of custom routes, but I knew a lot of them still worked so I didn’t want to manually try and compare them with the “rake routes” output to see what was out of date.
At the time of this posting the code doesn’t have a way to show me only what’s messed up, and while I know the developer could add some functionality to check which of my routes was not found in a proper way, in the meantime I’ve come up with a cheap hack.
Just change line 22 from:
"#{m}" => ""
to
"#{m}" => "#{m}"
EDIT: The original author implemented my change and now requires no editing in order to use in this manner. Enjoy!
and voila you’ll get some helpful output looking something like mine:
"The following new named routes you've specified seem to be not existent"
"home_room_path"
"partitioned_path"
"test_room_report_path"
Now instead of checking all 50+ of my crazy custom routes to figure out which ones don’t go anywhere, I’m given only the 3 from my code that don’t have a proper map.
At the time of this posting, the latest gem release of RedGreen has a bug. For those of you who don’t know, RedGreen is a handy tool to make specs/unit tests color according to the result of the test to make your testing output much easier to read. Unfortunately, at the time of this posting, the latest gems (Autotest, RedGreen, Ruby on Rails, RSpec) have an issue trying to play nicely together.
RedGreen actually will cause Autotest to crash when one of the following happens
You save your code with a compile error (eg. missing ‘end’ tag)
You add new spec files
The error looks like this:
/Library/Ruby/Gems/1.8/gems/ZenTest-3.6.1/lib/autotest/redgreen.rb:8: undefined method `match' for nil:NilClass (NoMethodError)
and will follow the compile error from your code, then stop autotest.
I posted a comment for the original author, Pat Eyler, and the comment was manually approved, but I haven’t received any actual response to know whether or not it will be updated. Just in case it won’t be, I hope this simple fix is useful for you.