Archive for December, 2008

Vim as a Ruby on Rails IDE

Monday, December 29th, 2008 By: Daniel

In the words of Tim Pope “TextMate may be the latest craze for developing Ruby on Rails applications, but Vim is forever”

Here’s how I use vim (and gnome-terminal) to program in Rails on Ubuntu.

I have a ruby script that opens two gnome-terminals each with 9 tabs each. That may seem like a lot, but each one is assigned a specific purpose and by now my fingers know just where to go depending on the task at hand. For example, models, views and controllers each get their own tab. So if I need to edit a model my fingers will do an vim model_name.rb (or :e model_name.rb if I’m already in vim). And of course bash aliases and tab_completion minimizes the actual number of keystrokes required. In the same way, I know exactly where to go to watch the log, play in the console, use the SCM or anything else.

My Vim Rails IDE

So that’s what it actually looks like. Notice how I have Gmail in the background so I can know immediately when I get a new email or IM (and ignore it at my leisure). Not shown is a separate Firefox window running on my other monitor so that I can see both the code and the running application at the same time.

Now, I have some doubts as to how well my solution would work for other people as it requires a bit of discipline to make sure everything has a place and to train yourself to always do things in the right place. It seems most people prefer to have only a few windows (or tabs or buffers or whatever) open and use some sort of fuzzy finder or file explorer to find the file they want to work on next. But that’s what I love about vim. I can come up with a custom workflow that works for me instead of conforming to the whims of some dictatorial IDE. True, the learning curve was quite steep at first, and I still occasionally invest some of my time in learning how to use additional features and customizations, but now contemplating using another text editor is like going back to riding tricycles after learning to fly jet planes.

Here’s the current version of the ruby script in case anyone’s interested. I call it zvim_rails_ide so I can use it with a quick zv<tab>.

#!/usr/bin/env ruby
 
if ARGV.size.zero?
  puts "Usage: #{File.basename($0)} name_of_project"
  exit
end
 
RAILS_ROOT = File.join(Dir.pwd, ARGV[0])
RAILS_ROOT << '/' unless RAILS_ROOT[0] =~ %r{/$}
 
unless File.exists?(RAILS_ROOT)
  puts "#{RAILS_ROOT}: No such file or directory"
  exit
end
 
ls = "ls --color"
scm = File.exists?('.git') ? 'git status' : 'svn status'
test_dir = File.exists?("#{RAILS_ROOT}spec") ? 'spec' : 'test'
 
[
  {
    :geometry => "140x38+90+270",
    :tabs => [
      {:path => "app/models", :command => ls},
      {:path => "app/views", :command => ls},
      {:path => "app/controllers", :command => ls},
      {:path => "app/helpers", :command => ls},
      {:path => "public", :command => ls},
      {:path => "vendor", :command => ls},
      {:path => test_dir, :command => ls},
      {:path => test_dir, :command => ls},
      {:path => "", :command => ls}
    ]
  },
  {
    :geometry => "141x32+185+110",
    :tabs => [
      {:path => "", :command => scm},
      {:path => "", :command => ls},
      {:path => "lib", :command => ls},
      {:path => "", :command => "ruby script/console"},
      {:path => "", :command => ls},
      {:path => "config/locales", :command => ls},
      {:path => "", :command => ls},
      {:path => "", :command => ls},
      {:path => "", :command => ls}
    ]
  }
].each do |window|
  tabs = window[:tabs].map {|tab| "--tab-with-profile=DEFAULT --working-directory='#{RAILS_ROOT}#{tab[:path]}' -e \"bash -c '#{tab[:command]}; bash'\""}.join(' ')
  system "gnome-terminal --geometry #{window[:geometry]} #{tabs}"
end

Using Rails’ New I18n Support in Real Life: Part the Fourth

Tuesday, December 16th, 2008 By: Daniel

