Peter Marklund

Peter Marklund's Home

Mon October 13, 2008
Programming

Rails Tip: SEO Friendly URLs (Permalinks)

There are several plugins already out there that can turn the typical Rails show path of /articles/show/1 into something more search engine friendly. I decided to roll my own implementation and it turned out to be fairly easy. My solution relies on the Slugalizer library by Henrik Nyh. First, I make sure we can turn any string into something URL friendly by patching the String class:

class String
  # Convert String to something URL and filename friendly
  def to_uri(max_size = 150, separator = "-")
    no_slashes = self.gsub(%r{[/]+}, separator)
    Slugalizer.slugalize(no_slashes.swedish_sanitation, separator).truncate_to_last_word(max_size, separator)
  end

  # We need this for SEO/Google reasons sincen å should become aa and Slugalizer translates å to a.
  def swedish_sanitation
    dup = self.dup
    dup.gsub!('å', 'aa')
    dup.gsub!('Å', 'aa')
    dup.gsub!('ä', 'ae')
    dup.gsub!('Ä', 'ae')
    dup.gsub!('ö', 'oe')
    dup.gsub!('Ö', 'oe')
    dup.gsub!('é', 'e')
    dup.gsub!('É', 'e')
    dup
  end
  
  def truncate_to_last_word(length, separator = "-")
    dup = self.dup
    if dup.size > length
      truncated = dup[0..(length-1)]
      if truncated.include?(separator)
        truncated[/^(.+)#{separator}/, 1]
      else
        truncated
      end
    else
      dup
    end
  end
end

All I have to do in my ActiveRecord model then is to override the to_param method:

  def permalink
    if name.present?
      "#{id}-#{name.to_uri}"
    else
      id
    end
  end

  def to_param
    permalink
  end

ActiveRecord will automatically ignore any non-digit characters after the leading digits in an id that you pass to it, but just to be on the safe side I added a before_filter to my application controller that will convert permalinks to proper ids:

  def convert_permalink_to_id
    if params[:id].present? && params[:id] =~ /\D/
      params[:id] = params[:id][/^(\d+)/, 1]
    end
  end

Credit for parts of this code goes to my cool programmer colleagues over at Newsdesk.se.

3 Comments

Mon October 06, 2008
Programming

Rails Hack: Auto Strip ActiveRecord Attributes

We have a user who unintentially enters a space after his email address. It seems that a lot of times it makes sense to automatically strip ActiveRecord model attributes before they are validated. Inspired by this post I came out with my own auto_strip method that adds a before validation callback which seems less intrusive than redefining the attribute setter method:

# Sometimes users accidentally enter space before or after text in text fields. Let's not punish them
# with an error message for this.
module ActiveRecord
  class Base
    def self.auto_strip(*attributes)
      attributes.each do |attribute|
        before_validation do |record|
          record.send("#{attribute}=", record.send("#{attribute}_before_type_cast").to_s.strip) if record.send(attribute)
        end
      end
    end
  end
end

Now in my ActiveRecord model I can say for example:

  auto_strip :price, :vat, :max_tickets

Maybe auto stripping would be useful as an option to the Rails validation macros?

Make a Comment

Wed September 17, 2008
Programming

Rails I18n: Array#to_sentence and :skip_last_comma

The Array#to_sentence method in ActiveSupport joins an array into a string with a locale dependent connector such as "and" for english and "och" for swedish. The sentence connector is determined by the I18n message support.array.sentence_connector. The issue is that in english you have a comma before the last connector whereas in swedish you don't. The existance of the last comma is set with the :skip_last_comma option. Ideally we would like this to always be true for the swedish locale. Therefore I added the I18n key support.array.sentence_skip_last_comma and patched the to_sentence method:

class ::Array
  alias_method :to_sentence_orig, :to_sentence
  def to_sentence(options = {})
    extra_options = {}
    skip_last_comma = I18n.translate(:'support.array.sentence_skip_last_comma')
    extra_options[:skip_last_comma] = skip_last_comma if skip_last_comma !~ /translation missing: /
    to_sentence_orig(options.reverse_merge(extra_options))
  end
end

Here is the corresponding spec:

    it "can join array elements with Array#to_sentence" do
      with_locale("en-US") { ["knatte", "fnatte", "tjatte"].to_sentence.should == "knatte, fnatte, and tjatte" }
      with_locale("sv-SE") { ["knatte", "fnatte", "tjatte"].to_sentence.should == "knatte, fnatte och tjatte" }
      with_locale("sv-SE") do
        ["knatte", "fnatte", "tjatte"].to_sentence(:skip_last_comma => false).should == "knatte, fnatte, och tjatte"
      end
    end

1 Comment

Wed September 10, 2008
Programming

Upgrading to Rails 2.2 and Drinking the I18n Koolaid

The other day I got tired of waiting for the Rails 2.2 release and upgraded my application from Rails 2.1 to edge. The process went a little something like this:

rake rails:freeze:edge

cd vendor/plugins
rm -rf rspec
rm -rf rspec_on_rails
git clone git://github.com/dchelimsky/rspec.git rspec_on_rails
git clone git://github.com/dchelimsky/rspec-rails.git
rm -rf rspec/.git
rm -rf rspec-rails/.git
cd ../../
./script/generate rspec

At this point I ran my Test::Unit tests and RSpec specs with rake and ended up with a single failing test and a bunch of warnings:

Overall, it was fairly easy to transition from 2.1 to edge. The really interesting part though was I18n. Thanks to the new I18n support in Rails I got to throw out the Simple Localization plugin that apparently is no longer supported, as well as my own hack to get error messages to be localized. I used the I18n demo app as a starting point and added config/locales/sv-SE.yml. It turned out the structure of I18n keys had changed a little since the demo app was written. You can use my file as a starting point or probably better, copy the following files into your single locales file and translate those:

vendor/rails/actionpack/lib/action_view/locale/en-US.yml
vendor/rails/activerecord/lib/active_record/locale/en-US.yml
vendor/rails/activesupport/lib/active_support/locale/en-US.yml

I source the locales file from config/initializers/i18n.rb:

I18n.default_locale = 'en-US'

LOCALES_DIRECTORY = "#{RAILS_ROOT}/config/locales/"
locale_files = Dir["#{LOCALES_DIRECTORY}/*.{rb,yml}"]
LOCALES_AVAILABLE = (locale_files.collect do |locale_file|
  File.basename(File.basename(locale_file, ".rb"), ".yml")
end + ['en-US']).uniq
locale_files.each { |locale_file| I18n.load_translations locale_file }

I am using the gibberish and gibberish_translate plugins and am quite happy with those. Of course, it would be nice if they were rewritten to use the new I18n API. Another TODO item is to move over attribute names from Gibberish to I18n. I have my own override of ActiveRecord::Base.human_attribute_name that is no longer needed now that the 2.2 version of the method so nicely does I18n message lookups (with a key naming convention like 'activerecord.attributes.model_name.attribute_name').

Thanks so much to Sven Fuchs and the I18n team, Jeremy Kemper, and all the others who made I18n a part of Rails! Code will be cleaner from now on and life easier...

3 Comments

Fri September 05, 2008
Programming

Test Driven Development with Ruby

I am closing my DreamHost account, partly because they have security issues and partly because I just don't need it anymore. I had to move my Test Driven Development with Ruby article to a new home here at marklunds.com. Hopefully Google will pick up the new page and update its index soon.

Make a Comment

Wed September 03, 2008
Programming

Modularizing Your Rails App with Concerns

In his keynote here at RailsConf Europe 2008, David (DHH) talked about living with legacy code, how we should enjoy it instead of trying to avoid it, and how it can give us new insights by showing us how we have grown as developers. I loved the keynote and it resonated really well with my own experiences. It's also highly relevant with my current work at Newsdesk and Simple Signup.

As an example of refactorings you might find yourself doing in legacy Rails apps, David showed us how to break a big application helper or a fat model into Ruby modules. The idea was to find groups of methods that represent a certain concern or aspect of your app and collect those in a module. This is typically not done for reuse but to make your code more readable and easier to navigate.

Last week I found myself creating a plugin for a certain aspect of my application, namely the acceptance of terms of its service. It was a minimalist plugin with just a helper method and a controller filter method. The code was highly application specific and thus it felt wrong to keep it in the plugin directory. After all, plugins are supposed to be shared across applications and my plugin was inherently tied to the application. Still, I wanted to have the ability to keep an aspect of my application in its own directory, especially when the aspect spans across several layers of MVC. The solution I came up with was to create a new directory RAILS_ROOT/app/concerns with a sub directory and an init.rb file for each concern, very much like with plugins. I then generated a concerns plugin to make sure my concerns get loaded:

Dir[File.join(RAILS_ROOT, "app", "concerns", "*", "init.rb")].each do |init_file|
  require init_file
end

I talked to David (DHH) about this approach and the funny thing was that he had experiemented with it too. David says the approach can be appropriate if you use it wisely. If you overuse it your concerns directory will end up being a new "garbage can" and bring you back to the problem that you were trying to solve with concerns in the first place... :-)

