Development blog posts

Below you find an overview of our most recent blog posts.
Check regularly for updates!

 

How to create a mobile version of your (ruby on rails) site

You have several very useful libraries that do most of the styling / behavior for you:

iWebkit
jQTouch (sencha touch)
iui

We chose for iWebkit, but if you desire a more native feel you might choose jQTouch. The standard browsers on iPhone, Android, palmOS and Blackberry OS6 are all Webkit based. This ensures that sites based on iWebkit will work on all these devices, which is great! Copy the required javascript, images and stylesheets to your public folder.

Next, you have to detect if your user is on a mobile device.
Add the following code to your application controller:

helper_method :mobile_session?, :on_mobile?
def mobile_session?
if session[:mobile_param]
return session[:mobile_param] == “1″
else
return on_mobile?
end
end
   
def on_mobile?
request.user_agent =~ /Mobile|webOS|BlackBerry|Android|iPhone|Opera Mini/
end

Next, in config/initializers/mime_types.rb add the following:


Mime::Type.register_alias "text/html", :mobile

The only thing left to do now is to create mobile views. The extension for these files is .mobile.erb Look at the examples provided by the framework to see what html to use in your mobile views.

You’re done!

Some last tips:

  • You probably want to leave out some pages, and only show the mobile user the core functionality.
  • Always include a link to get to the normal site, which is done by appending ?mobile=0 to a link.

Some screenshots of the mobile version of one of our products:

mobile-screenshot mobile-screenshot2

Posted August 5th, 2010 by Martijn Lafeber
Logo
 

Development and release strategy with Git

At Innovation Factory we offer our clients two solutions for hosting our Innovation Suite applications: we can host it for them as a software-as-a-service solution or they can host it on premises, i.e. in their own IT environment. This means that we have several deployments of our suite that we need to maintain. In this article we show how we use Git branches and tags for this purpose.

Development with story branches

Every new feature and bugfix originates from a story in the Pivotal Tracker. The work that is done for these stories is committed to the master branch. This branch should be stable at all times; no tests should fail when you run the test suite in master. For this reason it is usually a good idea to work on a story in a separate branch and merge this branch back into master when the story is ready for acceptance.

Such a story branch has a name that briefly describes the story. This name is namespaced with ’stories’ and the initials of the developer who created it. For example, when Kyle Broflovski adds an exception handling mechanism to our application, he can do this in a branch called ’stories/KB/add-exception-handling’.

git checkout -b stories/KB/add-exception-handling

gitx-screen-1
The master branch of our example application

gitx-screen-2
Creating a story branch for adding an exception handling mechanism

gitx-screen-3
Building the exception handling mechanism in its own story branch

Kyle then becomes the ‘owner’ of the branch. This means that other people may also work in this branch, but Kyle is the one who is responsible for deleting it once it is merged into master. In other words: if you create a story branch, you’ll also have to destroy it when it is no longer in use.

git checkout master
git merge stories/KB/add-exception-handling
git branch -d stories/KB/add-exception-handling

gitx-screen-4
Merging the story branch into master

gitx-screen-5
Removing the story branch

If somewhere along the line you have pushed the branch to origin, make sure to delete it there too.

git push origin :stories/KB/add-exception-handling

The master branch is regularly deployed to our acceptance server, where the product owners can test the stories and either accept or reject them.

Releases and deployments

After a while we have made enough changes to our applications to do a new release. We record the release history in Git by tagging the last commit that is part of the release.

git tag kenny

gitx-screen-6
Creating a new release tag ‘Kenny’

The tag is a release name that all developers agree on. It is not a version number, because a name is easier to remember and talk about during development. However, externally we could use version numbers for our releases. So a release that is known as ‘Kenny’ by the dev team might be known as release 1.0 by our clients.

