XPlayer

A blog on my daily efforts to be a better developer, and keep improving every day.

Setting the Rails Session Cookie Domain Based on the Actual Server Name

| Comments

Say you have single Rails codebase publishing different apps with different domain names, all sharing a common higher-level domain name, for example:

store.mysite.com
www.mysite.com
read.mysite.com

and you want all these apps to share the same Rails session cookie.

What you should do is set a proper domain in the session cookie, so that this domain is based on the current server name.

We needed just this for our e-commerce platform, which supports multiple stores (and thus multiple domain names) on a single Rails app.

This is the solution we came up with (tested on Rails 2.3.4): it’s a simple middleware.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SetCookieDomain
  def initialize(app)
    @app = app
  end

  def call(env)
    subdomain = remove_innermost_domain_from(env["SERVER_NAME"])
    env["rack.session.options"][:domain] = subdomain
    log "forced cookie domain to #{subdomain}"

    @app.call(env)
  end

  private

  def remove_innermost_domain_from(server_name)
    server_name.gsub(/^[^.]*/, '')
  end

  def log(message)
    RAILS_DEFAULT_LOGGER.info("========")
    RAILS_DEFAULT_LOGGER.info(message)
    RAILS_DEFAULT_LOGGER.info("========")
  end
end

And this is a spec:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
describe SetCookieDomain do

  let(:app) { stub("app", :call => "") }
  let(:set_cookie_domain) { SetCookieDomain.new(app) }

  before(:each) do
    @env = {
      "SERVER_NAME" => "store.any.it", "SERVER_PORT" => "3000",
      "HTTP_HOST"   => "store.any.it:3000",
      "REQUEST_URI" => "/",
      "rack.session.options" => { :path => "/", :key => "_session_id", :expire_after => nil, :httponly => true, :domain => nil, :id => "any_id" },
    }
  end

  it "forces the session cookie domain to be the second-level domain of the full domain name" do
    @env["SERVER_NAME"] = "store.xpeppers.com"

    @env["rack.session.options"][:domain].should be_nil

    set_cookie_domain.call(@env)

    @env["rack.session.options"][:domain].should == ".xpeppers.com"
  end
end

To enable the middleware, you’ve to put this line in your Rails startup files (e.g. environment.rb)

1
config.middleware.use "SetCookieDomain"

Introducing Tracco: The Trello Effort Tracker Gem

| Comments

Trello meets tracking

Trello is a very good surrogate for a physical team board: it’s simple and effective, and it can really help when you have a distributed team. That said, Trello (still) doesn’t offer a way to track time estimated and actually spent on cards, though many people are asking for that feature on Trello’s development board.

I had such precise need while working with one of our teams, so I came up with the following idea: using Trello’s mention system to send tracking notifications to a predefined board member (I call him the ‘tracking user’), collecting along this way all tracking events such as estimates and efforts.

A tracking example

Then I built a simple tool to persist and aggregate this data, so that it would have been possible to show interesting metrics such as estimate errors. I called this tool Tracco: a gem to help track estimates and efforts from Trello.

Tracco is a gem, and you can use it as is, but it’s intended use is inside an app which displays collected data. To give an idea of what I mean, I developed a bare minimum Rails app to properly present card estimates and efforts: it’s called Trello Effort App. It’s really simple, but I wish it could be improved with the help of other committers :O)

More details

To start using Tracco you should have a Trello account, a Trello board and a board member to use as ‘tracking user’. You’ll also need to know your Trello developer key and generate a proper auth token to have access to the trackinguser’s notifications. To see how to have these two keys, read the following section.

The Trello API is used behind the scenes to read data from the team board. Tracco uses the awesome Trello API Ruby wrapper for this purpose.

An example

Here I show an example of how you could use Tracco. For more info please refer to the official README.

1
2
3
git clone git://github.com/xpepper/tracco.git
cd tracco
bundle install

Then run the initializer

1
tracco --initialize

to create the two configuration files, which you’ll need to edit properly (see “Where do I get an API key and API secret?” section).

To fill the correct values for the mongodb environments (see here to have more details).

Where do I get an API key?