4 Comments

Tue September 02, 2008
The IT Business, Programming, Sweden

Introducing Rails Mentor

Me and fellow Rails developer Carl-Johan Kihlbom from Gothenburg have just founded Rails Mentor. Rails Mentor is a network of Ruby on Rails experts and we offer mentorship and training in anything related to Ruby on Rails. Me and Carl-Johan are committed to Rails best practices and together we have a broad experience of applying Ruby on Rails to varying types of projects. Now we would like to help spread this knowledge to others. If you need to be brought up to speed quickly with Rails or need a code or architecture review, please don't hesitate to get in touch with us.

We are currently at RailsConf in Berlin and we are giving free Rails Mentor t-shirts so come talk to us if you want one!

Make a Comment

Tue August 12, 2008
Programming

Installing Ruby on Rails on Ubuntu 8.04 Hardy Heron

I've compiled a set of detailed instructions on how to install Ruby on Rails on Ubuntu 8.04 Hardy Heron. The instructions are available in GitHub and they show you how to turn a clean Ubuntu 8.04 install into a production ready Ruby on Rails stack including MySQL, Nginx with fair proxy balancer, Monit, and Mongrel Cluster. There are a few additions and improvements I'd like to make:

Please help me point what else is missing or how I can improve.

I'm talking to GleSYS about the possibility of offering my production setup as an installable VPS image.