Ideally we would immediately upgrade all the deployments to the newest release. In practice this is not always possible, for example because we don’t have direct access to an on-premises deployment. This means that we not only have to keep track of which releases exist, but also of which deployment is currently on which release. For this purpose we give every deployment its own branch namespaced with ‘deployments’ (why we use branches and not tags here will become apparent in the next section).

Let’s assume that we have two deployments of our software: one is our SAAS install and one is an install for client Amazing-co. We then create two deployment branches, named ‘deployments/saas’ and ‘deployments/amazing-co’, respectively.

git checkout -b deployments/saas
git checkout -b deployments/amazing-co

gitx-screen-7
Creating deployment branches for the SAAS and Amazing-co deployments

When a deployment is on a certain release, both the deployment branch and the release tag will point to the same commit. That is, if no hotfixes have been applied…

Hotfixes

Sometimes a bugfix is so important that we cannot wait for the next release to roll it out to the various deployments. But we don’t want to do an intermediate release which contains all kinds of new and perhaps untested features just to apply one hotfix. Instead, we use separate hotfix branches for our releases. Once we do a release we not only create a tag, but also a corresponding hotfix branch. This branch has the same release name, but namespaced with ‘releases’. Thus, for a release named ‘kenny’ the hotfix branch is called ‘releases/kenny’.

git checkout releases/kenny

gitx-screen-8
Creating a hotfix branch for the ‘Kenny’ release

A hotfix branch should honour its name and contain only bug fixes, not new features. A hotfix can be a cherry-picked commit from the master branch, but also a release specific patch that will be replaced by a different fix in the upcoming release (e.g. if we cannot cherry-pick from the master branch because the affected code has been refactored in the mean time). For clarity we prefix commit messages of hotfixes with ‘HOTFIX:’.

gitx-screen-9
Cherry-picking a bug fix from master into the hotfix branch for the ‘Kenny’ release

Once a hotfix has been rolled out to a deployment, we update its deployment branch to reflect this. We do this by merging the hotfix branch into the deployment branch. The example below shows how we update our SAAS deployment with the latest hotfixes for the ‘Kenny’ release.

git checkout deployments/saas
git merge releases/kenny

gitx-screen-10
Applying a hotfix to the SAAS deployment by merging the hotfix branch for the ‘Kenny’ release into the SAAS deployment branch

When a deployment is updated to a new release, we discard the hotfixes in favour of the changes in the master branch. So if we upgrade our SAAS deployment from the hotfixed ‘Kenny’ release to the new ‘Stan’ release, we first rewind the SAAS deployment branch to the original ‘Kenny’ release tag and then fast-forward along the master branch to the ‘Stan’ release tag. However, before we can do all of this we need to remove the deployments/saas branch from our remote, otherwise it won’t let you push the changes as it thinks we’re behind of the remote branch.

git push origin :deployments/saas
git checkout deployments/saas
git reset –hard kenny
git merge stan
git push origin deployments/saas

gitx-screen-11
Before upgrading to the ‘Stan’ release, the SAAS deployment is at the head of the ‘Kenny’ hotfix branch

gitx-screen-12
The SAAS deployment has been rewinded to the ‘Kenny’ release tag

gitx-screen-13
The SAAS deployment has been fast-forwarded from the ‘Kenny’ release tag to the ‘Stan’ release tag

Once all the deployments have been updated to a new release, the hotfix branch for the previous release becomes ‘abandoned’. Although it is not in use anymore, we don’t delete it. It is part of the history of our applications and it may contain work that can be reused in another context in a new release.

gitx-screen-14
After upgrading the Amazing-co deployment to the ‘Stan’ release the ‘Kenny’ hotfix branch is now ‘abandoned’

Experimental branches

If you want to try out a nice new plugin or do some prototyping, an experimental branch is the way to go. An experimental branch will usually not be merged into master, but contains work that may be integrated into the master branch in an altered form. The name of an experimental branch is namespaced with ‘experiments’ and the initials of the owner. For example, Kyle Broflovski can try out a Facebook integration plugin in a branch called ‘experiments/KB/facebook-integration’.

