Blog
what did i learn today
rspec2 using watchr instead of autotest
I was having troubles with autotest, and not really finding a good solution. But i noticed that inside rspec2 they were using watchr. Watchr is a very generic gem that will allow you to watch a set of files and take action when something changes. That sounds great, but maybe a bit too general :) So, it appears you need a script to run your rspec2 continuously using watchr. Luckily i did find some examples, which allowed my to brew my own (save in your rails-root as specs.watchr): [ruby wraplines="false"] # adapted from http://github.com/rspec/rspec-rails/blob/master/specs.watchr # Run me with: # # $ watchr specs.watchr # -------------------------------------------------- # Convenience Methods # -------------------------------------------------- def all_spec_files Dir['spec/**/*_spec.rb'] end def run_spec_matching(thing_to_match) matches = all_spec_files.grep(/#{thing_to_match}/i) if matches.empty? puts "Sorry, thanks for playing, but there were no matches for #{thing_to_match}" else run matches.join(' ') end end def run(files_to_run) puts("Running: #{files_to_run}") system("rspec -c #{files_to_run}") no_int_for_you end def run_all_specs run(all_spec_files.join(' ')) end # -------------------------------------------------- # Watchr Rules # -------------------------------------------------- watch('^spec/(.*)_spec\.rb') { |m| run_spec_matching(m[1]) } watch('^app/(.*)\.rb') { |m| run_spec_matching(m[1]) } watch('^app/(.*)\.haml') { |m| run_spec_matching(m[1]) } watch('^lib/(.*)\.rb') { |m| run_spec_matching(m[1]) } watch('^spec/spec_helper\.rb') { run_all_specs } watch('^spec/support/.*\.rb') { run_all_specs } # -------------------------------------------------- # Signal Handling # -------------------------------------------------- def no_int_for_you @sent_an_int = nil end Signal.trap 'INT' do if @sent_an_int then puts " A second INT? Ok, I get the message. Shutting down now." exit else puts " Did you just send me an INT? Ugh. I'll quit for real if you do it again." @sent_an_int = true Kernel.sleep 1.5 run_all_specs end end [/ruby] It even seems to be quicker than autotest, but not sure if that is just wishful thinking.
authlogic on rails3
If you want to get authlogic working in a fresh rails 3 project, it will take a bit more steps than devise. This has everything to do with vision: authlogic only claims to deliver you the backend, allowing itself to remain more stable and to easily replace other authentication libraries (even your home-brewn). That seems a great philosophy, but starting from scratch involves more steps. First you need to setup a clean rails3 project, as i described before.

Install the gem

As simple as adding the following line to your Gemfile : [ruby] gem "authlogic" gem "rails3-generators" [/ruby] (note: you need the rails3-generators to include the needed generators). Then run bundle install or bundle update.

Create the UserSession

[ruby] rails g authlogic:session UserSession [/ruby] This would be all, but in my installation it was not enough. Something inside rails3 broke the authlogic session. But the fix, luckily, is pretty easy: you have to add the to_key function. So your complete UserSession model will look as follows: [ruby] class UserSession < Authlogic::Session::Base def to_key new_record? ? nil : [ self.send(self.class.primary_key) ] end end [/ruby]

Create the User

If you do not yet have a User model, and i am assuming you don't, you need to [ruby] rails g model User [/ruby] For now, this is an empty model. You will need to fill in your migration to create the model correctly. [ruby wraplines="false"] class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :login, :null => false t.string :email, :null => false t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false #t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params #t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability # magic fields (all optional, see Authlogic::Session::MagicColumns) t.integer :login_count, :null => false, :default => 0 t.integer :failed_login_count, :null => false, :default => 0 t.datetime :last_request_at t.datetime :current_login_at t.datetime :last_login_at t.string :current_login_ip t.string :last_login_ip t.timestamps end add_index :users, ["login"], :name => "index_users_on_login", :unique => true add_index :users, ["email"], :name => "index_users_on_email", :unique => true add_index :users, ["persistence_token"], :name => "index_users_on_persistence_token", :unique => true end def self.down drop_table :users end end [/ruby] Now we still have to add some code to the User model : [ruby] class User < ActiveRecord::Base acts_as_authentic end [/ruby]

ApplicationController

We need to add some generic code to our applicationcontroller to persist the sessions, to check whether a user is required and do the correct redirects when needed. [ruby] class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user_session, :current_user private def current_user_session logger.debug "ApplicationController::current_user_session" return @current_user_session if defined?(@current_user_session) @current_user_session = UserSession.find end def current_user logger.debug "ApplicationController::current_user" return @current_user if defined?(@current_user) @current_user = current_user_session && current_user_session.user end def require_user logger.debug "ApplicationController::require_user" unless current_user store_location flash[:notice] = "You must be logged in to access this page" redirect_to new_user_session_url return false end end def require_no_user logger.debug "ApplicationController::require_no_user" if current_user store_location flash[:notice] = "You must be logged out to access this page" redirect_to account_url return false end end def store_location session[:return_to] = request.request_uri end def redirect_back_or_default(default) redirect_to(session[:return_to] || default) session[:return_to] = nil end end [/ruby]

UserSessionsController

[ruby] rails g controller UserSessions new [/ruby] and fill the controller with the correct code: [ruby] class UserSessionsController < ApplicationController before_filter :require_no_user, :only => [:new, :create] before_filter :require_user, :only => :destroy def new @user_session = UserSession.new end def create @user_session = UserSession.new(params[:user_session]) if @user_session.save flash[:notice] = "Login successful!" redirect_back_or_default users_url else render :action => :new end end def destroy current_user_session.destroy flash[:notice] = "Logout successful!" redirect_back_or_default new_user_session_url end end [/ruby] and of course that also needs a view, in new.html.haml [ruby] %h1 Login = form_for @user_session, :url => {:action => "create"} do |f| = f.error_messages %div = f.label :login = f.text_field :login %div = f.label :password = f.password_field :password %div = f.check_box :remember_me = f.label :remember_me %div = f.submit "Login" [/ruby] We want to use the f.error_message, but that is now removed from Rails3 and we need to install a plugin instead: [ruby] rails plugin install git://github.com/rails/dynamic_form.git [/ruby] We also need to define, inside config/routes.rb. You will see that the generator will have added the route get 'user_sessions/new', but that is not enough. You will have to add: [ruby] resources :user_sessions match 'login' => "user_sessions#new", :as => :login match 'logout' => "user_sessions#destroy", :as => :logout [/ruby]