11 Comments

Wed August 06, 2008
Programming

Faster Capistrano Subversion Deployments with remote_cache

This is old news, but if you are deploying an application with Capistrano from Subversion and you find yourself waiting impatiently for the checkout to happen everytime, try adding this to deploy.rb:

set :deploy_via, :remote_cache

With this setting Capistrano will keep a Subversion checkout under shared/cached-copy, and on each deploy just do an svn update followed by a local copy of the whole tree into releases. I found this to be significantly faster than the default checkout on each deploy.

I am planning to switch to Git and GitHub now, and I am curious to see if this setting will affect deployment speed with Git. Given that a git clone in my experience is blazingly fast, maybe the remote cache won't be needed?

4 Comments

Sat August 02, 2008
Programming

Installing Telenor 3g Modem (Mobilt Bredband) on Ubuntu

As far as I know none of the turbo 3g GSM modems on the swedish market officially support Linux. However, I was able to get my Telenor modem working just fine on my Ubuntu Eee just now. I used the USB_ModeSwitch software along with swedish instructions from Hasain. Once my modem was recognized I ran sudo wvdialconf. I then edited my /etc/wvdial.conf to be:

[Dialer Defaults]
#Init1 = ATZ
Init1 = AT+CPIN=<YOUR PIN HERE>
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = Analog Modem
Baud = 9600
New PPPD = yes
Modem = /dev/ttyUSB0
ISDN = 0
Phone = *99#
Password = peter
Username = peter

Note that you have to change the PIN above. I then ran sudo wvdial and voila - I was online! Well, online on a slow and unreliable connection that is. Oh well, I'm thinking of changing to tre.se one of these days. They supposedly have the best 3g network.

2 Comments

Sat August 02, 2008
Programming

Asus Eee + Ubuntu + Rails