So after tediously going through your entire site and extracting all displayed strings to a separate translation file, how do you know you didn’t miss something somewhere? My solution was to create a quick rake task that machine translates my English yaml file to something else. A quick sudo aptitude install bsdgames and I had pig latin at my fingertips. So I switched to that and browsed through the site and looked for anything that hadn’t changed. And I found quite a bit of stuff that I had missed.

# lib/tasks/pig.rake
desc "Creates a config/locales/pig-US.yml from config/locales/en-US.yml"
task :pig => :environment do
  DEFAULT_LOCALE = 'en-US'
  File.open(File.join(RAILS_ROOT, 'config', 'locales', 'pig-US.yml'), "w") do |fout|
    File.readlines(File.join(RAILS_ROOT, 'config', 'locales', "#{DEFAULT_LOCALE}.yml")).each do |line|
      unless line =~ /"/
        fout.puts line.sub(DEFAULT_LOCALE, 'pig-US')
      else
        key, *translation = line.split(':')
        translation = translation.join(':')
        translation.split(/(".*?")/).each do |quote|
          if quote =~ /"/
            quote.gsub!('"', '')
            string = quote.split(/(\{\{.*?\}\})/).map do |s|
              (s =~ /\{\{/) ? s : `echo "#{s.gsub('$', '\$')}" | pig`.chomp
            end
            translation.sub!(quote, string.join)
          end
        end
        fout.puts "#{key}:#{translation}"
      end
    end
  end
end

P.S. Note to anyone that actually tries to use this: This is just a quick and dirty script and will probably require some modification to work in your situation. In particular it requires all the strings you want translated to be within double quotes.

Using Rails’ New I18n Support in Real Life: Part the Third

Monday, December 15th, 2008 By: Daniel

What happens when you add a new string to your default locale file and forget about the other languages? Well, by default it’ll raise a MissingTranslationData and your users will see an ugly string the likes of “es-MX, marketing_interface, index, title“. Wouldn’t it be better to at least try and default to English? My guess is that most people would prefer to see the message in another language than some cryptic error message. And while we’re wishing, don’t you think the localize method shouldn’t die a noisy death when you happen to pass it a nil?

My solution? A custom I18n backend. I simply copy/pasted the code from the default “Simple” backend, tweaked a few lines and I was good to go!

# In some file that gets sourced on startup, like maybe in your config/initializers directory
module I18n
  module Backend
    class Moki < Simple
 
      def translate(locale, key, options = {})
        raise InvalidLocale.new(locale) if locale.nil?
        return key.map { |k| translate(locale, k, options) } if key.is_a? Array
        reserved = :scope, :default
        count, scope, default = options.values_at(:count, *reserved)
        options.delete(:default)
        values = options.reject { |name, value| reserved.include?(name) }
        entry = lookup(locale, key, scope)
        if entry.nil?
          entry = default(locale, default, options)
          entry ||= lookup(I18n.default_locale, key, scope)
          raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
        end
        entry = pluralize(locale, entry, count)
        entry = interpolate(locale, entry, values)
        entry
      end
 
      def localize(locale, object, format = :default)
        return nil if object.nil?
        raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
        type = object.respond_to?(:sec) ? 'time' : 'date'
        formats = translate(locale, "#{type}.formats")
        format = formats[format.to_sym] if formats && formats[format.to_sym]
        format = format.to_s.dup
        format.gsub!(/%a/, translate(locale, "date.abbr_day_names")[object.wday])
        format.gsub!(/%A/, translate(locale, "date.day_names")[object.wday])
        format.gsub!(/%b/, translate(locale, "date.abbr_month_names")[object.mon])
        format.gsub!(/%B/, translate(locale, "date.month_names")[object.mon])
        format.gsub!(/%p/, translate(locale, "time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
        object.strftime(format)
      end
 
    end
  end
end
I18n.backend = I18n::Backend::Moki.new

Using Rails’ New I18n Support in Real Life: Part the Second

Friday, December 12th, 2008 By: Daniel

A few more thoughts on handling I18n for real projects.

Read the rest of this entry »