Rails on the Run

Rails experiments by Matt Aimonetti

Ruby is sexy, Ruby is cool and its metaprogramming potential offers some really cook features. However you might not realize that your cleverness is slowing down your code.

Today I was working on cleaning up merb_helper a Merb plugin that brings a lot of the stuff Rails developers are used to. In Merb we aim for speed and try to avoid magic.

merb_plugin didn’t receive a lot of love from the main contributors but few features were added by different contributors and the code became hard to maintain.

Looking at the code I quickly found this bad boy:

(Old Merb Time DSL using metaprogramming)

module MetaTimeDSL

    {:second => 1,
     :minute => 60,
     :hour => 3600,
     :day => [24,:hours],
     :week => [7,:days],
     :month => [30,:days],
     :year => [364.25, :days]}.each do |meth, amount|
      define_method "m_#{meth}" do
        amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
        self * amount
      end
      alias_method "m_#{meth}s".intern, "m_#{meth}"
    end

  end
  Numeric.send :include, MetaTimeDSL

The above code looks awful to me and I decided to rewrite it a way I thought would be more efficient:

 module TimeDSL

    def second
      self * 1
    end
    alias_method :seconds, :second

    def minute
      self * 60
    end
    alias_method :minutes, :minute

    def hour
      self * 3600
    end
    alias_method :hours, :hour

    def day
      self * 86400
    end
    alias_method :days, :day

    def week
      self * 604800
    end
    alias_method :weeks, :week

    def month
      self * 2592000
    end
    alias_method :months, :month

    def year
      self * 31471200
    end
    alias_method :years, :year

  end
  Numeric.send :include, TimeDSL

To make sure I was right, I run the following benchmarks:

require 'benchmark'
TIMES = (ARGV[0] || 100_000).to_i

Benchmark.bmbm do |x|

  x.report("metaprogramming 360.seconds") do
    TIMES.times do
      360.m_seconds
    end
  end

  x.report("no metaprogramming 360.hours") do
    TIMES.times do
      360.seconds
    end
  end

  x.report("metaprogramming 360.minutes") do
    TIMES.times do
      360.m_minutes
    end
  end

  x.report("no metaprogramming 360.minutes") do
    TIMES.times do
      360.minutes
    end
  end

  x.report("metaprogramming 360.hours") do
    TIMES.times do
      360.m_hours
    end
  end

  x.report("no metaprogramming 360.hours") do
    TIMES.times do
      360.hours
    end
  end

  x.report("metaprogramming 360.days") do
    TIMES.times do
      360.m_days
    end
  end

  x.report("no metaprogramming 360.days") do
    TIMES.times do
      360.days
    end
  end

  x.report("metaprogramming 360.weeks") do
    TIMES.times do
      360.m_weeks
    end
  end

  x.report("no metaprogramming 360.weeks") do
    TIMES.times do
      360.weeks
    end
  end

  x.report("metaprogramming 18.months") do
    TIMES.times do
      18.m_months
    end
  end

  x.report("no metaprogramming 18.months") do
    TIMES.times do
      18.months
    end
  end

  x.report("metaprogramming 7.years") do
    TIMES.times do
      7.m_years
    end
  end

  x.report("no metaprogramming 7.years") do
    TIMES.times do
      7.years
    end
  end

end

 Rehearsal ------------------------------------------------------------------
metaprogramming 360.seconds      0.130000   0.000000   0.130000 (  0.133164)
no metaprogramming 360.hours     0.050000   0.000000   0.050000 (  0.042655)
metaprogramming 360.minutes      0.130000   0.000000   0.130000 (  0.133327)
no metaprogramming 360.minutes   0.040000   0.000000   0.040000 (  0.042401)
metaprogramming 360.hours        0.140000   0.000000   0.140000 (  0.134312)
no metaprogramming 360.hours     0.040000   0.000000   0.040000 (  0.043125)
metaprogramming 360.days         0.130000   0.000000   0.130000 (  0.134949)
no metaprogramming 360.days      0.050000   0.000000   0.050000 (  0.043745)
metaprogramming 360.weeks        0.130000   0.000000   0.130000 (  0.135581)
no metaprogramming 360.weeks     0.050000   0.000000   0.050000 (  0.043544)
metaprogramming 18.months        0.130000   0.000000   0.130000 (  0.135234)
no metaprogramming 18.months     0.050000   0.000000   0.050000 (  0.044354)
metaprogramming 7.years          0.140000   0.000000   0.140000 (  0.144062)
no metaprogramming 7.years       0.050000   0.000000   0.050000 (  0.044392)
--------------------------------------------------------- total: 1.260000sec

                                     user     system      total        real
