rspec on rails, LocalJumpError - no block given
Quite frequently in rails I find myself saying something like:
def show
@photo = User.find(params[:user_id]).photos.find(params[:id])
end
Pretty simple right…
But it can be the cause of headaches when trying to write rspec tests. Writing the following test:
describe PhotosController, ‘handling GET /users/1/photos/2.png’ do
before(:each) do
@photo = mock_model(Photo)
@user = mock_model(User, :photos => [@photo])
[@photo].stub!(:find).and_return(@photo)
User.stub!(:find).and_return(@user)
end
def do_get
get ’show’, :user_id => 1, :id => 2, :format => ‘png’
end
it “should find the specified user” do
User.should_receive(:find).once.with(‘1′).and_return(@user)
do_get
end
it “should find the found users photos” do
@user.should_receive(:properties).once.and_return([@photo])
do_get
end
it “should find the specified photo from within the found photos” do
[@photo].should_receive(:find).once.with(‘2′).and_return(@photo)
do_get
end
end
Will result in the following errors:
LocalJumpError in 'PhotosControllerhandling GET /users/1/photos/2.png should find the specified user'
no block given
LocalJumpError in 'PhotosControllerhandling GET /users/1/photos/2.png should find the found users photos'
no block given
LocalJumpError in 'PhotosControllerhandling GET /users/1/photos/2.png should find the specified photo from within the found photos'
no block given
The cause of the problem is this. When rspec runs it is calling the Ruby Enumerable#find on an array of one object [@photo]. Enumerable#find expects a block. In the rails code, ActiveRecord::Base::find is being called which is really doing a scoped find for all photos belonging to the user. The solution is to stub the find method on the photos array in the before block of your rspec tests. Here is the working test:
describe PhotosController, ‘handling GET /users/1/photos/2.png’ do
before(:each) do
@photo = mock_model(Photo)
@photos = [@photo]
@photos.stub!(:find).and_return(@photo)
@user = mock_model(User, :photos => @photos)
User.stub!(:find).and_return(@user)
end
def do_get
get ’show’, :user_id => 1, :id => 2, :format => ‘png’
end
it “should find the specified user” do
User.should_receive(:find).once.with(‘1′).and_return(@user)
do_get
end
it “should find the found users photos” do
@user.should_receive(:properties).once.and_return(@photos)
do_get
end
it “should find the specified photo from within the found photos” do
@photos.should_receive(:find).once.with(‘2′).and_return(@photo)
do_get
end
end
best-mobile-phones.org attempt 2!
I have finally gotten around to re-writing the first rails site I ever wrote. So here it is http://www.best-mobile-phones.org.uk, the new and improved best mobile phones. The original was just a simple DB, and literally everything was using page caching. I had used out of the box scaffolding to edit the db, and whenever a user submitted a review, a cache_sweeper would clear the required pages from the cache.
The new site is built almost completely using REST. All phones lists are paginated using the will_paginate plugin, although I have not implemented the AJAX version yet. All the images are stored in the DB, and cached on the server when requested. I’ve also built a sweet CMS which allows me to edit almost anything on the site once logged in. There are sweepers set up for all of the data so any caches affected by the CMS are automatically expired. Finally, I also decided it was time to learn some web2.0 CSS! So I got my GIMP out, made a “Beta” badge, and curved the edges of any box I saw! So there it is, please go take a look, find some bugs, give me some feedback, buy a phone. End advertisement!
Custom REST routes
When developing rails applications using REST, all controllers have only seven actions (index, show, new, create, edit, update and destroy). Occasionally there is a need for additional custom actions. They can be added very simply in config/routes.rb. For example, in part of an application showing a list of venues, you may want to list only venues which have not closed down, and you may want to mark a venue as closed_down.
Your initial config/routes.rb setup would look something like this:
ActionController::Routing::Routes.draw do |map|
map.resources :venues
end
To list all venues that are closed down you would first need to add the following action to your venue_controller.rb:
def closed
@venues = Venue.find(:all, :conditions => ['closed_down = ?', true])
end
However, trying to navigate to this action will result in a routing error. To fix this, you will need to amend routes.rb:
ActionController::Routing::Routes.draw do |map|
map.resources :venues, :collection => {:closed => :get}
end
The collection hash is used to specify actions which reference collections of objects instead of actions which reference one object. In this case the closed action is referencing a collection of venues. Inside the hash :closed is mapped to a GET request. This will also automatically create a named route: closed_venues_path. Cool!
So that’s working. Now to be able to mark a venue as closed_down.
In your venues_controller.rb you would add the following action:
def mark_closed_down @venue = Venue.find(params[:id]) @venue.update_attribute :closed_down, true redirect_to closed_venues_path end
However, trying to navigate to this action will currently throw an error, as following the current RESTful setup the application will try to render the show action, which will try to find a venue with id => ‘mark_closed_down’.
To fix this you need to make the following change to your routes.rb file:
ActionController::Routing::Routes.draw do |map|
map.resources :venues, :collection => {:closed => :get}, :member => { :mark_closed_down => :put }
end
The member hash is used to specify actions that reference one object in particular. In this case mark_closed_down is referencing one venue. Inside the hash :mark_closed_down is mapped to a PUT request. This also gives you a named route: mark_closed_down_venue_path(venue). Thats it!
You can add as many custom routes as you like to each controller, but you should do so with caution. If you find yourself needing lots of them it probably means that you have missed a model, or that you should create another controller. Each controller maps one to one with a resource, but not necessarily with a model. In this case, for example, it would be possible to map another resource in routes.rb maybe called closed_down_venues:
map.resources :closed_down_venues
Then create a closed_down_venues controller with:
script/generate controller closed_down_venues
The index action would then be the list of closed venues, and the update action would be the mark_closed_down action.
I hope this is all clear. Please add a comment if not, and I will do my best to clarify…
Asshole driven development!
This link was sent around the office a couple of days ago, and I just couldn’t resist posting it. Very amusing. I’ve definitely witnessed a some of these methodologies in practice… http://www.scottberkun.com/blog/2007/asshole-driven-development/
acts_as_emailable
Quite often when building a site, I find that it would be nice if users could email each other within the site. In my previous post I explained how to set up the models and relationships for this. Today I’ve gone one step further and packaged them up into a plugin: acts_as_emailable. Here is how to use it:
Setup
=====
First up you need to install the plugin. This can be done like so:
script/plugin install svn://matt-beedle.com:3396/acts_as_emailable
Then generate the model and migration for the emails table.
script/generate acts_as_emailable_model Email
rake db:migrate
Now you are ready to go. Just add the following line of code to your User model:
class User < ActiveRecord::Base
acts_as_emailable
#
# The rest of you code
end
Usage
=====
After the plugin is installed the following methods become available to your User class.
These methods are the associations. They return what you would expect:
user.users_whom_i_have_emailed
user.users_who_have_emailed_me
At the moment there is only bare-bones functionality, but if any interest is shown or I feel the need, I will build in some more useful methods and make it more complete…
UPDATE
I am really pleased with all the interest and feedback I have received, especially from egze who basically did all the work for these changes. I have now added in three new functions with two aliases as I couldn’t decide on the which I preferred:
These two are really the same, they both return unread mail, although you need to make sure you update the ‘read_at’ field in the emails table when an email is read by the receiver.
user.unread_mailoruser.new_mail
These two return all read emails, although I’m not sure if there will be much use for it.
user.read_mailoruser.old_mail
Finally this returns all users who have emailed or been emailed by the user.
user.all_mail
I have a couple more ideas for new functions but suggestions are welcome. I was also intending to write some example code but haven’t had the chance. I’ll try to do that this week.
UPDATE: EXAMPLE CODE
In a REST based application the code may look something like this to send an email. The current_user that is referred to in the create method of the emails controller can be whatever way you get the current user. In my case I usually use Rick Olson’s Restful Authentication plugin which does this for me.
app/views/emails/new.rhtml
<% form_for :email, @email, :url => emails_path(:user_id => params[:user_id]) do |f| %>
<%= render :partial => ‘form’, :locals => {:f => f} %>
<%= submit_tag 'Send' %>
<% end %>
app/views/emails/_form.rhtml
<fieldset>
<legend>Email</legend>
<%= f.hidden_field :receiver_id %>
<div>
<label for="subject">Subject</label><br/>
<%= f.text_field :subject %>
</div>
<div>
<label for="body">Message</label><br/>
<%= f.text_area :body %>
</div>
</fieldset>
emails_controller.rb
def index
@emails = User.find(params[:user_id]).all_mail
end
def new
@email = Email.new
end
def create
current_user.send_mail(User.find(params[:user_id]), Email.new(params[:email]))
respond_to do |format|
format.html do
flash[:notice] = 'Email Sent'
redirect_to emails_path
false;
end
end
rescue ActiveRecord::RecordInvalid
render :action => 'new'
end
How to model an internal emailing system using self-referential has_many :through associations
I am currently working on a flat sharing site. One of the requirements is that users should be able to email each other within the site, a sort of internal emailing system. When one user emails another user that user has an email sent to their actual email address saying that they have an email waiting for them on the site. The intention being to keep them locked in to the site for as long as possible, and keep those adsense clicks coming in, and to provide a useful service of course! In order to do this a user model needs to be able to get all users who have emailed them and also carry through associated email details.
Anyway, here is the migration and model code to do this:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column 'username', :string
t.column 'forename', :string
t.column 'surname', :string
t.column 'email', :string
end
end
def self.down
drop_table :users
end
end
class CreateEmails < ActiveRecord::Migration
def self.up
create_table :emails do |t|
t.column 'sender_id', :integer, :null => false
t.column ‘receiver_id’, :integer, :null => false
t.column ’subject’, :string
t.column ‘body’, :text
t.column ‘created_at’, :datetime
end
add_index(’emails’, ’sender_id’)
add_index(’emails’, ‘receiver_id’)
end
def self.down
drop_table :emails
end
end
The important thing to notice in the User model below is that the “users_who_emailed_me” goes through “emails_as_receiver” with :source => :sender and “users_whom_i_have_emailed” goes through “emails_as_sender” with :source => :receiver. It took me a while to figure that out.
class User < ActiveRecord::Base
has_many :emails_as_sender,
:foreign_key => ’sender_id’,
:class_name => ‘Email’
has_many :emails_as_receiver,
:foreign_key => ‘receiver_id’,
:class_name => ‘Email’
has_many :users_who_emailed_me,
:through => :emails_as_receiver,
:source => :sender
has_many :users_whom_i_have_emailed,
:through => :emails_as_sender,
:source => :receiver
end
class Email < ActiveRecord::Base
belongs_to :sender,
:foreign_key => ’sender_id’,
:class_name => ‘User’
belongs_to :receiver,
:foreign_key => ‘receiver_id’,
:class_name => ‘User’
end
I adapted this code from an excellent article on the excellent blog of Josh Susser which you can find here. If fact this is pretty much the same. However, after reading his post I did not notice the way a couple of the joins worked, which I have tried to point out here using possibly a more relevant example.
UPDATE:
I have now packaged all of this code up into a plugin. For install instructions read this post.
Refactor your controller code by using postbacks
It is very easy to end up with an action for every single request. You may find you generate a controller like this for example:
./script/generate controller User create_form, create, edit_form, edit
It is not necessary to have 4 actions here. Instead this can all be handled by the same action:
def update
@user = User.find_by_id(params[:id]) || User.new
if request.post?
@user.attributes = params[:user]
if @user.save
redirect_to :action => 'index'
return
end
end
end
On first calling the action there is no data, and the template with your form is displayed. Once the form is submitted to the same action, the user object is saved and the controller redirects to the home page (or the page of your choice).
Using ActiveRecord to store session information in Ruby on Rails
Rails default setting is to store sessions on the file system. This is fine during development, but this is not very scalable once an application goes into production. Often the application will be deployed to a number of servers. One good solution to is to use ActiveRecord to store the session information in the database. Fortunately this is very easy to do!
First of, open the config/environments.rb file and uncomment the following line:
config.action_controller.session_store = :active_record_store
Now we create the sessions table. There is already a rake task for doing this:
rake db:sessions:create
rake db:migrate
Now restart your server and voila! Rails does not automatically manage sessions so it is worth noting that you will need to deal with this. If you do not you could end up with a sessions table with millions of rows. This will slow your application down considerably. I will post more information soon on sweepers and dealing with session expiry.
Creating secure login code in Ruby on Rails
Many web sites require secure user authentication, but a shocking number of them implement it very badly. At the time of posting one of the best ways of dealing with this problem not to store the password in the database, but instead to save a “salt” and a “hash”. The salt is a set of 6 random characters which when combined with the users password and encoded, is equal to the hash. Before going any further, you should be aware that there are plugins for rails which deal with salted logins (http://wiki.rubyonrails.org/rails/pages/SaltedHashLoginGenerator), but I believe it is best to be able to write the code yourself, or at least to have a basic understanding.
user migration
t.column :username, :string t.column :password_salt, :string t.column :password_hash, :string
app/models/user.rb:
require 'digest/sha2'
class User < ActiveRecord::Base
attr_accessor :password
def password=(pass)
salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp
self.password_salt, self.password_hash = salt, Digest::SHA256.hexdigest(pass + salt)
end
def self.authenticate(username, password)
user = User.find(:first, :conditions => [”username = ?”, username])
if user.blank? || Digest::SHA256.hexdigest(password + user.password_salt) != user.password_hash
raise “Username or password invalid”
end
user
end
end
application_controller.rb
def check_authentication
if session[:user].blank?
session[:intended_controller] = controller_name
session[:intended_action] = action_name
redirect_to :controller => 'user', :action => 'login'
end
end
app/controllers/user_controller.rb:
def login
if request.post?
begin
session[:user] = User.authenticate(params[:username],params[:password]).id
redirect_to :controller => session[:intended_controller], :action => session[:intended_action]
rescue
flash[:notice] = "Username or password invalid"
end
end
end
def logout
session[:user] = nil
redirect_to :controller => :user, :action => :index
end
def register
if request.post?
@user = User.new(params[:user])
if @user.save
redirect_to :action => :account_creation_success, :id => @user
end
end
end
app/views/user/login.rhtml:
<%= flash[:notice] -%> <% form_tag :controller => :user, :action => :login do %> <fieldset><label for=”user_username”>Username</label> <%= text_field 'user', 'username' -%><label for=”user_password”>Password</label> <%= password_field 'user', 'password' -%></fieldset> <%= submit_tag 'Login' -%> <% end %>
app/views/user/register.rhtml:
<% form_tag :controller => :user, :action => :register do %> <fieldset><label for=”username”>Username>/label> <%= text_field_tag 'user', nil, 'username' -%><label for=”password”>Password</label> <%= password_field_tag 'user', nil, 'password' -%></fieldset> <%= submit_tag 'Register' -%> <% end %>
Now use a before_filter on any controllers where you need to login.
Non tech staff in a web company are useless dead weight
Sorry for the sensationalist title, but I thought it might grab your attention. I have found myself wondering a great deal recently, whether an internet company really needs so many non-tech staff as the ones I have worked in have had. It seems to me, that the majority of the work is done by web developers, database programmers and search engine experts. Is there really a need for an editorial team, an analytical team, a bunch of product managers and a whole advertising team? The tech staff are outnumbered by non-tech, but produce almost all of the work.
I understand that this is a very black and white way to view things. I know that there is a need to sell advertising on a site to keep revenue coming in. I know that someone needs to provide fresh content to keep the search engines happy. But, surely it is possible to survive as a business with just one person selling advertising space, and just one person writing content. Instead you end up with a whole bunch of people all with different ideas, all trying to take the business in different directions, all fighting for the time of the developers. The developers then end up working on 10 different things at once, getting none of them done fast, or to the quality they could achieve if they were more focused. Which brings me to my next point.
Who should control the direction of the business, tech or editorial?
I’m pretty sure that most of the big successful web sites are run and controlled by IT staff. Google, Yahoo, Digg, Reddit, Plentyoffish (1 guy), and many more. Non-tech staff are ill-equipped to make decisions as they are usually unaware of the possibilities and/or limitations of diffferent languages and technologies. I’m not saying they should not have a say at all. Everyone should be able to come up with ideas, but the nitty-gritty should be handled by IT. Advertising should stick to selling advertising, project management should stick to managing projects, and editorial should be primarily writing content to put on the site.