Restrict access

Suppose you now have some other controller, e.g. HomeController, then restricting acces is straightforward: [ruby] rails g controller home index [/ruby] [ruby] class HomeController < ApplicationController before_filter :require_user def index end end [/ruby] The method require_user, defined in ApplicationController, will check if there is a user logged on, and if not redirect to the login-page. If we now delete the index.html inside your public folder, and we add the following at the bottom of config/routes.rb [ruby] root :to => 'home#index' [/ruby] Now we can start our application, and it should redirect to our login-page. But of course, since we have no users for now, we can't login just yet. To allow testing, you fire up your console: [bash] $ rails c Loading development environment (Rails 3.0.0.rc) ruby-1.9.2-p0 > User.create(:login => 'test', :email => 'test@tester.com', :password => 'test123', :password_confirmation => 'test123') => #<User id: 1, login: "test", email: "test@tester.com", crypted_password: "0129d9733b7912017e37a50263901488da90e127e6fd1ae6081...", password_salt: "ygufdflWkJQZGhbsGyia", persistence_token: "957788609b2067092dd3852b01613d53f96d0692ce5131174ca...", login_count: 0, failed_login_count: 0, last_request_at: nil, current_login_at: nil, last_login_at: nil, current_login_ip: nil, last_login_ip: nil, created_at: "2010-08-29 14:11:04", updated_at: "2010-08-29 14:11:04"> ruby-1.9.2-p0 > [/bash] Now you should be able to login using this user. For completeness, you should add some links to your application-view to allow logging in and out: [ruby] #user_nav - if current_user = "Signed in as #{current_user.email}. Not you?" = link_to "Sign out", logout_path - else = link_to "Sign in", new_user_session_path [/ruby] This would get your rails3 project started. The next steps would be to add some user management, or allowing users to sign up themselves, and maybe add some roles to limit certain users access if needed. Now, to contrast this with devise: i now have a bunch of code in my application that actually is not specific to my code, but is also completely not tested. I will provide example rspec tests for this later. Actually, the problem with this scenario as outlined, is it is one big bang. You should start little, declaring things you need to be able to do, and work in little steps to get there. But this scenario is intended as a draft to see how you could get there. Now, in your real application, you should start with the tests, and then from this example you now how you can implement it.If you want to get authlogic working in a fresh rails 3 project, it will take a bit more steps than devise. This has everything to do with vision: authlogic only claims to deliver you the backend, allowing itself to remain more stable and to easily replace other authentication libraries (even your home-brewn). That seems a great philosophy, but starting from scratch involves more steps. First you need to setup a clean rails3 project, as i described before.

Install the gem

As simple as adding the following line to your Gemfile : [ruby] gem "authlogic" [/ruby] and then run bundle install or bundle update.

Create the UserSession

[ruby] rails g authlogic:session UserSession [/ruby] This would be all, but in my installation it was not enough. Something inside rails3 broke the authlogic session. But the fix, luckily, is pretty easy: you have to add the to_key function. So your complete UserSession model will look as follows: [ruby] class UserSession < Authlogic::Session::Base def to_key new_record? ? nil : [ self.send(self.class.primary_key) ] end end [/ruby]

Create the User

If you do not yet have a User model, and i am assuming you don't, you need to [ruby] rails g model User [/ruby] For now, this is an empty model. You will need to fill in your migration to create the model correctly. [ruby wraplines="false"] class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :login, :null => false t.string :email, :null => false t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false #t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params #t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability # magic fields (all optional, see Authlogic::Session::MagicColumns) t.integer :login_count, :null => false, :default => 0 t.integer :failed_login_count, :null => false, :default => 0 t.datetime :last_request_at t.datetime :current_login_at t.datetime :last_login_at t.string :current_login_ip t.string :last_login_ip t.timestamps end add_index :users, ["login"], :name => "index_users_on_login", :unique => true add_index :users, ["email"], :name => "index_users_on_email", :unique => true add_index :users, ["persistence_token"], :name => "index_users_on_persistence_token", :unique => true end def self.down drop_table :users end end [/ruby] Now we still have to add some code to the User model : [ruby] class User < ActiveRecord::Base acts_as_authentic end [/ruby]

ApplicationController

We need to add some generic code to our applicationcontroller to persist the sessions, to check whether a user is required and do the correct redirects when needed. [ruby] class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user_session, :current_user private def current_user_session logger.debug "ApplicationController::current_user_session" return @current_user_session if defined?(@current_user_session) @current_user_session = UserSession.find end def current_user logger.debug "ApplicationController::current_user" return @current_user if defined?(@current_user) @current_user = current_user_session && current_user_session.user end def require_user logger.debug "ApplicationController::require_user" unless current_user store_location flash[:notice] = "You must be logged in to access this page" redirect_to new_user_session_url return false end end def require_no_user logger.debug "ApplicationController::require_no_user" if current_user store_location flash[:notice] = "You must be logged out to access this page" redirect_to account_url return false end end def store_location session[:return_to] = request.request_uri end def redirect_back_or_default(default) redirect_to(session[:return_to] || default) session[:return_to] = nil end end [/ruby]

UserSessionsController