Log in to Trello with your account and visit https://trello.com/1/appKey/generate to get your developer_public_key.

Where do I get an API Access Token Key?

To generate a proper access token key, log in to Trello with the ‘tracking user’ account. Then go to this URL:

https://trello.com/1/connect?key=<YOUR_DEVELOPER_PUBLIC_KEY>&name=Tracco&response_type=token&scope=read&expiration=never

At the end of this process, you’ll receive a valid access_token_key, which is needed by Tracco to have the proper rights to fetch all the tracking notifications sent as comments to the ‘tracking user’.

Collecting data from Trello

1
2
3
4
5
tracco collect today --environment test # will extract today's tracked data and store on the test db

tracco collect today  # will extract today's tracked data and store on the default (that is development) db

tracco collect 2012-11-1 --environment production  # will extract tracked data starting from November the 1st, 2012 and store them into the production db

Console

You can open a irb console with the ruby-trello gem and this gem loaded, so that you can query the db or the Trello API and play with them

1
tracco console

The default env is development. To load a console in the (e.g.) production db env, execute:

1
tracco console -e production

Estimate format convention

To set an estimate on a card, a Trello user should send a notification from that card to the tracker username, e.g.

@trackinguser [15p]
@trackinguser [1.5d]
@trackinguser [12h]

estimates can be given in hours (h), days (d/g) or pomodori (p).

@trackinguser 22.11.2012 [4h]

will add the estimate (4 hours) in date 22.11.2012.

Effort format convention

To set an effort in the current day on a card, a Trello user should send a notification from that card to the tracker username, e.g.

@trackinguser +6p
@trackinguser +4h
@trackinguser +0.5g

efforts can be given in hours (h), days (d/g) or pomodori (p).

Tracking an effort in a specific date

To set an effort in a date different from the notification date, just add a date in the message

@trackinguser 23.10.2012 +6p

There’s even a shortcut for efforts spent yesterday:

@trackinguser yesterday +6p
@trackinguser +6p yesterday

Tracking an effort on more members

By default, the effort is tracked on the member which sends the tracking notification.

To set an effort for more than a Trello user (e.g. pair programming), just add the other user in the message, e.g.

@trackinguser +3p @alessandrodescovi

To set an effort just for other Trello users (excluding the current user), just include the users in round brackets, e.g.

@trackinguser +3p (@alessandrodescovi @michelevincenzi)

Tracking a card as finished (aka DONE)

Sending a tracking notification with the word DONE

@trackinguser DONE

will mark the card as closed.

Moreover, a card moved into a DONE column (the name of the Trello list contains the word “Done”) is automatically marked as done.

Google Docs exporter

To export all your tracked cards on a google docs named ‘my_sheet’ in the ‘tracking’ worksheet, run

1
tracco export_google_docs my_sheet tracking -e production

The default env is development.

If you provide no name for the spreadsheet, a default name will be used. If the spreadsheet name you provide does not exists, it will be created in you google drive account.

So, running simply

1
tracco export_google_docs

will create (or update) a spreadsheet named “trello effort tracking” using the development db env.

Requirements

  • MRI version 1.9.3+
  • mongoDB - macosx users with homebrew will just run ‘brew install mongodb’ to have mongoDB installed on their machine.
  • (optional) rvm is useful (but optional) for development

Roadmap and improvements

I develop Tracco using Trello itself.

Contributing

If you’d like to hack on Tracco, start by forking the repo on GitHub:

https://github.com/xpepper/tracco

Learning Ruby Reimplementing It: Attr_reader

| Comments

How handy is the attr_reader method? Very handy indeed.

Never asked yourself how it may be implemented? Just do it!

This is my take:
module Kernel
  def attribute_reader(attribute)
    define_method(attribute) do
      instance_variable_get("@#{attribute}")
    end
  end
end

class MyClass
  attribute_reader :my_attribute
end

m = MyClass.new
m.instance_variable_set("@my_attribute", 42)

puts m.my_attribute # => 42
What’s yours? Have fun!

Learning Ruby Reimplementing It: Attr_writer

| Comments

This is how attr_writer may be (re)implemented:

