Monday, June 28, 2010

Ruby: Additional Attributes in ActiveRecord

I'm a bit surprised that this returns a string instead of an int:
Book.find_by_sql("select name, 5 as some_number from books").first.some_number
My buddy Shailen was the one who pointed this out to me.

Monday, June 21, 2010

Ruby: rails_xss

There's a new plugin called rails_xss:
This plugin replaces the default ERB template handlers with erubis, and switches the behaviour to escape by default rather than requiring you to escape. This is consistent with the behaviour in Rails 3.0.

Strings now have a notion of "html safe", which is false by default. Whenever rails copies a string into the response body it checks whether or not the string is safe, safe strings are copied verbatim into the response body, but unsafe strings are escaped first.
This is a nice feature that I used to like in other templating engines, so I'm glad Rails now has it as well. I just updated my code to use it, and things went relatively smoothly. I deleted all the useless calls to "h()" and added "raw" or "html_safe" in all the right places. Thank goodness for tests ;)

Saturday, June 12, 2010

A Few Simple Ruby, Rails, and MySQL Tips

Sometimes it makes sense to store the value of a query in a variable within MySQL. You might do this if you're avoiding ActiveRecord because you're in a data migration. Here's how:
    execute %{SELECT (@setuid_id:=id) FROM roles WHERE title = 'setuid'};
execute %{SELECT (@admin_id:=id) FROM roles WHERE title = 'admin'};
Similarly, sometimes it's helpful to use the results of a query in order to insert several rows at the same time. MySQL has INSERT...SELECT syntax that can be used like this:
    execute %{
INSERT INTO roles_users (role_id, user_id)
SELECT @setuid_id, user_id
FROM roles_users
WHERE role_id = @admin_id
}
The above query finds all the admins and gives them the setuid role as well.

If you're a Python coder coding in Ruby, don't forget that ["a", "b", "c"] is more simply written %w(a b c).

You probably know that you can lookup rows via ActiveRecord finder methods like Role.find_by_name(name). However, did you know that you can also find multiple records at the same time via Role.find_all_by_name(names_array)?

Remember that Role.find_by_name(name) will return nil if there is no such record, whereas Role.find_by_name!(name) will raise an exception if there is no such record. Unfortunately, there is no "!" version of Role.find_all_by_name. Hence, if you need to make sure that every name resulted in a record, you'll have to look at the number of records returned.

Thursday, June 10, 2010

IDEs: Netbeans

As I've mentioned before, I use Netbeans with the jVi plugin. It's enough IDE to improve my productivity without being so much that I feel overwhelmed. Using the jVi plugin makes a Vim fanatic like me feel right at home.

Just in case it's helpful, here's my NetBeans setup (in outline form):
Setup NetBeans:
Downloaded the Ruby pack.
It's a shell script. Run it as root. Don't install GlassFish.
It installs to /usr/local/netbeans-6.8.
To uninstall: /usr/local/netbeans-6.8/uninstall.sh
There is a menu item to run it.
After install:
Installed the Python plugin:
Jython distribution.
Installed the PHP plugin.
Installed the Cucumber Features plugin.
Installed the nbgit plugin by downloading it manually.
Installed the jVi plugin by downloading it manually.
Setup jVi:
Tools >> Options:
JVi:
Buffer Modifications:
bs: 2
sts: 2
sw: 2
et
Control-Key Bindings:
Click Control-V to enable rectangle edit.
Unclick Control-W so I can close windows quickly.
Set the default font to Monospace 12:
Make sure you're not setting the whitespace category.

Ruby: Using reset_session in Rails with Cucumber and Webrat

I filed the following bug:
All the Rails security guides say that you should call reset_session after the user logs in or logs out. This clears out the session and forces a new session ID to be created. It seems there have been a few Rails bugs related to reset_session over the years. However, I'm now worried that there's a conflict somewhere related to Rails' testing infrastructure and/or Webrat.

In my login action, I call reset_session and then put a nice message in flash. When I actually use the website, I can see (via Firefox) that I'm getting a new session ID, and I can see my flash message. However, when I write tests for those two things, the flash message gets lost, and I don't get a new session ID in my cookies. It's almost as if the new session is being ignored, and the old session is being used.

I'm sorry, but I can't tell exactly where the problem is. I know that there's special Rails code that handles session cookies when you're doing integration testing.

I'm using actionpack-2.3.8 and webrat-0.7.1.
Here's how I did my best to work around the problem.

First of all, I'm storing my sessions in the database. Hence, I created an ActiveRecord model to manually delete session records:
class Session < ActiveRecord::Base
# Rails' reset_session has been nothing but trouble for us. I'm taking
# matters into my own hands. This code will have to change if we stop
# putting our sessions in the database.
def self.nuke_session(session_id)
find_by_session_id!(session_id).destroy
end
end
Since I'm using authlogic, my logout action looks something like this:
def destroy
current_user_session.destroy

# Avoid session fixation attacks. This may seem redundant, but it was
# necessary to make the tests pass.
session[:test_that_this_disappears] = 'ok'
session_id = cookies[ActionController::Base.session_options[:key]]
Session.nuke_session(session_id)
reset_session

redirect_to :action => :logged_out
end
My login action is something like this:
def create
@user_session = UserSession.new(params[:user_session])
unless @user_session.valid?
return render :action => :new
end

# Avoid session fixation attacks by resetting the session.
reset_session
@user_session = UserSession.new(params[:user_session])
@user_session.save!

# For some reason, flash, reset_session, Cucumber, etc. don't get along,
# so I have to pass the flash message via a parameter.
redirect_to(root_url(:login => 1))
end
Instead of setting a message via flash, I pass a query parameter.

I check for this parameter in my ApplicationController:
before_filter :check_for_login_message
...
# For some reason, flash, reset_session, Cucumber, etc. don't get along.
# Hence, after login, we have to pass the flash message via a query parameter
# rather than via flash.
#
# If I have to do this kind of thing again, I'll create a lookup table full
# of messages.
def check_for_login_message
flash.now[:success_message] = "Login successful!" if params[:login]
end
Finally, I have this in a feature file:
# This test should work.  In fact, it does work when you use your browser.
# However, there's a bug somewhere between Rails and Webrat that prevents
# it from working when you use Cucumber. Somehow, reset_session is broken.
#
# Scenario: logging in should invalidate your session cookie
# Given I am on the homepage
# When I look at my session cookie
# And I am logged in
# Then I should have a different session cookie
That test relies on these steps:
# This test should work.  In fact, it does work when you use your browser.
# However, there's a bug somewhere between Rails and Webrat that prevents
# it from working when you use Cucumber. Somehow, reset_session is broken.
#
# Given /^I look at my session cookie$/ do
# @cookie = cookies[ActionController::Base.session_options[:key]]
# end
#
# Then /^I should have a different session cookie$/ do
# new_cookie = cookies[ActionController::Base.session_options[:key]]
# new_cookie.should_not == @cookie
# end

Tuesday, June 01, 2010

Linux: Qimo for Kids

I just installed Qimo, an Ubuntu-based distribution for kids. If you already have Ubuntu, just run "apt-get install qimo-session".

I just started poking around, but I like it already. I had Edubuntu previously. For some reason, this seems simpler, neater, easier, and more engaging. I think Edubuntu has everything Qimo has, and more, but Qimo presents it in a more compelling manner.

By the way, Qimo doesn't provide parental controls, so you'll have to install something extra if you're into that sort of thing.