Yesterday one of the adminitrators of ux.etao.com, which is a
website developed with Ruby on Rails, reported a bug saying that cannot
change the categorization of some posts. The cause of that bug was obvious.
There are several types of posts, such as Post, Work, Brick and so on.
The category part share the polymophyism. There are PostCategory and BrickCategory.
These two category models both has_many :posts
.
class PostCategory < Category
has_many :posts, :foreign_key => :category_id
end
class BrickCategory < Category
has_many :posts, :class_name => 'Brick', :foreign_key => :category_id
end
BrickCategory should has_many :bricks
. But to cut long story short, it
has_many :posts
for brevity in views.
And back to the bug. The cause is that the category should be an instance of BrickCategory but it appeared to be PostCategory instead. So it was searching wrong type of `posts’. We can simply run this to rectify data:
# c.channel.brick? determines whether or not this category should be BrickCategory
# We can update the type column with #update_column
Category.find_each do |c|
c.update_column(:type, 'BrickCategory') if c.channel.brick?
end
Now let’s fix the controller that wronged those poor categories at the first place.
First things first, we shall enrich our functional test. The controller is
app/controllers/categories_controller.rb
. Hence the test file should be
test/functional/categories_controller_test.rb
.
class CategoriesControllerTest < ActionController::TestCase
test "brick category creation" do
post :create, :category => {
:name => '我是个小类目',
:channel_id => channels(:bricks).id
}
ret = JSON.parse @response.body
assert_not_nil ret['id']
category = Category.find(ret['id'])
assert_equal category.channel_id, channels(:bricks).id
assert category.channel.brick_channel?
assert_instance_of BrickCategory, category
end
end
Let’s fire it up:
$ ruby -Itest test/functional/categories_controller_test.rb
The first time I run this command, it returned nothing but a line saying that there was an error. Where is my backtrace? After an hour of googling and frustration, I figured out finnaly.
It’s because of some stupid gem. I curse you gem 'turn', '0.8.2'
.
Just remove the 0.8.2
part, run bundle update turn
.
Now the backtrace is back, pun not intended. The JSON.parse @response.body
failed.
The value of @response.body
looks like this:
<html><body>You are being <a href="http://test.host/login">redirected</a>.</body></html>
That’s because of cancan. To create some category, one need to login first and
he should be able to perform that action. That said, can? :create, Category.new # ==> true
.
This project uses authlogic. In controllers, to login programmaticly,
we just do UserSession.create(some_user)
then everything is good to go.
But in the test environment things is a little bit complicated than that.
The author of authlogic documented this.
First, include this line at the top of your test_helper.rb
which should be found
right in the test
folder.
require "authlogic/test_case" # include at the top of test_helper.rb
Next, we need to activate_authlogic
in the #setup method.
Put this in your test file, in this case the test/functional/categories_controller_test.rb
.
def setup
activate_authlogic
end
Read the rails guide if you want more about the #setup method in testing.
But to keep things tidy. You can just put this line at the bottom of test_helper.rb
.
setup :activate_authlogic # run before tests are executed
Then you can login any user defined in the test/fixtures/users.yml
now:
UserSession.create(users(:whomever)) # logs a user in
There’s still a gotcha. Authlogic uses the persistence_token
attribute of the User model
to login that user. It puts that value into session[:user_credentials]
and uses that
to fetch the logged in user.
So to make it work. You should fill that column in the users fixture too.
john:
email: john.doe@foobar.com
roles: admin
password_salt: <%= salt = Authlogic::Random.hex_token %>
crypted_password: <%= Authlogic::CryptoProviders::Sha512.encrypt("doe" + salt) %>
persistence_token: <%= Authlogic::Random.hex_token %>
perishable_token: <%= Authlogic::Random.friendly_token %>
The functional test is complete finnaly. But your rails odyssey does not end here. Sail on my friend.