module Kernel
  def attribute_writer(attribute)
    define_method("#{attribute}=".to_sym) do |value|
      instance_variable_set("@#{attribute}", value)
    end
  end
end

class MyClass
  attribute_writer :my_attribute
end

m = MyClass.new
m.my_attribute = 23

puts m.instance_variable_get "@my_attribute" # => 23

A Rails Project File for Sublime Text 2

| Comments

The simplest thing that you can do to open up the Sublime Text 2 editor on your Rails project is to just type in
$ subl .
on the Rails root directory¹. A slightly better solution is to create a .sublime-project file in your project root directory, so that you may launch the editor with something like
$ subl --project my_rails_project.sublime-project
A question then arises, “How do I exclude useless directories as tmp or log from the project file?” Answer: just edit the sublime-project file this way:
{
 "folders":
 [
   {
     "path": ".",
     "folder_exclude_patterns": ["tmp", "log"],
     "file_exclude_patterns": [".tmtags"]
   }
 ]
}
(and of course you should adapt it to your actual preferences on what to exclude from your project). Note that you can also specify a “file_exclude_patterns” property to filter out some files (in my case I wanted to exclude the ctag index file).
  1. to install the command line tool, just follow the docs here.

A Review of “Great Bash” Video by Carl Albing (O’Reilly Media)

| Comments

Shell programming is a topic that every professional programmer should care about, and keep improving on, for several reasons. Here are the first two I can think about:
  1. Because is essential in order to promote the automation of many manual processes (and automation is damn important!).
  2. Because I like to be a programmer as much as a devop, and I want to be able to deploy what I develop, and take care of all the operational and system stuff related to the system I contribute to build.
Me myself made the mistake of underestimate the relevance of this topic for way too time in the past: don’t make my same mistake :-) Carl Albing’s “Great Bash” by O’Reilly Media is a collection of several short video lessons on the basics of the shell programming. I watch the “Great Bash” lessons hoping to learn more about shell programming, but unfortunately Carl Albing’s video lessons are too introductory (IMHO). It was nevertheless great to recap many things I learned here and there, and so my time watching it was not wasted at all, but I would recommend this video only to shell programmer beginners. There are also some (really) minor defects in the technical way the video is recorded: the audio quality may be improved and sometimes the speaker take some long inexplicable pauses. But, that said, the overall quality is really good. In the end I recommend to watch this video if you are a shell programmer beginner and you want to start understanding more about this topic.

Dynamically Add Data Accessor Methods on “Static” Rails Data Model

| Comments

An useful metaprogramming spell I recently played with is the Module#define_method(), which dynamically adds an instance method to the class on which is called.   I found it particularly useful to add data accessor methods on “static” Rails data model: suppose I’m working an e-commerce Rails webapp, and I have a Country model which maps the countries suitable for shipping, or a PaymentType model which represents all the possible payment types. For these kind of models (and tables), which are typically static (they don’t change often), you often have to access specific values, say Country.italy or PaymentType.credit_card. In these cases, defining dynamically an accessor method may be useful and more clear than always perform a find_by_name("my value"). So, for example, I open up my country.rb model class and add these lines [sourcecode language=”ruby”] class << self Country.all.each do |each_country| define_method(each_country.name.downcase.gsub(‘.’, ”).gsub(’ ‘, ‘_’)) do Country.find_by_iso_code(each_country.iso_code) end end end [/sourcecode] And then opening the Rails console I will be able to type
1.8.7@epistore > Country.sri_lanka
# {
                :id => 59,
              :zone => "U9",
           :enabled => true,
        :created_at => Tue, 20 Apr 2010 17:01:45 CEST +02:00,
        :updated_at => Tue, 20 Apr 2010 17:01:45 CEST +02:00,
          :iso_code => "LK",
    :country_set_id => nil
}
Just a note: as I said, Module#define_method() will add an instance method on the class. To add a class method, which is what I want, we have to use a different approach, using the class << self syntax to add a singleton method in the receiver. I may also add a query method on each Country instance to check that country against another country (for example, I may ask my_country.italy?)
  Country.all.each do |each_country|
    define_method(each_country.name.downcase.gsub('.', '').gsub(' ', '_').concat('?')) do
      has_iso_code? each_country.iso_code
    end
  end