On thursday I bought myself an Asus Eee 900 - a tiny and cheap Linux powered laptop that is currently selling out in stores here in Sweden. I got a lot of attention in the office with this laptop and within a day it seemed every programmer in the office had an Eee on their desk.

I installed Ubuntu Eee and this was a huge improvement over the Linux OS that Asus provides. I am ashamed to admit that I haven't used Linux on the desktop for a long time and I was totally blown away by how advanced, slick, and user friendly Ubuntu has gotten.

Obviously, my ultimate goal was to install Ruby on Rails. At first I wanted to install from source in order to get an exact version and patch level of Ruby, namely the one that is officially recommended on ruby-lang.org. However, when attempting this various libraries were missing. I found a FiveRuns article listing the packages I needed but after I had installed them I ran into an issue with the MD5 library. In the end I resorted to using the ruby-full package which gives you the old tried and tested Ruby 1.8.6 patch level 111 (without the recent security patches). Here, roughly, are the steps I went through to set up my Rails environment:

  #####################################
  # RUBY
  #####################################

  sudo apt-get install ruby-full
  which ruby
  ruby -v
  # => ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]
  ruby -ropenssl -rzlib -rreadline -e "puts :success" 

  #####################################
  # RUBYGEMS
  #####################################

  # Get latest stable recommended release of RubyGems from rubygems.org
  wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz
  tar xzf rubygems-1.2.0.tgz
  cd rubygems-1.2.0/
  sudo ruby setup.rb
  # Not sure if/why this step is necessary
  sudo ln -s /usr/bin/gem1.8 /usr/bin/gem
  which gem
  gem --version
  # => 1.2.0

  #####################################
  # MYSQL
  #####################################

  sudo aptitude install mysql-server mysql-client libmysqlclient15-dev

  #####################################
  # Some useful Gems
  #####################################

  sudo gem install rails mongrel capistrano mysql

I haven't double checked those instructions for accuracy since I would have to re-install my OS to do that. If you find errors or have improvements, please let me know. Here is a sample of articles on the subject that you might want to check out if you want to dig deeper:

Make a Comment

Fri July 04, 2008
Programming

Rails Configuration: Yielding self in initializer

I've come across situations in Rails where you want to repeatedly invoke methods on some class with a long name and it gets ugly and tedious that you have to repeat the class name on every line. I just realized that unlike in the config/environments files the config object is not available in the config/initializer files. I use the AppConfig plugin to parameterize my application and I came up with the yield_self method to make my config/intializers/app_config.rb more readable:

# Method to supplement the app_config plugin. I want to crash early and be alerted if
# I have forgotten to define a parameter in an environment.
def config_param(name)
  AppConfig.param(name) do
    raise("No app_config param '#{name}' defined in environment #{RAILS_ENV}, " +
      "please define it in config/environments/#{RAILS_ENV}.rb and restart the server")
  end
end

class Object
  def yield_self
    yield self
  end
end

AppConfig::Base.yield_self do |config|
  config.site_name = "Simple Signup"
  config.admin_email = '"Simple Signup" <info@simplesignup.se>'
  config.exception_emails = %w(info@simplesignup.se)
  config.email_prefix = "[#{config.site_name}]"
  config.signup_timeout = 5
  config.send_activate_email = false

  config.transaction_fee = lambda do |price|
    price.blank? ? 0.0 : [10.0, 5.0+0.035*price.to_f].max.round(2)
  end

  config.bank_fee = lambda do |price, card|
    0.0
  end
end

ExceptionNotifier.exception_recipients = config_param(:exception_emails)
ExceptionNotifier.sender_address = %{peter_marklund@fastmail.fm}
ExceptionNotifier.email_prefix = "#{config_param(:email_prefix)} ERROR: "

The method config_param might be more appropriately named app_config. That will be a refactoring for another day though.

Make a Comment

Thu June 26, 2008
Programming

Ruby Gotcha: Default Values for the options Argument Hash