[ruby] rails g controller UserSessions new [/ruby] and fill the controller with the correct code: [ruby] class UserSessionsController < ApplicationController before_filter :require_no_user, :only => [:new, :create] before_filter :require_user, :only => :destroy def new @user_session = UserSession.new end def create @user_session = UserSession.new(params[:user_session]) if @user_session.save flash[:notice] = "Login successful!" redirect_back_or_default users_url else render :action => :new end end def destroy current_user_session.destroy flash[:notice] = "Logout successful!" redirect_back_or_default new_user_session_url end end [/ruby] and of course that also needs a view, in new.html.haml [ruby] %h1 Login = form_for @user_session, :url => {:action => "create"} do |f| = f.error_messages %div = f.label :login = f.text_field :login %div = f.label :password = f.password_field :password %div = f.check_box :remember_me = f.label :remember_me %div = f.submit "Login" [/ruby] We want to use the f.error_message, but that is now removed from Rails3 and we need to install a plugin instead: [ruby] rails plugin install git://github.com/rails/dynamic_form.git [/ruby] We also need to define, inside config/routes.rb. You will see that the generator will have added the route get 'user_sessions/new', but that is not enough. You will have to add: [ruby] resources :user_sessions match 'login' => "user_sessions#new", :as => :login match 'logout' => "user_sessions#destroy", :as => :logout [/ruby]

Restrict access

Suppose you now have some other controller, e.g. HomeController, then restricting acces is straightforward: [ruby] rails g controller home index [/ruby] [ruby] class HomeController < ApplicationController before_filter :require_user def index end end [/ruby] The method require_user, defined in ApplicationController, will check if there is a user logged on, and if not redirect to the login-page. If we now delete the index.html inside your public folder, and we add the following at the bottom of config/routes.rb [ruby] root :to => 'home#index' [/ruby] Now we can start our application, and it should redirect to our login-page. But of course, since we have no users for now, we can't login just yet. To allow testing, you fire up your console: [bash] $ rails c Loading development environment (Rails 3.0.0.rc) ruby-1.9.2-p0 > User.create(:login => 'test', :email => 'test@tester.com', :password => 'test123', :password_confirmation => 'test123') => #<User id: 1, login: "test", email: "test@tester.com", crypted_password: "0129d9733b7912017e37a50263901488da90e127e6fd1ae6081...", password_salt: "ygufdflWkJQZGhbsGyia", persistence_token: "957788609b2067092dd3852b01613d53f96d0692ce5131174ca...", login_count: 0, failed_login_count: 0, last_request_at: nil, current_login_at: nil, last_login_at: nil, current_login_ip: nil, last_login_ip: nil, created_at: "2010-08-29 14:11:04", updated_at: "2010-08-29 14:11:04"> ruby-1.9.2-p0 > [/bash] Now you should be able to login using this user. For completeness, you should add some links to your application-view to allow logging in and out: [ruby] #user_nav - if current_user = "Signed in as #{current_user.email}. Not you?" = link_to "Sign out", logout_path - else = link_to "Sign in", new_user_session_path [/ruby] This would get your rails3 project started. The next steps would be to add some user management, or allowing users to sign up themselves, and maybe add some roles to limit certain users access if needed. Now, to contrast this with devise: i now have a bunch of code in my application that actually is not specific to my code, but is also completely not tested. I will provide example rspec tests for this later. Actually, the problem with this scenario as outlined, is it is one big bang. You should start little, declaring things you need to be able to do, and work in little steps to get there. But this scenario is intended as a draft to see how you could get there. Now, in your real application, you should start with the tests, and then from this example you now how you can implement it.
etsy fast_tagger v2
There used to be a greasemonkey script fast_tagger which allowed an etsy seller to add all tags at once when creating or editing an item. Since april of this year this script was broken. My girlfriend has had a shop for a couple of years and missed the functionality from etsyhacks dearly. Apparently Ian, the person behind etsyhacks, is now working for etsy himself and isn't maintaining the scripts anymore. So, without further ado: you can download the fast_tagger_2 script to install. It should operate in the same way as before.

Installation instructions:

  • install firefox and greasemonkey if you haven't done so already.
  • once Greasemonkey is installed and running, follow this link.
  • The "fast tagger 2" script will be downloaded, and Greasemonkey will ask if you want to install it. Assuming you trust me, click "Install".

Operation

When you create a new listing and go to the tags page, or when you edit the tags of an existing listing, you will see that a new box is now visible. Inside this box, you can enter tags, comma separated and ...
  • click "add tags" to add the tags to the listing
  • click "replace tags" to remove any existing tags and replace them with your current ones.
  • or you can click "delete tags" to remove any existing tags.
[UPDATE] I have been in touch with Ian, and he assured me he is going to continue supporting etsyhacks, only that he currently was swamped in work. I leave this script for whoever it could help in the meantime.
fixing pgadmin3
When i try to run pgadmin3 on my ubuntu 10.04 box i now get the following error: [bash] $ pgadmin3 pgadmin3: relocation error: pgadmin3: symbol _ZN21wxMemoryFSHandlerBase19AddFileWithMimeTypeERK8wxStringPKvjS2_, version WXU_2.8 not defined in file libwx_baseu-2.8.so.0 with link time reference [/bash] I assume it has something to do with one of the latest updates. I run all the updates always when they are proposed, but i am not really sure this always is a good idea. When googling the error i found a lot of links, and ultimately, also the fix: [bash] apt-get source pgadmin3 sudo apt-get install debhelper libpq-dev libwxgtk2.8-dev libxml2-dev libxslt1-dev autotools-dev devscripts cd pgadmin3-1.10.2 dpkg-buildpackage sudo dpkg -i ../pgadmin3_1.10.2-1_i386.deb [/bash]
setting up delayed_job in rails3
If you want to run longrunning jobs in the background, one very easy solution is using delayed_job. One other very interesting alternative is resque, but it seemed harder to setup (it uses redis), and delayed_job seemed to be just right for my needs. In short (from the redis documentation) : Choose Resque if:
  • You need multiple queues
  • You don't care / dislike numeric priorities
  • You don't need to persist every Ruby object ever
  • You have potentially huge queues
  • You want to see what's going on
  • You expect a lot of failure / chaos
  • You can setup Redis
  • You're not running short on RAM