And then, after issuing a reload! command in the Rails console, I may type:
1.8.7@epistore > Country.usa.usa?
true
1.8.7@epistore > Country.usa.italy?
false
1.8.7@epistore > Country.usa.south_korea?
false
1.8.7@epistore > Country.south_korea.south_korea?
true
Depending on the kind of Rails app you have, these may be a useful tip.

Assert_select_rjs Reloaded!

| Comments

If you ever dared to unit-test a Rails RJS action, for example something like this:
def my_ajax_action
   ...
   render(:update) do |page|
     page.replace_html 'shoppinglist', :partial => 'cart'
     page.replace_html 'items', :partial => 'layouts/items', :locals => { :cart => @cart }
   end
end
you may already know and use the assert_select_rjs testing helper, which basically will verify the structure of your RJS response.
This testing method may really help you shortening the TDD feedback loop in an AJAX-based Rails webapp, and then you’ll may even be confident enough and save one or two brittle Selenium tests.
The only problem with assert_select_rjs is that is (IMHO) poorly documented and rarely googled about.
So, this is my turn to give back what we discovered.
If you have a Rails webapp using jQuery as javascript framework, you may have a hard time using assert_select_rjs correctly, and this is why:
for jQuery, this is the correct way to use assert_select_rjs:
assert_select_rjs :replace_html, '#shoppinglist'
it’s important the ‘#’ prefix here to refer to DOM element IDs, since the notation without ‘#’ will work only if your app uses Prototype.
Another nice thing to know is the way to make assertion on the selection matched by the assert_select_rjs.
For example, this code
assert_select_rjs :replace_html, '#shoppinglist' do
    assert_select '#shipping_cost_description', /Shipping costs for France/
    assert_select '#shipping_cost_value', /&euro; 12,30/
end
will verify that the section replaced inside the ‘shoppinglist’ element will match the two followings assetions.

My First Test Using Webdriver (Aka Selenium 2.0)!

| Comments