git checkout -b experiments/KB/facebook-integration

gitx-screen-15
Experimental work on Facebook integration taking place in a separate branch

Wrap-up

We have been using the above strategy for a few months now and it really fits well in our development and release cycle. It would be interesting to know about other strategies, so feel free to share yours in the comments below.

Posted June 9th, 2010 by Lukas Spee
Logo
 

All the world’s flags in a sprite

For the short version: check out http://github.com/lafeber/world-flags-sprite

Lukas and I are working on a pet project when we’re traveling between Utrecht and Amsterdam. We’ve built a website that contains cheeses of many countries. I’ve been a fan of the famfamfam world flags for years, so it made sense to include them in the site.

You quickly get too many server requests when you have many separate images on one page. This is bad. So I thought of making a sprite of all the world’s flags. I’ve tried spriteme first but it didn’t work - my guess is there were too many images. Then I tried smartsprites which did the trick.

You run the command

./smartsprites.sh --root-dir-path ~/path_to_famfamfam/flags_iso/16

which generates a super long image and a new css file that looks like this:


.ad {
background-color:transparent;
background-repeat:no-repeat;
background-image: url('flags16.png');
background-position: left -336px;
}
.ae {
background-color:transparent;
background-repeat:no-repeat;
background-image: url('flags16.png');
background-position: left -352px;
}
.af {
background-color:transparent;
background-repeat:no-repeat;
background-image: url('flags16.png');
background-position: left -368px;
}
...

You’ll immediately notice a lot of duplication, which is unacceptable since there are more than 240 flags. I’ve created a simple ruby script to generate the css needed for the sprite. When run in the image directory, it generates a css file that looks like this:


.f16 .flag{background:url(flags16.png) no-repeat}
.f16 .ad{background-position:left -336px;}
.f16 .ae{background-position:left -352px;}
.f16 .af{background-position:left -368px;}
...

An example of how you can use it in your site:

<link rel="stylesheet" type="text/css" href="http://cloud.github.com/downloads/lafeber/world-flags-sprite/flags32.css" />


<ul class="f16">
<li class="flag ar">Argentina</li>
<li class="flag au">Australia</li>
<li class="flag at">Austria</li>
...
</ul>

See http://www.cheesewiki.com/ (which lists all the worlds’ cheeses) for an example.

Note that you can’t use this in a https environment, and that images won’t show if github is down. However, if people have visited a site that uses the same css file, they won’t have to download it again which speeds up things drastically!

Posted March 15th, 2010 by Martijn Lafeber
Logo
 

iPhone version of our blog

This is a story about the power of Rails.

It all started two days ago, when Seth Godin and Guy Kawasaki blogged about Appmakr.com. It allowed you to have an app in the Appstore based on your own RSS feed. The price they ask for this is $199,-

I believe that the appstore shouldn’t be polluted with RSS feeds. They should be websites instead of apps. Websites can have their own icon on the iPhone’s home screen.

With this in mind, I started to scaffold on my way back home. Commute by train is a bless.

The following gems were already installed:

I had used http://code.google.com/p/iui/ before.

Within two hours, http://iphonify.net/ was born. It allows anyone to claim their own url. For example, the iPhone version of this blog can be found at http://iphonify.net/ifdev.

If it weren’t for Rails - and the amazing plugins - I would have never been able to build this site that fast.

Posted January 8th, 2010 by Martijn Lafeber
Logo
 

Pair programming experiences

Last year we talked about pair programming with Obie Fernandez at Rails Underground. He was very enthusiastic about this practice, to the extent that the developers at his company Hashrocket, pair up full time. Inspired by this, we decided to try out pair programming ourselves. And we got quite excited about it too. So we chose to write a series of blog posts, or at least one, to spread the word and share our experiences. In this first post on this subject, we give a global introduction and we talk about our personal experiences with pair programming at Innovation Factory.