metaprogramming 360.seconds      0.130000   0.000000   0.130000 (  0.132567)
no metaprogramming 360.hours     0.040000   0.000000   0.040000 (  0.042777)
metaprogramming 360.minutes      0.140000   0.000000   0.140000 (  0.132554)
no metaprogramming 360.minutes   0.040000   0.000000   0.040000 (  0.043193)
metaprogramming 360.hours        0.130000   0.000000   0.130000 (  0.133027)
no metaprogramming 360.hours     0.050000   0.000000   0.050000 (  0.042613)
metaprogramming 360.days         0.130000   0.000000   0.130000 (  0.138637)
no metaprogramming 360.days      0.050000   0.000000   0.050000 (  0.043213)
metaprogramming 360.weeks        0.130000   0.000000   0.130000 (  0.134049)
no metaprogramming 360.weeks     0.040000   0.000000   0.040000 (  0.043713)
metaprogramming 18.months        0.140000   0.000000   0.140000 (  0.134941)
no metaprogramming 18.months     0.040000   0.000000   0.040000 (  0.043980)
metaprogramming 7.years          0.150000   0.000000   0.150000 (  0.143389)
no metaprogramming 7.years       0.040000   0.000000   0.040000 (  0.044585)
 0.136591)

The metaprogramming version of the same implementation is almost 3 times slower!

Moral of the story: be careful when using metaprogramming, you might end up slowing down your code considerably.

BarCamp

Just a reminder, this coming week end, San Diego presents BarCamp for the third time.

This time, the chosen Venue is Microsoft in La Jolla

I was thinking about preparing 2 intro talks, one on Merb and one on Unobtrusive Javascript (jQuery, Prototype + LowPro etc…), then we’ll see the crowd and what people are interested in. Feel free to give me your feedback, suggestions…

I also heard that on top of the awesome people from san Diego, some other important people are coming just for the event:

Warning

Based on previous BarCamps, you might end up seeing a guy wearing a kilt who talks about how he is his own imaginary friend, having to look at ActionScript code, meet a lot of interesting people and even maybe learn about lock picking.

last year barcamp sd crowd

outside

powerpoint karaoke

tshirt printing

presentation

I even heard rumors saying that Ryan Felton is organizing another wii tournament.

As you’ve probably heard, Rails now moved to its own GitHub repo.

If, like me you were a heavy piston user, you are wondering how you will be able to do the same thing if you switch to git.

First off, you need to know that Piston will soon support git. As a matter a fact it already does. At least you can download a beta version from François’s blog.

You can also go with giston/braids which was meant to make the svn/switch easy on you. I heard rumors that evilchelu might not keep on developing this project. You might want to check with him.

Personally I didn’t really like using any of these solutions. Rails also came with its’ own approach. (rake rails:freeze:edge)

When I recently worked on Merb’s freezer, I discovered the power of git submodules.

Submodules allow you to import “modules” from other git repos inside your own repo. Basically they do what piston does for SVN, apart that submodules are built-in git. Of course it has an expected limitation, you can only add git submodules.

The good news is that Rails moved to git and now you can “freeze” Rails as a submodule and update really easily!

First thing first, you need to move your project to git. If you are not confident it’s a good move yet, you can use “git-svn”. However, I would personally recommend you don’t. I did that for few months and when I finally moved to git only, it was a pain to restructure the entire path of the app.

Anyways, let’s say you created a new github project and if still wish to use git svn do:

1
2
3
4
$ git-svn import svn://path-to-your-svn-repo project-name
$ cd project-name
$ git remote add origin git@github.com:mattetti/project-name.git
$ git push origin master

Your project is now under git, but if you pistonized Rails, you can’t update it anymore :(

Do not fear my dear friend and do as follows:

1
2
3
4
5
6
$ rm -rf vendor/rails
$ git commit
$ git submodule add git://github.com/rails/rails.git vendor/rails
$ git submodule init
$ git commit
$ 

That’s it you are done :)

Next time you want to update just do:

1
2
3
$ cd vendor/rails
$ git remote update
$ git merge origin/master

or you can also do

1
2
$ cd vendor/rails
$ git pull

(yes, each plugin acts a a normal git repo)

A quick note for gitHub users. If you browse your repo you won’t see the vendor/rails folder and might freak out. Don’t! Git is smart and wants to stay slim, instead of copying the files over, it just creates a reference to the original repo. If you try to pull your project in another folder you will see that the Rails folder gets created as expected.

Personally, when plugins are not available in a git repo I usually do a simple svn export to my project vendor’s folder. If I need to modify one of these plugins, I just import it to github and work on it from there.

You might still want to stick to Piston or Braids and that’s fine, but now you won’t have an excuse not to switch to Git :)

UPDATE: I just found out that Graeme wrote a nice detailed post about tracking plugins using git, check it out