Choose DelayedJob if:
  • You like numeric priorities
  • You're not doing a gigantic amount of jobs each day
  • Your queue stays small and nimble
  • There is not a lot failure / chaos
  • You want to easily throw anything on the queue
  • You don't want to setup Redis
And in my case: a short list of outstanding jobs, i am using a database already, not sure about redis, performance is not that important, i do not expect a lot of failure, i do not want to setup redis (yet :). So to use the delayed_job gem in a rails3 you will need to use the code from github (as the gem version 2.0.3 is not yet rails3 compatible). Add the following line to your Gemfile : [ruby] gem "delayed_job", :git => 'git://github.com/collectiveidea/delayed_job.git' [/ruby] and then run [bash] bundle install [/bash] Because i am using ActiveRecord, i can just use the generator, that will create the table and add the script to run a worker: [ruby] rails generate delayed_job rake db:migrate [/ruby] Include the rake tasks from delayed-jobs into your Rakefile: [ruby] begin require 'delayed/tasks' rescue LoadError STDERR.puts "Run `bundle:install` to install delayed_job" end [/ruby] Create an initializer file delayed_jobs_config.rb and write [ruby] # config/initializers/delayed_job_config.rb Delayed::Worker.destroy_failed_jobs = false #Delayed::Worker.sleep_delay = 60 #Delayed::Worker.max_attempts = 3 Delayed::Worker.max_run_time = 5.minutes [/ruby] If you are happy with the defaults, you can leave it. For me it was important to keep a record of the jobs that failed. Then you can just delay any function-call like this: [ruby] envelope.delay.query_status(delivery) [/ruby] Just adding the .delay does all the magic for you! And starting a worker is as simple as [ruby] rake jobs:work [/ruby]
Devise seems to become the new authentication standard in rails land. Also the last version of the gem is for rails3, so it is very easy to start using it. If you are using rspec2 for your tests, it is also really easy to test your controllers that are authenticated by devise. Suppose you have a DoStuffController like this: [ruby] class DoStuffController < ApplicationController before_filter :authenticate_user! def index end end [/ruby] You need to test it as follows in do_stuff_controller_spec.rb: [ruby] require 'spec_helper' describe DoStuffController do include Devise::TestHelpers before (:each) do @user = Factory.create(:user) sign_in @user end describe "GET 'index'" do it "should be successful" do get 'index' response.should be_success end end end [/ruby] And in your factory, users.rb: [ruby] Factory.define :user do |f| f.email "bla@blabla.com" f.password "blax123" end [/ruby] I am not testing the fact that the redirect will work, under the assumption that that is specific devise functionality which i assume to be tested and working.
Previously, in rails 2.3.8 i used the prototype-helpers link_to_remote and form_remote_for (amongst others). These had the option to add callbacks as follows: [ruby] link_to_remote "Add to cart", :url => { :action => "add", :id => product.id }, :update => { :success => "cart", :failure => "error" } [/ruby] (an example from the documentation). This example would, upon success update the html-element with class "cart", and upon failure the class "error". The possible callbacks were:
  • :loading: Called when the remote document is being loaded with data by the browser.
  • :loaded: Called when the browser has finished loading the remote document.
  • :interactive: Called when the user can interact with the remote document, even though it has not finished loading.
  • :success: Called when the XMLHttpRequest is completed, and the HTTP status code is in the 2XX range.
  • :failure: Called when the XMLHttpRequest is completed, and the HTTP status code is not in the 2XX range.
  • :complete : Called when the XMLHttpRequest is complete (fires after success/failure if they are present).
Now the modus operandi has changed, instead we write: [ruby] link_to "Add to cart", :url => {:action => "add", :id => product.id}, :remote => true [/ruby] and it seems there is no option to set the callbacks anymore. Instead of a normal html, we now render javascript, like this (in jquery) : [ruby] $('.cart').replaceWith(<%= escape_javascript(render :partial => 'cart') %>) [/ruby] But how do you handle an error situation? Do i handle it in my controller, and use seperate views? It would seem useful to me to somehow be able to mimic the behaviour we had before. Luckily in this article I was able to find the solution. I had already found that in rails.js the following callbacks were checked:
  • ajax:beforeSend : triggered before executing the AJAX request
  • ajax:success : triggered after a successful AJAX request
  • ajax:complete : triggered after the AJAX request is complete, regardless the status of the response
  • ajax:error : triggered after a failed AJAX request, as opposite to ajax:success