Pair programming is a software development technique in which two programmers work together at one work station. One types in code while the other reviews each line of code as it is typed in. The person typing is called the driver. The person reviewing the code is called the observer or navigator. The two programmers switch roles frequently.

While reviewing, the observer also considers the strategic direction of the work, coming up with ideas for improvements and likely future problems to address. This frees the driver to focus all of his or her attention on the “tactical” aspects of completing the current task, using the observer as a safety net and guide.

Here are our experiences so far:

Martijn Lafeber: I really enjoyed pair programming. Especially because we’re working on several projects - it allows you to get familiar with new ones pretty fast. You get distracted less than when you would work on your own; it forces you to stay focused.

Sam Aaron: I think that the main benefit of pair programming is that it forces you to externally communicate your ideas and code with another. This makes it the best vehicle for sharing programming-level domain knowledge I’ve yet found. I also love the fact that it provides you with an opportunity to learn and share lots of small little tips and tricks that you don’t normally find the time to talk about.

Lukas Spee: I have experienced lots of benefits from pair programming. Two brains know more than one and two pairs of eyes see more than one pair. So you make better design decisions — which gives you more confidence — and you discover errors earlier on — which saves you time. Another nice thing is that knowledge sharing takes place on the spot, as you code. And perhaps most importantly: it’s way more fun than solo programming.

Sjoerd Andringa: To me the power of pair programming lies in keeping each other focused and continuous communication. If a pair isn’t talking all the time they’re probably not getting the maximum out of pair programming. Ongoing code reviews and knowledge sharing are key. However, pair programming also poses challenges. It can for instance be difficult to adapt the right role. When do you intervene? When do you ask questions? The perfect balance is different for every partner. Also staying focused all day (e.g. not letting your partner down by being distracted by things like email) requires a lot of energy. Be sure to take short breaks.

Posted December 31st, 2009 by Martijn Lafeber
Logo
 

never use sudo gem install again

It always bothered me that when you do a gem install instead of a sudo gem install you usually get the following errors:


WARNING:  Installing to ~/.gem since /usr/local/lib/ruby/gems/1.8 and
/usr/local/bin aren't both writable.
WARNING:  You don't have /Users/martijn/.gem/ruby/1.8/bin in your PATH,
gem executables will not run.

Most of the times, gems are all over the place. I just want them all to be in one place - ~/.gem - and I don’t want to type sudo gem install … again. And, most of all, I don’t want to see those annoying warnings when I type gem install …

Getting rid of the second warning is easy, just type

export PATH="$HOME/.gem/ruby/1.8/bin:$PATH"

To get rid of the first warning, put

:gemhome: /Users/{your_username}/.gem/ruby/1.8/

in the ~/.gemrc file.

All your gems will now be in ~/.gem/ and you’ll no longer see those annoying warnings when you do a gem install. YES!

–== UPDATE ==–

Apparently, instead of editing ~/.gemrc you can also type:

export GEM_PATH="$HOME/.gem/ruby/1.8"
export GEM_HOME="$HOME/.gem/ruby/1.8"

Thanks to Pat Allan, who wrote about it here

Posted October 26th, 2009 by Martijn Lafeber
Logo
 

Ruby on Rails, Git submodules and Vlad the Deployer

Do you want to deploy your Ruby on Rails project with Vlad the Deployer? And does your project include Git submodules? If the answer to both questions is yes, it is likely that you’ll run into trouble as Vlad does not offer support for submodules. In this post I will walk you through my solution for this problem. For that I’m gonna assume you know the general technicalities about Vlad and that you have it running, being able to remotely perform tasks and everything. Just not able to deploy your project including its submodules. Soon you will be!

Of course there have been others with this problem. Here’s a repo with an adjusted version of Vlad that handles submodules. (And it comes with some other improvements too.) However, it is based on an older version of Vlad and I would like to use the latest version available (2.0.0 at the time of writing). Another downside is the fact that it copies over the whole Git project, including .git folders, to the release directory, which means you’ll end up with all of Git’s objects in each of the releases that remain on the host; not very efficient in terms of disk space usage.