As many say, a good solution to selenese flu is Webdriver (see more at http://code.google.com/p/selenium). Webdriver has been accepted by the Selenium guys as the new approach to web application testing, opposed to the classical “selenium 1.0” approach, based on a javascript driver, which suffers from way too many issues. Unfortunately, Selenium 2.0, which plan to fully support Webdriver, is still on an alpha release, and actually is very difficult to find ruby-based web testing tools supporting this alpha version of selenium 2.0. One of those tools is actually Watir (though Webrat too is planning to support Selenium 2.0 sooner or later), and more precisely this project is quite stable to allow a first test drive. So this is what I did: First: installed required gems
  sudo gem install selenium-webdriver
  sudo gem install watir-webdriver --pre
Second: configure my Rails testing configuration to use watir
config/environments/test.rb
  ...
  config.gem "watir-webdriver"
  ...
test/test_helper.rb
  require 'test_help'
  ...
  require 'watir-webdriver'
  ...
Third: write a test
test/integration/paypal_integration_test.rb
require 'test_helper'

class PaypalIntegrationTest < ActionController::IntegrationTest
  include LocaleHelper
  self.use_transactional_fixtures = false

  def setup
    ... some setup stuff here ...   
    @browser = Watir::Browser.new(:firefox)
  end

  def teardown
    @browser.close
  end

  test "something interesting" do
    @browser.goto "https://developer.paypal.com/"
    @browser.text_field(:name, "login_email").set "my_test_account@sourcesense.com"
    @browser.text_field(:name, "login_password").set "mysecret"
    @browser.button(:name, "submit").click

    @browser.goto "https://localhost"

    @browser.link(:id, 'loginlink').click
    @browser.text_field(:name, "email").set @user.email
    @browser.text_field(:name, "password").set @user.password
    @browser.button(:text, "Login").click

    # add_a_product_to_cart
    product = Factory(:product, :code => "a code", :categories => [@juve_store])
    Factory(:product_variant, :code => "03", :availability => 99, :product => product)
    @browser.goto "https://localhost/frontend/products/show/#{product.id}"
    @browser.button(:id, "add_to_cart").click

    @browser.link(:text, "Checkout").click
    @browser.link(:id, "gotobuy").click

    # choose "Paypal"
    @browser.radios.last.set

    @browser.link(:id, "gotobuy").click

    sleep 5
    assert @browser.text.include?("Payment for order #{last_order_number()}")

    @browser.text_field(:name, "login_email").set "my_test_buyer@sourcesense.com"
    @browser.text_field(:name, "login_password").set "yetanothersecrethere"
    @browser.button(:text, "Accedi").click
    @browser.button(:text, "Paga ora").click

    sleep 5
    assert @browser.text.include?("Il pagamento è stato inviato")

    @browser.button(:id, "merchantReturn").click
    assert_contain_waiting("Your purchase")
    assert_contain_waiting(last_order_number())

  end

private

  def last_order_number
    Order.last ? Order.last.number : ""
  end

end
Some comments here:
  • This is a spike, so please don’t say this test is too long and not well refactored
  • I had to put two sleep calls in two places (I gotta say that this specific test, involving paypal sandbox, is really slow due to the slowness in the paypal response time).
  • Anyway, this alpha version of webdriver is still lacking: I cannot say wheather this is a problem I’ll have even with future (possibly more stable) version of Webdriver.
Some references:

A (Still Brief) Experience on Using Selenium to Test a Rails + Ajax App

| Comments

This is a note to make a point on our (mine and my team’s) current use of Selenium to test the ajax behaviour in the Rails webapp we’re currently developing. Ajax replacing of part of the page is growing, and with it we have to face the classical question: “how do we test (I mean automatically :-) the ajax/javascript behaviours in our webapp?”. This is how we are trying to manage this issue now, after some days of spiking on Selenium, Watir and BlueRidge (I hope to write more on Watir and BlueRidge in some future post, because these two tools are worth speaking). Actually we are giving a try to the combination of Webrat + Selenium, since we already have a big test suite of integration test using Webrat, and have a good knowledge of the Webrat API. We added the selenium-client gem to be able to drive Selenium through the Webrat API. This is extracted from our test environment configuration file:
test.rb
...
config.gem 'selenium-client', :lib => 'selenium/client'
config.gem "webrat", :version => '>= 0.6.0'
...
Then, we defined a class from which all the selenium test cases will inherit. This class basically is used to
  • disable the transactional fixtures in Rails, to allow the browser process where Selenium runs to access the data prepared in the tests
  • configure Webrat with the “selenium” mode
  • be the place to collect helper methods as “login” or “logout”, used in many tests.
selenium_integration_test.rb
class SeleniumIntegrationTest < ActionController::IntegrationTest
  self.use_transactional_fixtures = false

  setup :switch_webrat_to_selenium
  def switch_webrat_to_selenium
    Webrat.configure do |config|
      config.mode = :selenium
      config.application_environment = :test
    end

    selenium.set_speed(100)       # default is 0 ms
    selenium.set_timeout(10000)   # default is 30000 ms
  end

  teardown :delete_cookies
  def delete_cookies
    selenium.delete_all_visible_cookies
  end

protected
 ...
 [other helper methods here, like login, logout, and so on...]

 ...
We also added a rake task to be able to launch all the selenium tests
test.rake
namespace :test do
  ...
  ...

  desc "Run Selenium Test"
  Rake::TestTask.new(:selenium) do |t|
    t.libs << "test"
    t.test_files = FileList['test/selenium/*test.rb']
    t.verbose = true
  end
end
One thing we learned through several repeated mistakes is that the Webrat API is different when called in the “selenium” mode then the one we were used to when using Webrat in the classical “rails” mode. For example, the “assert_have_selector” method for selenium only takes one argument, that is the CSS selector, while in the classical webrat mode, the same method takes another parameter to specify the expected content to match with (see this rdoc: http://gitrdoc.com/brynary/webrat/tree/master). So we had to define helper methods based on “assert_have_xpath” method using xpath to express the same intent of a method like assert_have_selector(css_selector, expected_content) Here is our helper method
selenium_integration_test.rb
  ...
  def assert_has_id id, text_content
    assert_have_xpath "//*[@id='#{id}'][1][text()='#{text_content}']"
  end
  ...