But i had no idea how to provide these callbacks. The javascript should be unobtrusive, so this coupling is not done straight in the HTML anymore. From the same article i found a very clear example how to solve this. Take the following Rails 2.3.8 code : [ruby] <% form_remote_tag :url => { :action => 'run' }, :id => "tool-form", :update => { :success => "response", :failure => "error" }, :loading => "$('#loading').toggle()", :complete => "$('#loading').toggle()" %> [/ruby] That translates to this in Rails3 : [ruby] <% form_tag url_for(:action => "run"), :id => "tool-form", :remote => true do %> [/ruby] and inside some javascript (application.js), you bind the events [javascript] jQuery(function($) { // create a convenient toggleLoading function var toggleLoading = function() { $("#loading").toggle() }; $("#tool-form") .bind("ajax:beforeSend", toggleLoading) .bind("ajax:complete", toggleLoading) .bind("ajax:success", function(data, status, xhr) { $("#response").html(status); }); }); [/javascript] For completeness, here is a list of the events and their expected parameters: [javascript] .bind('ajax:beforeSend', function(xhr, settings) {}) .bind('ajax:success', function(data, status, xhr) {}) .bind('ajax:complete', function(xhr, status) {}) .bind('ajax:error', function(xhr, status, error) {}) [/javascript] [UPDATED 7/2/2012] Updated to reflect the new event-names. :loading was renamed to :beforeSend, and :failure was renamed to :error.
I have created a Rails3 application, started with Haml/Sass and finding it awesome. I am also trying to do unobtrusive javascript. Before, in Rails 2.3, I would have expected a remote-form to have an :update attribute, where you could specify a selector where the response of the remote method would be rendered. Now it needs to be done differently: my controller function will render a ".js" view, which will do the necessary actions itself. This makes it more library agnostic, and i prefer jquery. So for instance i have a controller action "search", and i have a corresponding "search.js.erb" as follows: [ruby] $('.grid').replaceWith('<%= escape_javascript(render :partial => 'list') %>') [/ruby] This works. It will replace the html of the element with a class grid with the html from my rendered partial. I started out trying to achieve that in HAML, and failed at first. So first i created the above ERB code which worked. Now my task seemed simpler: translate this seemingly simple line to HAML. My first naive approach was to do the following: [ruby] = "$('.grid').replaceWith('#{escape_javascript(render :partial => 'list')}')" [/ruby] But this just places the html as a readable string inside my page. Even i do something simple like [ruby] = "$('.grid').replaceWith('<h3>Text</h3>') [/ruby] i see the actual characters [ruby] <h3>Text</h3> [/ruby] and not the markup. But i need to contain the javascript inside the double-quotes or haml will not recognise it, and can not interpolate my ruby there. After looking through the HAML reference, actually the solution was incredible simple. Once more. [ruby] != "$('.grid').replaceWith('#{escape_javascript(render :partial => 'list')}')" [/ruby] The != unescapes HTML (as opposed to the standard =). This is exactly what we need of course.
With Rails3 i can create a project fully cut to my needs. I will write it down here, just so i remember it well and hopefully it will help some of you too. In my projects, i want to use haml, rspec2, factory-girl, jquery, ... We need a few steps to complete this.

Create the project

For starters, create the folder, without Test::Unit (-T) and without prototype (-J). [ruby] rails new test-project -T -J [/ruby] You could also specify the database, using option -d, [--database=DATABASE], with the following options:
  • mysql
  • oracle
  • postgresql
  • sqlite3 (default)
  • frontbase
  • ibm_db

Specify needed gems

We also have to specify the gems we need. To do that, we need to edit the Gemfile which is located in the root of your project. My file looks as follows: [ruby] source 'http://rubygems.org' gem 'rails', '3.0.0.rc' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' #gem 'pg' gem 'sqlite3-ruby', :require => 'sqlite3' # Use unicorn as the web server # gem 'unicorn' # Deploy with Capistrano # gem 'capistrano' # To use debugger # gem 'ruby-debug' gem 'rails3-generators' gem "bson_ext" gem "haml" gem "haml-rails" gem "jquery-rails" gem "rcov" # we need this here, see http://blog.davidchelimsky.net/2010/07/11/rspec-rails-2-generators-and-rake-tasks/ group :development, :test do gem "rspec-rails", ">= 2.0.0.beta.18" end # test-environment gems group :test, :spec, :cucumber do gem "factory_girl_rails" gem "rspec", ">= 2.0.0.beta.18" gem "remarkable", ">=4.0.0.alpha2" gem "remarkable_activemodel", ">=4.0.0.alpha2" gem "remarkable_activerecord", ">=4.0.0.alpha2" gem "capybara" gem "cucumber" gem "database_cleaner" gem "cucumber-rails" end [/ruby] Run bundle install to install all needed gems.

Use wanted generators

Then now edit the file application.rb, located in the config folder, and add the following lines: [ruby] # Configure generators values config.generators do |g| g.test_framework :rspec, :fixture => true g.fixture_replacement :factory_girl, :dir=>"spec/factories" end [/ruby] This will make sure that the generators use our own defaults. So now when you type [ruby] rails generate model TestModel name:string rails generate controller TestModel [/ruby] and it will create haml views, rspec tests, and factories. Awesome :)

Prepare to use jquery!

To start using jQuery in Rails3, you have to first get the rails.js specifically for jQuery (stored in the jquery-ujs project). Save it in your public\javascripts directory. Download jquery, and make sure to include both in your application layout or view. In HAML it would look something like. [ruby] = javascript_include_tag 'jquery.min.js' = javascript_include_tag 'rails' [/ruby] BUT: there is a quicker route! We also installed the gem jqeury-rails, which has the following generator if we want to install jquery in one go! [ruby] rails g jquery:install #--ui to enable jQuery UI [/ruby] Rails requires an authenticity token to do form posts back to the server. This helps protect your site against CSRF attacks. In order to handle this requirement the driver looks for two meta tags that must be defined in your page's head. Luckily rails makes it easier for us, again, and we just need to include csrf_meta_tag somewhere inside your page's head (rails3 does this default in your application.html.erb). Be sure to include it in your haml code too. An example application.html.haml would look like this: [ruby] !!! Strict %html{ "xml:lang" => "en", :lang => "en", :xmlns => "http://www.w3.org/1999/xhtml" } %head %title== Test-project #{@page_title} %meta{'http-equiv' => "Content-Type", :content => "text/html; charset=utf-8"} %link{ :href => "/favicon.ico", :rel => "shortcut icon" } = stylesheet_link_tag :all = javascript_include_tag 'jquery-1.4.2.min.js', 'rails.js', 'application.js', :cache => 'all_1' = csrf_meta_tag = yield :local_javascript = yield :head %body #banner =image_tag 'your-logo.png' .the-title %h1 %p Your application #navigation #container #sidebar #contents =yield #footer == &copy; 2010 your company [/ruby]

Done! :)

awesome rspec hints and tips
Read "Pure Rspec", a great slideshow by Jon "Lark" Larkowski. It contains some really cool rspec tips and hints, how to write your tests more readable and compact.
creating a new gem
I found a few articles that describe easier ways to create a gem. From the entire list of available tools, i want to highlight two: jeweler and (Mr.) bones. Mr. bones seems a very configurable route to take: it allows you to define your own skeletons (application templates) or use those of others, e.g. bort. On the other hand it also seems to do more than i need. Jeweler seems to be very specific in what i does for you, allowing to create a gem very easy and manage your gem locally. Also it is very configurable, so you can immediately create rspec and cucumber folders, fill the wanted rake tasks. It is awesome, and makes starting really easy. So i executed the following command: [ruby] D:\work-git\ruby>jeweler --rspec --cucumber --reek --roodi --create-repo word_automator create .gitignore create Rakefile create LICENSE create README.rdoc create .document create lib create lib/word_automator.rb create spec create spec/spec_helper.rb create spec/word_automator_spec.rb create spec/spec.opts create features create features/word_automator.feature create features/support create features/support/env.rb create features/step_definitions create features/step_definitions/word_automator_steps.rb Jeweler has prepared your gem in word_automator Jeweler has pushed your repo to http://github.com/nathanvda/word_automator [/ruby] which as you can tell created a folder, rspec and cucumber place-holders, prepared my gem and pushed my repository to github! Wow :) Now fill the gem with code :)
rails3 and rspec2 view testing
Testing the view in Rspec is just great. It allows you to test the view in total isolation. It just does the render. Make sure you set up some data that is needed, and then check if all areas are available for instance. Now for Rspec2 a lot of things have changed:
  • have_tag is no longer supported: use have_selector from webrat instead
  • response is deprecated, use rendered instead
  • the assignment has changed, write assign(:events, [stub_model(Event)]) instead of using the old assigns[:events]=[ ...] notation
Let me show you a full example, because that always makes things clearer: [ruby wraplines="false"] require 'spec_helper' DUMMY_NUMBER="dummy number" DUMMY_MESSAGE="dummy message" describe "envelopes/index.html.haml" do before (:each) do envelope = mock_model(Envelope) envelope.should_receive(:destination).and_return(DUMMY_NUMBER) envelope.should_receive(:message).and_return(DUMMY_MESSAGE) assign(:envelopes, [envelope]) render end it ("has a h1") { rendered.should have_selector('h1')} it ("shows the correct title") { rendered.should contain(t('envelopes.list.title')) } it ("have a table-grid") { rendered.should have_selector('.grid') } it ("shows the destination") { rendered.should contain(DUMMY_NUMBER) } it ("shows the message") { rendered.should contain(DUMMY_MESSAGE) } end [/ruby]
rspec testing using macros
[ruby wraplines="false"] STATUS_EXPECTED_TRANSLATIONS = [ [inp1, outp1], ... ] it "should translate received statuses" do STATUS_EXPECTED_TRANSLATIONS.each do |tr| controller.send('translate_status_to_envelope_status', tr[0]).should == tr[1] end end [/ruby] While this is short and DRY, a problem arises when one of the values in my look-up table does not behave as expected anymore. The complete test will fail, without having any clue which value caused it to fail. That is not good! But luckily, in rspec there exists the option to use macros. It is a kind of templates for tests. Using macros i could write this much cleaner, and generate my test-clauses on the fly. [ruby wraplines="false"] STATUS_EXPECTED_TRANSLATIONS.each do |tr| it "should translate #tr[0]} to success" do controller.send('translate_status_to_envelope_status', tr[0]).should == tr[1] end end [/ruby] Now if a test fails, i know which i value need to check, because i will get an appropriate error-message.
skipping slow tests in rspec2
For Test::Unit there is a module called skippy, that allows you to skip for instance slow tests. I was looking for a way to achieve the same in Rspec. I am currently developing against Rails 3.0.0.rc, and using rspec 2.0.0.beta.19 and could not find anything. So i asked on the rspec mailing list and got a great answer! It is actually present in rspec2, right out of the box, as a feature they needed for their own tests. Read the description by David himself here. He shows a lot of very good examples, even filtering tests based on ruby-version is now possible. Awesome. In short, you would do something like : [ruby] describe "something", :slow => true do it "should be blabla" end describe "something else" do it "should be a slow test", :slow => true do ... end it "should be a fast test" do .. end end [/ruby] then you can write the following to skip the slow tests : [ruby] # in spec/spec_helper.rb RSpec.configure do |c| c.filter_run_excluding :slow => true end [/ruby] and to run only the slow tests: [ruby] # in spec/spec_helper.rb RSpec.configure do |c| c.filter_run :slow => true end [/ruby] and to filter on slow tests, and run all tests if no slow tests are available: [ruby] # in spec/spec_helper.rb RSpec.configure do |c| c.filter_run :slow => true c.run_all_when_everything_filtered = true end [/ruby] Awesome! :) For the moment there is no command-line support, but in the spec/spec_helper.rb you could also check for any environment variables to do the slow or fast test-set. What i personally needed, was something that would either run all tests, or run all tests excluding a few (i have a test-suite for an sms-gateway and do not want to send sms-es all the time). So i handled that like this: [ruby] # in spec/spec_helper.rb RSpec.configure do |config| if ENV['SEND_REAL_SMS'] != "true" config.filter_run_excluding :send_sms => true end end [/ruby]
Using git-submodule to handle plugins
Using git-submodule can make it very easy for you to work with plugins in your rails projects. I will try to show you this, in an easy step-by-step manner. To start, make a plugin-project, call it your_plugin, in its own folder, and push the code to git. Your standard commands apply, as for any project:
  • git status : what is changed?
  • git add . : add the newly added files to your git-repository
  • git commit -am "a meaningful message" : commit your changes in your local repository
  • git push origin master : push your local changes to the remote master (origin/master)
How can you add the plugin as a submodule to your rails project? Navigate to the root of your rails project, and execute the following commands: [bash] git submodule add git://your_repository vendor/plugins/your_plugin_name git submodule init git submodule update git status # will show the difference: only the submodule is added git commit -a -m "added plugin your-plugin-name" [/bash] How do you retrieve the lastest version of a submodule/plugin. A submodule in git is coupled to the git-repository at a specific time, so it does not automatically evolve with the remote versions of your pluging. You have to manually make a few operations, to make sure you are using the latest version. In my opinion this is an advantage, as you can keep using a known working version until you really want to upgrade. To update your submodule, go to the root of your plugin/submodule (normally RAILS_ROOT/vendor/plugins/your-plugin-name), and execute the following commands: [bash] git remote update # since your submodule is actually a git repository on its own, you can do this git merge origin/master # retrieve the remote master version cd ..\..\.. # back to RAILS_ROOT git status # your submodule is updated to the latest version [/bash] Now to edit and change the plugin/submodule, it is the easiest to work inside the plugin folder itself. To do so, you have to proceed as follows. Inside the root of your plugin (e.g. @\vendor\plugins\your-plugin@) [bash] git checkout -b your_local_branch ... do some changes git commit -a -m "something changed" git checkout master git pull git checkout your_local_branch git rebase master ... solve posslble merge-conflicts ... git checkout master git merge your_local_branch git push [/bash] then cd to RAILS_ROOT [bash] git status # => your plugin will have changed git commit -a -m "improved plugin" [/bash] I used the following sources for inspiration:
rewriting find_by_sql for rails 3
I am starting in Rails 3. I have a simple situation where i would select a parent based on a condition of one of its children. Normally i would write something like: [ruby] Distributor.find_by_sql("select d.*, c.rate from distributors d, coverages c where c.country_code_id = 25 and c.distributor_id=d.id order by rate asc") [/ruby] Although this works, it is not the best solution to take. For one, it is vulnerable to sql-injection and secondly it shows a lot of the underlying database structure, and on top of that the sql-implementation could change between databases. In Rails 3 i can now write [ruby] Distributor.includes(:coverages).where("coverages.country_code_id=? and distributors.id =coverages.distributor_id", 25).order(:rate) [/ruby] This is so readable! Awesome :)
installing ubuntu for ruby development
I have made the complete switch. My home development machine is no longer Windows XP, no longer dual booting Ubuntu but a full-fledged Ubuntu. After installing the dual-boot, and my machine at work this is the third time to install an ubuntu machine, and getting everything ready for ruby development. I just want to archive my steps here, so a next time could be even faster :) Installing Ubuntu 10.04 itself is a piece of cake. My first step after the installation is to enable numlock on login screen (i always use numbers in my passwords) [bash] sudo apt-get install numlockx [/bash] Then run sudo gedit /etc/gdm/Init/Default and find the line [bash] exit 0 [/bash] and add the following code above that line [bash] if [ -x /usr/bin/numlockx ]; then /usr/bin/numlockx on fi [/bash] Secondly I install rvm for multiple rubies. We first need to install any ruby for that. Take the default ubuntu version (1.8). [bash] sudo apt-get install ruby rubygems [/bash] This does not install the latest version, but we don't need that yet. Rvm will solve this for us. Install rvm itself: [bash] sudo apt-get install curl bison build-essential zlib1g-dev libssl-dev libreadline5-dev libxml2-dev git-core bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head ) [/bash] Then make sure rvm will work always [bash] sudo gedit .bashrc [/bash] and replace [-z $SP1] && return with [bash] if [[ ! -z $SP1]]; then .. original content that was below the && return line [[ -s $HOME/.rvm/scripts/rvm ]] && source $HOME/.rvm/scripts/rvm fi #EOF [/bash] Then rvm should be available. Install the needed rubies. We use ree at work, i like to work with 1.9.1/2 and i want to check out rubinius. So install your rubies through rvm [bash] rvm install ree rvm install 1.9.2 rvm install rbx [/bash] Installing ruby 1.9.1 was a bit harder, when i just tried to run the standard rvm install 1.9.1, i received the following error: [bash] .rvm/src/ruby-1.9.1-p429/lib/optparse.rb:1310: [BUG] Segmentation fault [/bash] and that was caused because i use a newer gcc version, which can't handle the -O3 optimisation. It took me a while to find how you can fix it, but in the end (as always) it was easy. Just type [bash] export optflags="-O0"; rvm install 1.9.1 [/bash] To end, set your default ruby version [bash] rvm ree --default [/bash] now all your new sessions you should be using your selected default version. After installing your favourite editor (Netbeans or rubymine), your favourite database (postgreSQL, sqlite, mysql ), and your list of gems you are ready to roll some ruby! I must say as an previously avid Windows user for ages, i am very impressed by Ubuntu. This is a free operating system, and while i was aware that everything just works better being linux (meaning processes and filesystem); but not only that: it even looks better and is very user-friendly. It is way more customisable looks-wise then silly windows. In Windows 7 for instance you have to use this very large font, and in Ubuntu i can set the size to anything i want (small!!!). Awesome :) And i guess it will amaze me for the next few days and weeks whilst finding out other new stuff. [UPDATE] For me it is confusing that the minimize, maximize and close buttons are on the left-side, i think it is done to be more apple-like. But the fix to move the buttons to the right is luckily very easy once you know it :)
shopping cart usability
Last wednesday i was offered, in a mail i received, the opportunity to start shopping on friday at 1500hours CET for an very specific item, Instant film at an incredible price. So come last friday, I was ready and braced to start shopping. Unfortunately and to be expected, i was not alone. I was able to get 3 packs of film in my shopping basket, enter my details, select a payment method, and ... just when all I needed to do was confirm the whole thing the site hung up on me and when the site reacted again all stock was gone. This felt very wrong: I was almost there, I had the items in my shopping cart, i was in the checkout line, i should have been able to buy it. It got me thinking how to handle such a thing. So out of this negative experience, i mailed to the support of the site, claiming the way they handled the shopping cart was wrong (maybe i should have said: could be improved), because i was expecting that if i could place items in the shopping basket, i would also be able to buy them; and also to be able to let them know i was unhappy, and hoping they could do something about it come next time. They did reply, though:
Sorry, but that's the way our shop works: until you click the "place order" button, the stock is not reserved for you. Only by confirming your order, you are then guaranteed the order. Stock is subtracted not upon successful payment, but the moment you confirm your order. The payment is then processed and your order marked as "paid" and passed on to processing.
While their approach makes sense for normal e-commerce sites, sites where the stock is limited ask for another approach. Of course, as a developer it becomes more difficult to get items out of stock once they are placed in the shopping cart, most especially because when to release the items to stock if the customer does not buy? I did find another article, explaining when retailers should empty a shopping cart. From the article i deduced some rules:
  • if your stock is limited, placing items in the shopping cart should reserve the item (removing it from available stock temporarily), so you have a difference between the actual stock (what the retailer has in their storage, reserved stock (about to be sold or not) and sold stock). So the amount of stock visible on the site should be actual stock-reserved stock.
  • how long your shopping cart is valid should be visible or communicated (e.g. countdown clock)