In my approach we’ll override two methods in the Vlad::Git class (From the the vlad-git gem) and we’ll use a custom Shell script too. The process has been separated into 3 simple steps.

1. Creating a module for the overrides

We’re gonna put our versions of the methods that we want to override in a separate module. I have named it Vlad::Git::Submodules and saved it to lib/vlad/git/submodules.rb:


module Vlad::Git::Submodules
  # Our methods go here
end

To make sure the methods in this module are available to Vlad you’ll need to edit your project’s Rakefile:


#...

begin
  require 'vlad'
  Vlad.load :app => :passenger, :scm => :git

  # Add these lines to inject our methods:
  require 'lib/vlad/git/submodules'
  source.instance_eval do
    extend Vlad::Git::Submodules
  end

rescue LoadError
  puts 'Could not load Vlad'
end


2. Overriding Vlad::Git#checkout

Vlad::Git’s checkout method is responsible for cloning the latest revision of your project from the repository. It doesn’t initialize submodules, so we’ll need to make it do that by adding one line of code:


module Vlad::Git::Submodules

  # A copy of Vlad::Git#checkout, but also inits submodules.
  #  
  def checkout(revision, destination)
    destination = File.join(destination, 'repo')
    revision = 'HEAD' if revision =~ /head/i

    [ "rm -rf #{destination}",
     "#{git_cmd} clone #{repository} #{destination}",
     "cd #{destination}",
     "#{git_cmd} checkout -f -b deployed-#{revision} #{revision}",
     "git submodule -q update --init", # This line is added to make sure submodules are fetched too.
     "cd -"
    ].join(" && ")
  end

end

3. Overriding Vlad::Git#export

The original export method copies over the clone to the location for the new release (e.g. releases/20090101154101/). It uses git archive to create a tarball of the project and then untars it in the release directory in order to make sure no Git project files are included. (You don’t want to end up with all of Git’s objects in the actual release directories for reasons mentioned above.) The problem here is that git archive does not archive the files in directories which are submodules. This is where we need our own Shell script. The script is called git-archive-all.sh (Download from github) and it lets you create a tarball that does include submodules! Put the script in a location that’s in your PATH first, e.g. /usr/bin, and make it executable. Now we can override the export method to make use of it:


module Vlad::Git::Submodules

  #...
  def checkout(revision, destination)
    #...
  end

  # A copy of Vlad::Git#export, but also exports submodules.
  #  
  def export(revision, destination)
    [ "mkdir -p #{destination}",
     "cd repo",
     "git-archive-all.sh #{application}.tar", # write complete project to a tarball with git-archive-all
     "cat #{application}.tar | (cd #{destination} && tar xf -)", # untar it in the release directory
     "rm -f #{application}.tar", # remove the temporary tarball
     "cd -",
     "cd .."
    ].join(" && ")
  end

end

Now rake vlad:update will use these methods and that’s really all there is to making Vlad deploy your submodules with your project in a clean, disk space efficient way.

Note that nothing is perfect and there are some known downsides of this approach:

  • git-archive-all.sh does not support archiving a specific revision, therefor the revision argument in #export is ignored. Making the Shell script accept a revision argument would be trivial though.
  • git-archive-all.sh seems to break when there’re no submodules in the project, so make sure you only apply this approach when you really need it.

Good luck!

Posted September 30th, 2009 by Sjoerd Andringa
Logo
 

A simple everyday workflow with Git

A great thing about Git is that it is flexible enough for you to develop your own favourite workflow, rather than have it dictate to you which workflow you should use. Personally, I’ve spent quite some time finding a nice way to apply Git in my day to day workflow. The resulting workflow is pretty straightforward and has some nice characteristics: it avoids unnecessary merging (by using the ‘git rebase’ command where appropriate), it keeps your local master branch stable and detects possible merge conflicts at an early stage. I thought it would be nice to share it in a blog post as a possible inspiration for others. So here it is:

1. Make sure the local master branch is in sync with the upstream. A good strategy for this is to first do a fetch and then a rebase:
git fetch
git rebase origin/master

You can use a ‘git pull’ instead if the local branch and the remote branch have not diverged. Don’t use the pull command if the branches have diverged, because this will result in an additional merge.

2. Create a new branch with a descriptive name for this feature/bugfix:
git checkout -b add-single-sign-on

3. Start developing and testing. Sorry, no code snippet for that one ;-)

4. Commit frequently, but only when all tests pass:
git commit -m "Created db migration for SSO"

5. After every commit, make sure you’re in sync with the upstream:
git fetch
git rebase origin/master

6. When you are ready to push your work to the upstream, repeat the previous step and then switch back to your local master branch:
git switch master

7. Sync the local master branch with the upstream:
git fetch
git rebase origin/master

Again, you can use a ‘git pull’ instead if the local and remote master branches have not diverged.

8. Merge the changes into the local master branch:
git merge add-single-sign-on

9. Push the changes to the upstream:
git push

10. Delete the local branch:
git branch -d add-single-sign-on

In this workflow, you never work directly in the master branch. This way it will always be stable, so you can easily park your current work and branch off your local master branch again when a hotfix is needed.

Your work is only pushed at the end of the line, because prematurely pushing changes can result in unwanted merge conflicts later on. By deleting the local branch at the end, you prevent yourself from continuing to work on changes that have already been pushed and running into this kind of errors.

Posted September 28th, 2009 by Lukas Spee
Logo
 

Dealing with outsourced redesign

One of our clients needed a redesign of their website. There are a few important lessons we’ve learned from dealing with external designers, and outsourced html/css implementation.

It starts with the designer creating a new Photoshop design for every page. She does so by clicking through the entire website.

When you have a large website, it’s very likely that not all elements are taken into account. Things to look out for:

  • Pages that are missing temporarily. For example, when there are currently no vacancies available.
  • Recurring elements should be repeated on every page. For us, it’s logical that e.g. a breadcrumb should be everywhere. For the one doing the html/css, it’s not.
  • Are all error messages and notifications designed as well?
  • Long words like Vaselinecetomacrogolcrème (yes it’s a real world example) may break up the site.

When all this is done, the psd files are sent to an external company to be transferred into html/css.

Now it gets interesting.

  • You want to have haml and sass? It’s probably not going to happen. Hopefully this will change in the future.
  • Make sure that elements that are repeated over multiple pages share similar code. Our first design had completely different css per template, resulting in a 6500 line css file. A typical line in that css file looked like:.T02 ul.pagingTop li, .T02 ul.pagingBottom li, .T03 ul.pagingTop li, .T03 ul.pagingBottom li, .T16 ul.pagingBottom li, .T19 ul.pagingBottom li {...}The .T?? numbers represent the template numbers. Needless to say, even a small change required more than a dozen changes all over the css file.
  • The previous site used the prototype javascript library, the external guys preferred jQuery. Luckily, there’s a plugin called jrails.
  • As usual, the creation of the pages takes much longer than expected. Make sure that they’ll send you new stuff as soon as something new is finished. While you’re waiting, make sure that you have something else to do (like writing a blog)
  • Form buttons should be input type="button" instead of a href="#" You don’t want to write extra javascript to get your forms submitted, or redo the html/css of the buttons.
  • Check the site in IE6. It’s a terrible, outdated browser but unfortunately lots of people still use it. For transparent png support, we use the DD_belatedPNG fix.
  • If some minor elements are missing, rather implement them yourself than ask them to do it. It probably saves you both time and money.
  • There’s the language barrier. The new design in the site is in Dutch, which they don’t speak. So ids and classes in css are in Dutch and often misspelled. For example; facturen (invoices) has singular factuur while they spelled as factur. This is actually a minor thing that should make you smile. It’s easily renamed afterward. We prefer to have css classes and id’s in English because we have English employees. Besides that, the site will be rolled out in multiple countries.