Like in Java, method arguments in Ruby are passed by reference rather than by copy. This means it's possible for the method to modify the arguments that it receives. This can happen unintentionally and be a very unpleasant surprise for the caller. A good example of this is the typical options = {} at the end of the method argument list. If you set a default value in that hash then that is a potential issue for the caller when the options hash is reused in subsequent calls (i.e. in a loop). See below for an example:

  def foreign_key(from_table, from_column, options = {})
    options[:to_table] ||= from_column.to_s[/^(.+)_id$/, 1].pluralize
    execute ["alter table #{from_table}",
            "add constraint #{foreign_key_name(from_table, from_column)}",
            "foreign key (#{from_column})",
            "references #{options[:to_table]}(id)",
            "#{on_delete_clause(options[:on_delete])}"].join(" ")
  end
  
  def foreign_keys(from_table, from_columns, options = {})
    from_columns.each do |from_column|
      foreign_key(from_table, from_column, options)
    end
  end

In the first invocation of foreign_key options[:to_table] will be set (if it isn't set already) to the destination table of the first column. The options[:to_table] value will be retained throughout the loop causing all foreign keys to point to the same table. The fix is to make to_table a local variable or to do add an "options = options.dup" line at the beginning of the method.

Lesson learned - avoid modifying the options hash and any other arguments that the caller doesn't expect will be modified.

1 Comment

Thu June 19, 2008
Programming

Rails Testing Tip: Use Global Fixtures to Avoid Fixture Mayhem

I'm in a Rails team with mixed opinions on whether to use fixtures. Therefore we have everything from RSpec specifications that use a lot of mocking/stubbing and don't touch the database, to specifications that set up their own databse data through helper methods and the specifications that I write that rely mostly on fixture data. What I have found is that when you don't use global fixtures (a setting in your test_helper.rb or spec_helper.rb file) you can run into situations where seemingly unrelated specifications/tests fail, and fail in different ways depending on if you run them in separation, through autotest, or with rake. What is going on is test data spillover/interference between tests. This can lead to very long and frustrating debugging sessions indeed. The best way to avoid this seems to be to turn on global fixtures. This will probably increase the specification run time, an issue that I partially adress by keeping the number of records in my fixture files to a minimum. Also, I prioritize test coverage and convenient access to a common set of test data over making my specifications run faster.

Make a Comment

Mon June 16, 2008
Programming

Rails Optimistic Locking - Not Worth it for Me

When I upgraded to Rails 2.1 ActiveRecord partial updates were turned on, i.e. when you save a record only those attributes that have changed are saved to the database. In theory, if you have two almost simulateneous updates, and you have a validation rule across several columns, then those updates can render the database record in an invalid state. Of course, in practice, this is very unlikely to happen. Nevertheless, I decided to turn on optimistic locking to be on the safe side. It turned out optimistic locking caused more issues than it was worth. Suppose you have an Article model that has many instances of Chapter and also that the Article uses a counter cache. Then you can run into this issue:

article = Article.first

article.chapters.create(:name => "Summary")
# => UPDATE articles SET chapters_count = chapters_count + 1,
#    lock_version = lock_version + 1 WHERE id = XXX;

article.publish_date = 1.days.from_now
article.save
# => throws ActiveRecord::StaleObjectError

I like partial updates though since they make it less likely that simulteneous updates will clobber, since you are only writing to the db what has changed. It also makes SQL statements in the log file more readable.

I'm abandoning optimistic locking for now though.

Make a Comment

Mon June 16, 2008
Programming

Ruby Gotcha: Symlinked Scripts and File.dirname(__FILE__)

If you have a Ruby script say in ~/src/ruby/my_script that you are symlinking to from ~/bin/my_script, then invoking File.dirname(__FILE__) in that script will yield the directory of the symlink not the directory of the script file. If you want the directory of the script file you can do this instead:

THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__

THIS_FILE will contain the path to the script file instead of the path to the symlink. This is valuable if say you want to require some Ruby library from your script and you are using a relative path.

2 Comments

Mon June 16, 2008
Programming

Rails Tip: Validating Option Arguments

I think it's a good convention to validate options arguments passed to methods, i.e. to make sure they have valid keys and values. Misspellings can otherwise lead to unnecessary debugging sessions. Rails comes with the assert_valid_keys method. I added the assert_value method:

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Hash #:nodoc:
      module Keys
        # Assert that option with given key is in list
        def assert_value(key, options = {})
          options.assert_valid_keys([