I would like to note that this is especially true for ticketing services, where stock is in very limited supply and in large demand. You want to give the user the good shopping experience (when a customer puts the items in the shopping basket, there still was stock, so he or she should be able to buy them), but also not let your users hoard the stock. An excellent example from the above-mentioned article is Ticketmaster, where a user can put tickets in her basket, and then has a predefined time (two minutes) to complete the purchase or the cart is emptied and the stock released again. As a consequence, the meaning of your shopping cart is different: it is a clear intention to buy, so it will not be used as a wish list. On e-commerce sites where there is no such peak of sales, shopping carts can even persist over different sessions, allowing returning users to continue shopping. Such a use of a shopping cart would not be linked to the stock directly (e.g. reserving items), but whether an item is in stock would be checked at the start of a new session. This is in high contrast with an e-commerce site offering items which are in very limited supply and high demand (like tickets or instant film). The standard approach does not suffice. If putting items in the shopping basket takes them out of the stock, the load can be spread more evenly. Let me explain that by example, by comparing 2 scenarios. Scenario 1, the standard e-commerce site, items are only taken out of stock after an order is confirmed:
  • 1000's of people log on
  • all of them add a number of items to their shopping basket
  • all of them start filling in their details, payment methods, delivery address, and all this has to be verified, connections to Paypal, credit-card service, ...
  • in the meantime no items have been sold, so users logging on can still start buying things, thereby adding more load to the server, ...
  • the server bends under the escalating load