It’s quite crucial to have someone in house who understands html and css as well, since putting the new code in the templates and partials is never going to be a simple copy/paste job.

Posted September 9th, 2009 by Martijn Lafeber
Logo
 

Metamagical class variables in Ruby part 2

In my previous post Metamagical class variables in Ruby I described an oddity of Ruby causing a problem in a plugin I was writing at the time. It was about class variables that were set by dynamically defined methods. After a lot of asking around the general advise was just to avoid class variables, but I still couldn’t sleep.

I just had to figure it out, so I took another look at it and experimented away in the metamagical world of Ruby. And that has given me some new insights on the odd behaviour, of which I’m till not sure whether it’s a feature or a quirk.
It turns out that the manner in which a method that sets a class variable has been defined affects the class the class variable is actually stored in. This can be the class you intended to set it on, its metaclass or both. And to make it a little more complicated this also depends on whether the class variable had already been initialized or not.

This script shows several ways of adding a setter method to a class and for each of those outputs whether it has set the class variable on the class itself or its metaclass after calling it. Note that no class variables are being initialized anywhere before calling the method:

On class? On metaclass?
1. def within class definition yes no
2. def outside class definition yes yes
3. class_eval yes yes
4. instance_eval yes yes
5. define_method on metaclass yes yes
6. Include module into metaclass no yes
7. Extend with module no yes

Methods 2 to 5 set the class variable on both the class itself and its metaclass, however if the class variable has already been initialized on the class itself, calling the method WILL NOT override the currently set class variable but WILL set a new class variable only on the metaclass!

Now lets go back to the original case which caused problems:


module ClassMethods
  def authenticate_with_header(header, options = {})
    #...
    @@configuration = {}
    cattr_reader :configuration
    #...
  end
end

ActionController::Base is extended with this module, making the authenticate_with_header method available from within any controller’s class definition. Calling this method will initialize the class variable @@configuration. Now according to the results of our previous experiment calling a setter method which was mixed in by means of extending with a module initializes the class variable only on the metaclass. Now if you take a look at cattr_reader’s implementation you can see it uses class_eval to evaluate code that does a number of things: 1. it checks whether a class variable with the specified name already exists, 2. if it doesn’t it initializes the class variable with a value of nil and 3. it creates a getter method. As we’ve seen, code mixed in using class_eval will look on the class itself for class variables, i.e. not on its metaclass. This means calling cattr_reader will initialize another class variable on the controller class itself (step 2). Now there are two class variables, on both the class itself and its metaclass being respectively nil and {} (an empty hash). The getter method will look on the class itself, find nil there and return that. The class variable on the metaclass just remains there, orphaned, never to be requested again…

Congratulations if you’re still with me at this point. Your efforts haven’t been pointless, keep up just a little longer, because next I will tell you how to get around this ‘problem’. To avoid confusion we need to make sure we’re never setting class variables on metaclasses. Fortunately there’s a method that lets you set class variables (opposed to a regular assignment using ‘=’), and it will always do this on the bottom level class for you. It is called class_variable_set. The following example illustrates its difference with normal assignment:


class Foo
  def self.metaclass
    class << self; return self; end
  end
end

module Setters
  def set_a
    @@a = true
  end
  def set_b
    class_variable_set "@@b", true
  end
end

Foo.extend Setters

Foo.set_a
Foo.set_b

Foo.class_variables # => ["@@b"]
Foo.metaclass.class_variables # => ["@@a"]

The advise given in my previous post was to avoid the usage of class variables and I’d say that still holds true. However, if that is out of your control or when class variables are really the only way to solve your problem then be sure to use class_variable_set and class_variable_get, especially when you’re dealing with dynamically added methods like in Rails plugins.

Posted September 1st, 2009 by Sjoerd Andringa
Logo