Archive for September, 2009
Authlogic: after the initial hype
On my rails projects, I’ve used restful_authentication before and I am using authlogic now. Even though I have passed the initial hesitation phase with authlogic, I can’t say that I am totally sold.
What I like about authlogic is it refrains from providing any controller/view level support and handles model layer better. Instead, it provides a solid model functionality that is similar in principle to ActiveRecord and provides lots of how-to examples on how you might code your controllers/views/workflows. It does it well. Although authlogic is complemented for refraining from too much code generation, Authlogic still does a lot of magical stuff. You encounter this readily with tests. Instantiating and persisting a user will log you in! It is hard to test your User model from console since authlogic will fail if you try to instantiate User object in console. You have to jump thru hoops (include Authlogic, set proper controller reference) to get it to work.
Problem with Perishable Token:
Today I encountered another issue with authlogic realted to perishable token. According to docs, perishable tokens are useful for implementing features such as reset-password and confirm email. These functionalities are essentially similar in implementation in that application generates a token and emails url containing this token to the user’s registered email address. User clicks on the url and resets his password or confirms that email is indeed his email. The implementation details are explained very well by author here.
However, it didn’t work in my case. The problem, I believe, is that perishable_token changes often… too often actually. Looking side the codebase for Authlogic::ActsAsAuthentic::PerishableToken reveals this:
def self.included(klass) return if !klass.column_names.include?("perishable_token") klass.class_eval do extend ClassMethods include InstanceMethods before_save :reset_perishable_token, :unless => :disable_perishable_token_maintenance? end end def reset_perishable_token self.perishable_token = Random.friendly_token end
As you can see, the token changes on each save. This can be readily verified from console:
>> u = User.find_by_email("sjain@iit.edu") #=> #<User id: 1, email: "sjain@iit.edu", perishable_token: "6qLHkU_vBQK6rLQGy-oL", login_count: 54, failed_login_count: 0, last_request_at: "2009-09-30 18:00:46", current_login_at: "2009-09-30 18:00:42", last_login_at: "2009-09-30 18:00:42", current_login_ip: "127.0.0.1", last_login_ip: "127.0.0.1"...> >> u.perishable_token #=> "6qLHkU_vBQK6rLQGy-oL" >> u.save #=> true >> u.perishable_token => "1tlcpvc_sCGtNEfIQCt1" >>
Everytime the user object is saved, the token changes. In a running app this could happen for any number of reasons. I wonder, if having other authlogic magic attributes like login_count, last_request_at etc. causes the instance to get saved immediately after the token was sent in email, practically overwriting the token that was critical to password-reset workflow…
Solution:
Fortunately, Authlogic allows you to take control and manage token yourself. To do this, you disable the automatic maintenance of perishable_token by authlogic as follows:
class User < ActiveRecord::Base acts_as_authentic disable_perishable_token_maintenance(true) end
By doing this, authlogic leaves this column in database alone. You can now reset the token when you need it. Here is what my password-reset and confirm-email hooks look like then:
class User < ActiveRecord::Base def deliver_confirm_email_instructions! reset_perishable_token! Notifier.deliver_confirm_email_instructions(self) end def deliver_password_reset_instructions! reset_perishable_token! Notifier.deliver_password_reset_instructions(self) end end
In addition, since we took the control of perishable_token in our hands and that it is a non-nullable field in database, we have to reset the token when a new user registers for an account on our website.
class UsersController < ApplicationController def create @user = User.new(params[:user]) # set the token manually @user.reset_perishable_token if @user.save @user.deliver_confirm_email_instructions! flash[:notice] = "Registration successful. Please check your email to confirm your email address." redirect_to root_url else render :action => 'new' end end end
Conclusion:
All in all, authlogic is a well designed authentication library. However, you can’t get around the fact that authentication and its surrounding sub-features (password reset, registration, email confirmation) can get complex. When coming away from restful_authentication to authlogic you will be glad to find intuitive for its API that is very similar to ActiveRecord, configurablility to enable/disable features that you need and don’t need. But, don’t expect an easy ride. You are bound to have to dive into the codebase to take advantage of some of the complex features.
Setting up Sphinx Search on remote server
If you are using sphinx gem for search functionality in your rails application, here is a quick rundown of commands that you will need to install and setup sphinx server and rails plugin on remote server.
Download and installl Sphinx package:
# download from http://sphinxsearch.com/downloads.html $ tar zxf <tar.gz file> $ ./configure $ make $ sudo make install
The rails app should already list gem dependency. Install the necessary gem on system:
# environment.rb: config.gem 'freelancing-god-thinking-sphinx', :lib => 'thinking_sphinx' $ sudo gem install freelancing-god-thinking-sphinx
Here are 2 rake commands to index the database for sphinx search. And to start the sphinx daemon when the app is running.
$ rake thinking_sphinx:index (have a cron job for this) $ rake thinking_sphinx:start (start the daemon)
There is also the capitate gem that makes managing sphinx on remote server easier.
Rails monkeypatching to pin-point offending code
Monkey-patching is generally a bad thing but it can help you pin point offending code. I ran into a situation today where I was getting logger to be nil inside ActiveRecord model class. Normally, every ActiveRecord object has reference to logger for logging purposes. This logger is ideally never nil.
I knew, this must be a code somewhere in my codebase that must be setting it to nil. The trouble with rails’ dynamic nature is you can’t find reference to a method call in your IDE. You are left with brute-force find/grep … or monkey patching.
I was able to pin-point offending code using following code:
# this can go in environment.rb temporarily module ActiveRecord class Base class << self def logger_with_nil_check=(new_logger) raise "logger cannot be set to nil" if !new_logger logger_without_nil_check end alias_method_chain :logger=, :nil_check end end end
Isn’t this what they call “developer freedom”.
Getting session_id in rails
If you are using database sessions, rails sessions table stores session information in “sessions” table that contains two columns: “session_id” and “data”.
Now, if you need to know the session_id of current session your first instinct would be to do the following:
session.session_idThis will not work since session is just hash containing name/value pairs and not an ActiveRecord object. So what do we do introspect session’s own information.
Solution: We use “request.session_options”. It is a hash that contains session_id and a few more options related to session.
request.session_options.inspect ={:secure=>false, :secret=>"ZBYULTOIJYLYWVNONXNSWUJCKMVVFPIZCEEGDAIE", :expire_after=>nil, :key=>"_SOMEGT_session", :id=>"4d4999d4650f4b21121c8f2b65917fac", :cookie_only=>true, :httponly=>true, :path=>"/", :domain=>nil}
Here :id is the session_id and :key is the cookie name under which it stores the session_id.
Now I know.
Working with Authlogic in script/console
When probing / debugging issues with Authlogic on rails console, you want to be able to create UserSessions and authenticate just like a live app does. However, you may run into issues if you plainly try to instantiate and save UserSession objects in console.
>> UserSession.new(:email => "validemail@domain.com", :password => "password") Authlogic::Session::Activation::NotActivatedError: You must activate the Authlogic::Session::Base.controller with a controller object before creating objects from /var/lib/gems/1.8/gems/authlogic-2.1.1/lib/authlogic/session/activation.rb:47:in `initialize' from /var/lib/gems/1.8/gems/authlogic-2.1.1/lib/authlogic/session/klass.rb:61:in `initialize' from /var/lib/gems/1.8/gems/authlogic-2.1.1/lib/authlogic/session/scopes.rb:79:in `initialize' from (irb):2:in `new' from (irb):2
Authlogic uses framework adapters (RailsAdapter or MerbAdapter) to extract information such as session/cookies when working on authentication stuff. In order to enable this same mechanism for rails console, you need to do the following inside console:
>> Authlogic::Session::Base.controller = Authlogic::ControllerAdapters::RailsAdapter.new(self)
After this, you should be able to create UserSession objects like you normally would:
>> us = UserSession.new(:email => "validemail@domain.com", :password => "password") => #<UserSession: {:password="<protected", :email="validemail@domain.com"}> >> us.save! User Load (0.6ms) SELECT * FROM `users` WHERE (LOWER(`users`.email) = 'validemail@domain.com') LIMIT 1 SQL (0.2ms) BEGIN User Update (0.4ms) UPDATE `users` SET `failed_login_count` = 2, `updated_at` = '2009-09-25 02:31:57', `perishable_token` = 'bP_s1_z9gwCFmrIX9cdV' WHERE `id` = 1 SQL (1706.2ms) COMMIT Authlogic::Session::Existence::SessionInvalidError: Your session is invalid and has the following errors: Password is not valid from /var/lib/gems/1.8/gems/authlogic-2.1.1/lib/authlogic/session/existence.rb:85:in `save!' from (irb):11
How to update current system time on Unix/Linux
$ sudo date --set='Thu Sep 24 16:00:30 CDT 2009'
Reverse Tunneling and mysql
Recently, I needed my app in staging to use my local database. Since staging server was on a different network that didn’t have access to my local machine (DMZ), I had to use ssh tunnelling. Here is how I did it:
I setup reverse tunnel that allowed mysql connection from staging server to my local database:
ssh -R 33306:localhost:3306 staging_server
This setup reverse ssh tunnel that allowed anybody trying to connecting to msyql server on staging_server at port 33306 to be routed to mysql server running on my local machine on default port (3306).
With this, I was able to do following on remote server:
mysql -h 127.0.0.1 -P 33306 -u root -p
This comment is trying to connect to mysql on port 33306 on localhost which is getting tunneled to mysql server on my local machine. In the above command, “-h 127.0.0.1″ is required since otherwise mysql will try to connect to “localhost” using /tmp/mysql.sock (socket connection). Also, firewall on staging_server prevented any incoming connection for port 33306, so I couldn’t use “-h 192.168.84.70″ which was the real IP of staging server. Instead, I had to use 127.0.0.1.
Nothing earth-shaking, just a few quirks when getting reverse tunnelling to work.
Respect the Pomodoro
An article on agile pair programming on NY Times, it is nice to see what is a routine thing in many of software development thru the eyes of somebody who is newly excited about it. It brings the fresh pair of critique and any reviews are expected to be positive.
Also, learnt about a 5 minute break every 25 minutes… called “Respect the Pomodoro” (Pomodoro == Tomato, in Italian).
Defining custom roles/servers in capistrano
Capistrano works seamlessly for standard deployment where your app is deployed on at most 3 servers, identified as roles: app, web, db. In most cases, these usually point to the same machine where everything is deployed.
set :deploy_host, "192.168.84.70" role :app, deploy_host role :web, deploy_host role :db, deploy_host, :primary => true
On a recent project, I needed to do some setup on a 4th machine. And so I added a role
set :deploy_host, "192.168.84.70" role :app, deploy_host role :web, deploy_host role :db, deploy_host, :primary => true role :asterisk, "asterisk.domain.com"
Soon, I noticed that default rails tasks such as update_code, symlink started attempting to deploy to this new role. I wondered why those tasks weren’t setup to run only on “app” role. There had to be a solution since I am not the first one who would have needed to define a custom role.
A little digging inside capistrano code revealed
nly and :except options. Most of these default tasks skipped any roles that were defined with option { :no_release => true }.
# <capistrano_gem>/lib/capistrano/recipes/deploy.rb task :update_code, :except => { :no_release => true } do end task :symlink, :except => { :no_release => true } do end
So, I had to change my role definition to:
role :asterisk, "asterisknow.pathf.com", :no_release => true
Googling a bit revealed that this was introduced in v1.1.9, as described by the author here.
Note to self: Always pay attention to release notes!
[My capistrano version is v2.5.8.]
Rails: Optimizing Database Indexes using query_analyzer and query_reviewer
When trying to identify various database query indexes (indices?) for optimizing databases, two plugins are useful.
Query Analyzer is very useful and has been around for a quite some time. It runs an EXPLAIN on each query sent to database and prints tabular output on console.
Query Reviewer is a new one that I used recently. It is very similar to Query Analyzer in functionality. However, it displays the result rendered on top of you existing page using absolutely positioned divs. It is well designed to be not very intrusive and can be enabled/disabled.
Both plugins require just installing them in vendor/plugins folders. That’s it.
Warning: Both these plugins work with mysql database only, since they depend on mysql’s EXPLAIN command.