Now compare this to scenario two, where items are being "reserved" if they are put inside the shopping basket, and a user has two minutes to complete the order-confirmation process:
  • 1000's of people log on
  • 400 or more of those people get rejected, because the items have been reserved
  • the users who were able to get items in the basket, fill in the details, which have to be verified, ...
  • users logging on later will in the meantime not be able to order anything, because all items have been reserved
  • after the first two minutes have passed, most of those first items will have been bought, and some will have released again
  • some users will be able to fetch released items and can continue shopping
This scenario will not only communicate better to your clients, the process is clearer, AND the load on your servers can't escalate in the same way. This process could then be further improved, so that users logging on are placed in a FIFO queue. The first users to place an order get their items until stock runs out. The following ones are placed in a waiting queue, and busy-wait at most the two minutes (the maximum time before items could be released) for those that are released, if any. If a user has no patience, he releases his place in the queue. If you add to that some real-time communication (pushing to the client?), you would have a real clean a user-friendly system. From the moment a user logs on, it is clear what is going on.
actionmailer attachments on windows
I have been having troubles sending e-mails using ActionMailer on windows. The simple code-sample: [ruby] my_filename="my_image.jpeg" attachment "image/jpeg" do |a| a.body = File.read(my_filename) a.filename = File.basename(my_filename) end [/ruby] did not work. I did find an attachment inside my e-mail, but it was corrupt. It took me a while to figure out it could be something related to my development platform, which unfortunately still is windows (not for long). On windows reading binary files is less obvious, apparently, so you need to write: [ruby] my_filename="my_image.jpeg" attachment "image/jpeg" do |a| a.body = File.open(my_filename,'rb') { |f| f.read} a.filename = File.basename(my_filename) end [/ruby] ... and this will work! :) I you have to combine normal e-mail content and one or more attachments, this then becomes something like: [ruby] # we send a mail to respond to a mention def mention_response(given_subject, addressee, pdf_attachment = nil) from APP_CONFIG["mail"]["from"] recipients addressee.to_a subject given_subject content_type "multipart/mixed" part "text/html" do |p| p.body = render_message("mention_response", :thr_name => thr_name) end attachment :content_type => "application/doc" do |a| a.filename File.basename(pdf_attachment) a.body File.open(pdf_attachment, "rb") { |f| f.read } end unless pdf_attachment.nil? end [/ruby] Nice :)
use %q to create strings
In my gemspec created by jeweler I saw string creating using %Q. What? [ruby] gem.summary = %Q{TODO: one-line summary of your gem} gem.description = %Q{TODO: longer description of your gem} [/ruby] I found a page describing ruby string creation, and it seems %Q is the equivalent of double quote delimited strings and %q is the equivalent of single quotes. While i was pondering what the advantage could be of writing it that way, i opened up irb and look and behold: [ruby] irb(main):001:0> test = %Q{ggg gggg ggg} => "ggg gggg ggg" irb(main):002:0> test2= "ff ff ff" => "ff ff ff" irb(main):003:0> test= %Q{%q{ffff ffff ffff}} => "%q{ffff ffff ffff}" irb(main):004:0> test= %Q{this is a "test", curious whether 'it' works :)} => "this is a \"test\", curious whether 'it' works :)" irb(main):005:0> test= %Q{this is a "test", i am curious whether #{test2} 'it' works :)} => "this is a \"test\", i am curious whether ff ff ff 'it' works :)" irb(main):006:0> [/ruby] Cool! It allows you to write your strings without having to think about escaping the quotes.