Phusion white papers Phusion overview

default_value_for 1.0.5 released: default attribute values for ActiveRecord models

By Hongli Lai on August 10th, 2011

We’ve just released default_value_for version 1.0.5. default_value_for is a Rails plugin for supporting default values in ActiveRecord models. Like this:

class User < ActiveRecord::Base
  default_value_for :age, 10
end

User.new.age   # => 10

You can read more about this plugin at Github.

Version 1.0.5 fixes support for Rails 3.0 and Rails 3.1. Unfortunately we had to remove one feature due to changes in ActiveRecord 3.1. It is no longer possible to access associations in the default_value_for_block. The following is no longer possible:

class User < ActiveRecord::Base
  belongs_to :organization

  default_value_for :name do |model|
    model.name = "Drone for #{model.organization.name}"
  end
end

evil_organization.name  # => "Evil Organization"
user = evil_organization.users.create
user.name               # => "Drone for Evil Organization"??

One would expect the last expression to evaluate to “Drone for Evil Organization”. On Rails 2 and Rails 3.0, it does, but on Rails 3.1 it does not. Because of the way the ‘organization’ attribute is assigned to the model by ActiveRecord, we are no longer able to support this behavior. Inside the default_value_for block, model.organization would evaluate to nil.

Launching unofficial, automatically updated Github mirror for the Nginx SVN repository

By Hongli Lai on August 9th, 2011

I probably don’t need to tell you what a great web server Nginx is. It’s lightweight, it’s fast, it’s scalable, and many people like it for its configuration file syntax yet flexible features. It’s no surprise that Nginx is doing a great job serving as a core for Phusion Passenger Standalone (our Ruby/Rails web application server which supports Apache and Nginx).

Nginx’s development has traditionally been behind closed doors. Nginx is written for the most part by one brilliant man, Igor Sysoev, who accepts patches on the Nginx mailing list. But for a long time there was no source repository with which contributors and interested people can track the development process, and no official bug tracker. All of that have changed in 2011. Igor has established Nginx as a company. The Nginx SVN repository is now open to the public and a few days ago they even opened a Trac. In short: a lot of great news lately.

Announcing the Github Nginx mirror

These days a lot of people have switched from Subversion to Git. When you’ve worked with Git for a while, Subversion probably feels archaic and unproductive. The “killer app” for Git is Github, which provides an unbeatable collaboration experience. Indeed, many projects that have switched to Github reported a dramatic increase in contributions!

In order to stimulate Nginx development, we’ve launched a Github mirror of the Nginx SVN repository:

A few notes about this mirror:

  • It’s automatically updated twice a day.
  • It’s read-only. We don’t accept pull requests. Changes should be sent to the Nginx mailing list in the form of patches.
  • We intentionally don’t mirror all the branches and tags, only the most recent ones, because we don’t believe the old branches and tags are useful to anybody.
  • Please feel free to contact us if you have an issue with our mirror.

Happy developing!

Phusion Passenger 3.0.0 final released

By Hongli Lai on October 18th, 2010

Phusion Passenger is an Apache and Nginx module for deploying Ruby web applications. It has a strong focus on ease of use, stability and performance. Phusion Passenger is built on top of tried-and-true, battle-hardened Unix technologies, yet at the same time introduces innovations not found in most traditional Unix servers. Since version 3.0 it can also run standalone without an external web server, making it not only easier for first-time users but also ideal on development environments.


Thanks for all the support on Twitter everyone!

What’s new compared to 2.2

Phusion Passenger 3 is a major new release and brings about many, many improvements in the areas of performance, features and stability. It is fully compatible with the latest technologies such as Rails 3.0, Ruby 1.9.2, RVM and Bundler.

You can read about the improvements in Phusion Passenger 3 through a set of articles that we had written on this subject:

What’s new compared to 3.0.0 RC 1

[Apache] `passenger-install-apache2-module –snippet` no longer prints ANSI color codes
This makes it possible to pipe its output directly to a config file. Fixes issue #548.
[Apache] Fixed thread stack size problems on FreeBSD
Previously it might crash for bizarre reasons.
[Nginx] Upgraded to Nginx 0.8.52 by default
This is because Nginx 0.8 has recently become the stable release series.
[Standalone] Fixed Unix domain socket support
There was a bug in Phusion Passenger Standalone which would cause the `-S` option to fail.

How do I upgrade to 3.0.0?

Via a gem

First install the gem with the following command:

gem install passenger

If you’re using Phusion Passenger for Apache or for Nginx, then re-run the Apache or Nginx module installer, whichever is appropriate:

passenger-install-apache2-module
passenger-install-nginx-module

At the end the installer will tell you to paste a configuration snippet into your web server config file. Replace the old snippet that you already had with this new one.

Phusion Passenger Standalone users don’t need to run anything else. Whenever you type

passenger start

it will automatically upgrade itself.

Via a native Linux package

John Leach from Brightbox has kindly provided Ubuntu packages for Phusion Passenger. The package is available from the Brightbox repository which you can find at:

http://apt.brightbox.net

Add the following line to the Third Party Software Sources:

deb http://apt.brightbox.net hardy main

(The simplest way to do that is to create a file in /etc/apt/sources.list.d/ containing the deb instruction, and then run ‘apt-get update’).

Once you’ve done this then you can install Phusion Passenger by running:

sudo apt-get install libapache2-mod-passenger

-or-

sudo apt-get install nginx-brightbox

(Note that John is currently packaging 3.0.0, so it might take a while before this release shows up in the apt repository.)

Final

Phusion Passenger is provided to the community for free. If you like Phusion Passenger, please consider sending us a donation. Thank you!

Phusion Passenger 3.0.0 RC 1 released

By Hongli Lai on October 1st, 2010

This is it, the first Release Candidate (internally named “pre 4″). A few more bugs have been fixed since beta 3, but if no more serious bugs are found then the next release will be 3.0.0 final.

What’s new

Autodetection of Ruby tool commands now take exe extension, –program-prefix and –program-suffix into account

Phusion Passenger had a pretty elaborate way of searching the system for Ruby tool commands like ‘gem’, ‘rake’, etc. The reason why all this search code exists is because the problem is much more complicated than just searching for “rake” in $PATH. A significant number of people have multiple Ruby versions installed on their system; let’s call these Ruby A and Ruby B. They run the Phusion Passenger installer with Ruby A and they installed the required gems with Ruby A’s RubyGems, but they have a Rake in $PATH that belongs to Ruby B. If we were to naively use the Rake in $PATH then the wrong Rake will be invoked and they would wonder why the gems they just installed aren’t detected. Our Ruby tool search code tries very hard to find the Rake that belongs to the Ruby interpreter that’s currently running.

However the code didn’t take the exe extension into consideration, as well as the –program-prefix and –program-suffix that Ruby could be configured with. For example, on some systems the Ruby command is called “ruby1.8″ and Rake is called “rake1.8″. Similarly, the default JRuby binary distribution calls Rake “jrake”. We’ve now updated our search code to take this into account as well. Issue #537.

Usability and ease of installation remains to be one of our biggest goals. As you can see we’ve gone through great lengths to make sure that everything works as expected and that everything is as fool-proof as possible, even if the system might not be entirely correctly configured.

Fixed linking problems on some systems
Phusion Passenger uses math functions such as pow(). On some systems — .e.g. NetBSD, some versions of Solaris and some Linux distros — this requires Phusion Passenger to be linked to the Math library. Issue #539.
Phusion Passenger Standalone fix
When Phusion Passenger Standalone is daemonized with -d, it exits with a non-zero exit code. This has been fixed. Issue #536.
Preferred PCRE version upgraded to version 8.10
passenger-install-nginx-module automatically installs PCRE for you in case you didn’t already have it.

Installation/upgrade

First install the latest Phusion Passenger gem:

gem install passenger --pre

(Or you can download the tarball.)

If you want to install or upgrade the Apache or Nginx version, then run the installer as you’re used to:

passenger-install-apache2-module
passenger-install-nginx-module

At the end the installer will tell you to paste a configuration snippet. If you’re upgrading then replace the old snippet with the new one.

If you want to run Phusion Passenger Standalone, then run:

cd /path-to-your-app
passenger start

No special upgrade instructions needed.

The documentation

Users guide for Apache
Users guide for Nginx
Users guide for Standalone

Phusion Passenger 3.0.0 public beta 3 released

By Hongli Lai on September 20th, 2010

What’s new

More RVM issues fixed
A lot of people who happen to use RVM have reported various problems. Some of the symptoms include:

  • The spawn server crashes during startup.
  • Phusion Passenger Standalone prints ‘command not found’ errors.

It turned out that these problems occur when one has an older RVM version installed. Phusion Passenger depends on features provided by relatively new RVM versions. We’ve added sanity checks in Phusion Passenger so that it warns you if it detects an old RVM installation.

You can upgrade your RVM installation by running these commands:

rvm update --head
rvm reload
rvm repair all

We’ve also fixed support for system-wide RVM installs.

Aggressively checks for permissions
Many problems that people report on the Phusion Passenger discussion board are actually caused by wrong permissions set on the application files or on one of the parent directories. We’ve added aggressive permission checking code in Phusion Passenger. If any permissions are wrong then it will tell the user exactly which directory has wrong permissions and how to fix it. This should make Phusion Passenger a lot more fail-proof than before.
Fixed some more compilation problems
These problems occur on some systems but not on others.
Fixed passenger-status crash
When passenger-status tries to tell the user that it needs to be run as root, it used to crash instead because of a typo.
Phusion Passenger Standalone checks for GNU make on FreeBSD
Phusion Passenger Standalone depends on the GNU version of make in order to be able to show a compilation progress bar.

Installation/upgrade

First install the latest Phusion Passenger gem:

gem install passenger --pre

(Or you can download the tarball.)

If you want to install or upgrade the Apache or Nginx version, then run the installer as you’re used to:

passenger-install-apache2-module
passenger-install-nginx-module

At the end the installer will tell you to paste a configuration snippet. If you’re upgrading then replace the old snippet with the new one.

If you want to run Phusion Passenger Standalone, then run:

cd /path-to-your-app
passenger start

No special upgrade instructions needed.

The documentation

Users guide for Apache
Users guide for Nginx
Users guide for Standalone

Phusion Passenger 3.0.0 public beta 2 released

By Hongli Lai on September 17th, 2010

Thanks to all the users who have provided constructive feedback! This release fixes all newly reported issues so far.

Phusion Passenger Standalone now correctly handles paths with spaces in them
Beta 1 would error out with a weird message.
Fixed some RVM support issues
If you’re using RVM and you had problems with beta 1, try this version. Thanks to Wayne for helping out!
Improved watchdog startup error checking
If the watchdog crashes during startup then the reason wasn’t always obvious. We’ve improved the startup error checking code so that more error information can be collected.
Fixed Phusion Passenger Standalone binary compatibility string generation
The binary compatibility string is generated from many components, including part of the output of ‘uname -p’. It turns out that on some systems that command outputs in an unexpected format.
Fixed curl error detection
Curl is used to download things when wget is not available. Beta 1 did not correctly recognize cases in which curl exit because of an HTTP 404 error.
Fixed PassengerMinInstances default value on Apache
It’s supposed to be 1, but in beta 1 it was accidentally still 0.
Fixed support for older Apache 2 versions
Beta 1 used API calls which are only available in newer versions.
Fixed various compilation warnings
Including warnings on CentOS 5 and Ubuntu 10.04.

Installation/upgrade

First install the latest Phusion Passenger gem:

gem install passenger --pre

(Or you can download the tarball.)

If you want to install or upgrade the Apache or Nginx version, then run the installer as you’re used to:

passenger-install-apache2-module
passenger-install-nginx-module

At the end the installer will tell you to paste a configuration snippet. If you’re upgrading then replace the old snippet with the new one.

If you want to run Phusion Passenger Standalone, then run:

cd /path-to-your-app
passenger start

No special upgrade instructions needed.

The documentation

Users guide for Apache
Users guide for Nginx
Users guide for Standalone

Phusion Passenger 3.0.0 public beta 1 is out!

By Hongli Lai on September 15th, 2010

Phusion Passenger is software with which one can deploy Ruby web applications on the Apache or Nginx web server. Now, with the release of version 3.0.0 beta 1, it also comes with a standalone version that does not require an external web server. Please read http://www.modrails.com/ for details.

In the past period we’ve blogged extensively about the progress of Phusion Passenger 3. Many exciting changes have been introduced. It’s been a long ride, but today we are happy to announce the first Phusion Passenger 3 public beta! We’ve already tested Phusion Passenger 3 extensively in private, and now we’re giving the community a chance to test it as well in order to make the final release rock-solid.

We’ve received a lot of emails from people who are excited about Phusion Passenger 3. Thank you all for your support!

To recap, we’ve described Phusion Passenger 3 extensively in our past Technology Preview articles:

Except for mass deployment, all features described in the technology previews are available as open source in Phusion Passenger 3. A few more changes have been made since the last Technology Preview:

The ‘PassengerPreStart’ option
In Technology Preview 4 we described PassengerMinInstances. That option ensures that, at least x instances will be kept around once they’ve been spawned, but it does not ensure that instances are immediately spawned up during web server start. Instead we have a separate configuration option for that, called PassengerPreStart. PassengerPreStart and PassengerMinInstances and the reason why they’re separate configuration options are described extensively in the manual.
RVM support
RVM is becoming more and more popular. Previous versions of Phusion Passenger work fine with RVM, but one has to follow special installation instructions for it to work properly. We’ve noticed that many users are not aware of this and as a result couldn’t get Phusion Passenger working properly with RVM. In Phusion Passenger 3 we’ve collaborated with Wayne E. Seguin and added special support for RVM so that everything should work out-of-the-box, without special installation instructions.

Other minor changes

For completeness, here are a list of minor changes that have not been described in the Technology Previews so far:

  • Nginx is now compiled with SSL support by default.
  • Nginx can now be compiled on a noexec /tmp filesystem. Fixes bug #380.
  • Support for Nginx 0.8.
  • Dropped support for Nginx 0.6.
  • Much better Ruby 1.9.2 support. Our unit test suite has been refactored; Ruby 1.9.2 passes our unit test suite 100%.
  • The PassengerFriendlyErrorPages option has been added. This allows you to turn off the friendly error pages that Phusion Passenger normally displays in the event of an error, so that only a standard 500 error page is shown. This is documented in the manual.
  • The PassengerDebugLogFile option has been added. This is documented in the manual.
  • User switching support has been made more flexible through the PassengerUser, PassengerGroup, PassengerDefaultUser and PassengerDefaultGroup options. These are documented in the manual.
  • RailsSpawnMethod has been renamed to PassengerSpawnMethod because we now support smart spawning for Rack apps as well.
  • passenger-install-apache2-module and passenger-install-nginx-module can now be run concurrently without the one deleting files compiled by the other.
  • #at_exit blocks are now called during application process shutdown.
  • passenger-install-apache2-module now supports a –snippet option. When this option is given, the command outputs the Apache configuration snippet that should be pasted, but doesn’t do anything else.
  • Temporary files that Phusion Passenger places in /tmp are touched every 6 hours. This prevents /tmp cleaner daemons that are present on many systems from deleting these temp files that are essential for Phusion Passenger’s functioning. Bug #365.

Install

Installation is almost the same as Phusion Passenger 2. Type:

gem install passenger --pre

If you want to install the Apache or Nginx version, then run the installer as you’re used to:

passenger-install-apache2-module
passenger-install-nginx-module

Or you can download the tarball.

If you want to run Phusion Passenger Standalone, then run:

cd /path-to-your-app
passenger start

The documentation

Users guide for Apache
Users guide for Nginx
Users guide for Standalone

Things to come

Although Phusion Passenger 3.0 brings many exciting improvements, it is only the beginning. In parallel to Phusion Passenger 3, we have been working on some interesting things which we plan to reveal to the world shortly after the release of Phusion Passenger 3.0. Please stay tuned for future developments.

Announcing EncryptedCookieStore plugin for Rails 2.3

By Hongli Lai on April 13th, 2010

EncryptedCookieStore is similar to Ruby on Rails’s CookieStore (it saves session data in a cookie), but it uses encryption so that people can’t read what’s in the session data. This makes it possible to store sensitive data in the session.

EncryptedCookieStore is written for Rails 2.3. Other versions of Rails have not been tested.

Note: This is not ThinkRelevance’s EncryptedCookieStore. In the Rails 2.0 days they wrote an EncryptedCookieStore, but it seems their repository had gone defunct and their source code lost. This EncryptedCookieStore is written from scratch by Phusion.

Source code at http://github.com/FooBarWidget/encrypted_cookie_store

Installation and usage

First, install it:

./script/plugin install git://github.com/FooBarWidget/encrypted_cookie_store.git

Then edit config/initializers/session_store.rb and set your session store to EncryptedCookieStore:

ActionController::Base.session_store = EncryptedCookieStore

You need to set a few session options before EncryptedCookieStore is usable. You must set all options that CookieStore needs, plus an encryption key that EncryptedCookieStore needs. In session_store.rb:

ActionController::Base.session = {
        # CookieStore options...
        :key            => '_session',     # Name of the cookie which contains the session data.
        :secret         => 'b4589cc9...',  # A secret string used to generate the checksum for
                                           # the session data. Must be longer than 64 characters
                                           # and be completely random.

        # EncryptedCookieStore options...
        :encryption_key => 'c306779f3...', # The encryption key. See below for notes.
}

The encryption key must be a hexadecimal string of exactly 32 bytes. It should be entirely random, because otherwise it can make the encryption weak.

You can generate a new encryption key by running rake secret:encryption_key. This command will output a random encryption key that you can then copy and paste into your environment.rb.

Operational details

Upon generating cookie data, EncryptedCookieStore generates a new, random initialization vector for encrypting the session data. This initialization vector is then encrypted with 128-bit AES in ECB mode. The session data is first protected with an HMAC to prevent tampering. The session data, along with the HMAC, are then encrypted using 256-bit AES in CFB mode with the generated initialization vector. This encrypted session data + HMAC are then stored, along with the encrypted initialization vector, into the cookie.

Upon unmarshalling the cookie data, EncryptedCookieStore decrypts the encrypted initialization vector and use that to decrypt the encrypted session data + HMAC. The decrypted session data is then verified against the HMAC.

The reason why HMAC verification occurs after decryption instead of before decryption is because we want to be able to detect changes to the encryption key and changes to the HMAC secret key, as well as migrations from CookieStore. Verifying after decryption allows us to automatically invalidate such old session cookies.

EncryptedCookieStore is quite fast: it is able to marshal and unmarshal a simple session object 5000 times in 8.7 seconds on a MacBook Pro with a 2.4 Ghz Intel Core 2 Duo (in battery mode). This is about 0.174 ms per marshal+unmarshal action. See rake benchmark in the EncryptedCookieStore sources for details.

EncryptedCookieStore vs other session stores

EncryptedCookieStore inherits all the benefits of CookieStore:

  • It works out of the box without the need to setup a seperate data store (e.g. database table, daemon, etc).
  • It does not require any maintenance. Old, stale sessions do not need to be manually cleaned up, as is the case with PStore and ActiveRecordStore.
  • Compared to MemCacheStore, EncryptedCookieStore can “hold” an infinite number of sessions at any time.
  • It can be scaled across multiple servers without any additional setup.
  • It is fast.
  • It is more secure than CookieStore because it allows you to store sensitive data in the session.

There are of course drawbacks as well:

  • It is prone to session replay attacks. These kind of attacks are explained in the Ruby on Rails Security Guide. Therefore you should never store anything along the lines of is_admin in the session.
  • You can store at most a little less than 4 KB of data in the session because that’s the size limit of a cookie. “A little less” because EncryptedCookieStore also stores a small amount of bookkeeping data in the cookie.
  • Although encryption makes it more secure than CookieStore, there’s still a chance that a bug in EncryptedCookieStore renders it insecure. We welcome everyone to audit this code. There’s also a chance that weaknesses in AES are found in the near future which render it insecure. If you are storing *really* sensitive information in the session, e.g. social security numbers, or plans for world domination, then you should consider using ActiveRecordStore or some other server-side store.

JRuby: Illegal Key Size error

If you get this error (and your code works with MRI)…

    Illegal key size

    [...]/vendor/plugins/encrypted_cookie_store/lib/encrypted_cookie_store.rb:62:in `marshal'

…then it probably means you don’t have the “unlimited strength” policy files installed for your JVM. Download and install them. You probably have the “strong” version if they are already there.

As a workaround, you can change the cipher type from 256-bit AES to 128-bit by
inserting the following in config/initializer/session_store.rb:

EncryptedCookieStore.data_cipher_type = 'aes-128-cfb'.freeze  # was 256

Please note that after changing to 128-bit AES, EncryptedCookieStore still requires a 32 bytes hexadecimal encryption key, although only half of the key is actually used.

Securely store passwords with bcrypt-ruby; now compatible with JRuby and Ruby 1.9

By Hongli Lai on August 13th, 2009

When writing web applications, or any application for that manner, any passwords should be stored securely. As a rule of thumb, one should never store passwords as clear text in the database for the following reasons:

  • If the database ever gets leaked out, then all accounts are compromised until every single user resets his password. Imagine that you’re an MMORPG developer; leaking out the database with clear text passwords allows the attacker to delete every player’s characters.
  • Many people use the same password for multiple sites. Imagine that the password stored in your database is also used for the user’s online banking account. Even if the database does not get leaked out, the password is still visible to the system administrator; this can be a privacy breach.

There are several “obvious” alternatives, which aren’t quite secure enough:

Storing passwords as MD5/SHA1/$FAVORITE_ALGORITHM hashes
These days MD5 can be brute-force cracked with relatively little effort. SHA1, SHA2 and other algorithms are harder to brute-force, but the attacker can still crack these hashes by using rainbow tables: precomputed tables of hashes with which the attacker can look up the input for a hash with relative ease. This rainbow table does not have to be very large: it just has to contain words from the dictionary, because many people use dictionary words as passwords.

Using plain hashes also makes it possible for an attacker to determine whether two users have the same password.

Encrypting the password
This is not a good idea because if the attacker was able to steal the database, then there’s a possibility that he’s able to steal the key file as well. Plus, the system administrator is able to read everybody’s passwords, unless he’s restricted access to either the key file or the database.

The solution is to store passwords as salted hashes. One calculates a salted hash as follows:

salted_hash = hashing_algorithm(salt + cleartext_password)

Here, salt is a random string. After calculating the salted hash, one should store the salted hash in the database, along with the (cleartext) salt. It is not necessary to keep the salt secret or to obfuscate it.

When a user logs in, one can verify his password by re-computing the salted hash and comparing it with the salted hash in the database:

salted_hash = hashing_algorithm(salt_from_database + user_provided_password)
if (salted_hash == salted_hash_from_database):
    user is logged in
else:
    password incorrect

The usage of the salt forces the attacker to either brute-force the hash or to use a ridiculously large rainbow table. In case of the latter, the sheer size of the required rainbow table can make it unpractical to generate. The larger the salt, the more difficult it becomes for the cracker to use rainbow tables.

However, even with salting, one should still not use SHA1, SHA2, Whirlpool or most other hashing algorithms because these algorithms are designed to be fast. Although brute forcing SHA2 and Whirlpool is hard, it’s still possible given sufficient resources. Instead, one should pick a hashing algorithm that’s designed to be slow so that brute forcing becomes unfeasible. Bcrypt is such a slow hashing algorithm. A speed comparison on a MacBook Pro with 2 Ghz Intel Core 2 Duo:

  • SHA-1: 118600 hashes per second.
  • Bcrypt (with cost = 10): 7.7 hashes per second.

Theoretically it would take 4*10^35 years for a single MacBook Pro core to crack an SHA-1 hash, assuming that the attacker does not harness any weaknesses in SHA-1. To crack a bcrypt hash one would need 6*10^39 years, or 10000 more times. Therefore, we recommend the use of bcrypt to store passwords securely.

There’s even a nice Ruby implementation of this algorithm: bcrypt-ruby! Up until recently, bcrypt-ruby was only available for MRI (“Matz Ruby Interpreter”, the C implementation that most people use). However, we’ve made it compatible with JRuby! The code can be found in our fork at Github. The current version also has issues with Ruby 1.9, which we’ve fixed as well. The author of bcrypt-ruby has already accepted our changes and will soon release a new version with JRuby and Ruby 1.9 support.

default_value_for Rails plugin: declaratively define default values for ActiveRecord models

By Hongli Lai on October 3rd, 2008

Introduction

The default_value_for plugin allows one to define default values for ActiveRecord models in a declarative manner. For example:

class User < ActiveRecord::Base
  default_value_for :name, "(no name)"
  default_value_for :last_seen do
    Time.now
  end
end

u = User.new
u.name       # => "(no name)"
u.last_seen  # => Mon Sep 22 17:28:38 +0200 2008

We at Phusion use it for generating UUIDs for models.

Note: critics might be interested in the “When (not) to use default_value_for?” section. Please read on.

Installation

Install with:

./script/plugin install git://github.com/FooBarWidget/default_value_for.git

See also the AgileWebDevelopment Plugins entry.

If you like this plugin, then please consider donating and/or recommending us:

Hongli Lai Ninh Bui

The default_value_for method

The default_value_for method is available in all ActiveRecord model classes.

The first argument is the name of the attribute for which a default value should be set. This may either be a Symbol or a String.

The default value itself may either be passed as the second argument:

default_value_for :age, 20

…or it may be passed as the return value of a block:

default_value_for :age do
  if today_is_sunday?
    20
  else
    30
  end
end

If you pass a value argument, then the default value is static and never changes. However, if you pass a block, then the default value is retrieved by calling the block. This block is called not once, but every time a new record is instantiated and default values need to be filled in.

The latter form is especially useful if your model has a UUID column. One can generate a new, random UUID for every newly instantiated record:

class User < ActiveRecord::Base
  default_value_for :uuid do
    UuidGenerator.new.generate_uuid
  end
end

User.new.uuid  # => "51d6d6846f1d1b5c9a...."
User.new.uuid  # => "ede292289e3484cb88...."

Note that record is passed to the block as an argument, in case you need it for whatever reason:

class User < ActiveRecord::Base
  default_value_for :uuid do |x|
    x   # <--- a User object
    UuidGenerator.new.generate_uuid
  end
end

Rules

Instantiation of new record

Upon instantiating a new record, the declared default values are filled into the record. You’ve already seen this in the above examples.

Retrieval of existing record

Upon retrieving an existing record, the declared default values are not filled into the record. Consider the example with the UUID:

user = User.create
user.uuid   # => "529c91b8bbd3e..."

user = User.find(user.id)
# UUID remains unchanged because it's retrieved from the database!
user.uuid   # => "529c91b8bbd3e..."

Mass-assignment

If a certain attribute is being assigned via the model constructor’s mass-assignment argument, that the default value for that attribute will not be filled in:

user = User.new(:uuid => "hello")
user.uuid   # => "hello"

However, if that attribute is protected by attr_protected or attr_accessible, then it will be filled in:

class User < ActiveRecord::Base
  default_value_for :name, 'Joe'
  attr_protected :name
end

user = User.new(:name => "Jane")
user.name   # => "Joe"

Inheritance

Inheritance works as expected. All default values are inherited by the child
class:

class User < ActiveRecord::Base
  default_value_for :name, 'Joe'
end

class SuperUser < User
end

SuperUser.new.name   # => "Joe"

Attributes that aren’t database columns

default_value_for also works with attributes that aren’t database columns. It works with anything for which there’s an assignment method:

# Suppose that your 'users' table only has a 'name' column.
class User < ActiveRecord::Base
  default_value_for :name, 'Joe'
  default_value_for :age, 20
  default_value_for :registering, true

  attr_accessor :age

  def registering=(value)
    @registering = true
  end
end

user = User.new
user.age    # => 20
user.instance_variable_get('@registering')    # => true

Caveats

A conflict can occur if your model class overrides the ‘initialize’ method, because this plugin overrides ‘initialize’ as well to do its job.

class User < ActiveRecord::Base
  def initialize  # <-- this constructor causes problems
    super(:name => 'Name cannot be changed in constructor')
  end
end

We recommend you to alias chain your initialize method in models where you use default_value_for:

class User < ActiveRecord::Base
  default_value_for :age, 20

  def initialize_with_my_app
    initialize_without_my_app(:name => 'Name cannot be changed in constructor')
  end

  alias_method_chain :initialize, :my_app
end

Also, stick with the following rules:

  • There is no need to alias_method_chain your initialize method in models that don’t use default_value_for.
  • Make sure that alias_method_chain is called after the last default_value_for occurance.

When (not) to use default_value_for?

You can also specify default values in the database schema. For example, you can specify a default value in a migration as follows:

create_table :users do |t|
  t.string    :username,  :null => false, :default => 'default username'
  t.integer   :age,       :null => false, :default => 20
  t.timestamp :last_seen, :null => false, :default => Time.now
end

This has the same effect as passing the default value as the second argument to default_value_for:

user = User.new
user.username   # => 'default username'
user.age        # => 20
user.timestamp  # => Mon Sep 22 18:31:47 +0200 2008

It’s recommended that you use this over default_value_for whenever possible.

However, it’s not possible to specify a schema default for serialized columns. With default_value_for, you can:

class User < ActiveRecord::Base
  serialize :color
  default_value_for :color, [255, 0, 0]
end

And if schema defaults don’t provide the flexibility that you need, then default_value_for is the perfect choice. For example, with default_value_for you could specify a per-environment default:

class User < ActiveRecord::Base
  if RAILS_ENV == "development"
    default_value_for :is_admin, true
  end
end

Or, as you’ve seen in an earlier example, you can use default_value_for to generate a default random UUID:

class User < ActiveRecord::Base
  default_value_for :uuid do
    UuidGenerator.new.generate_uuid
  end
end

Or you could use it to generate a timestamp that’s relative to the time at which the record is instantiated:

class User < ActiveRecord::Base
  default_value_for :account_expires_at do
    3.years.from_now
  end
end

User.new.account_expires_at   # => Mon Sep 22 18:43:42 +0200 2008
sleep(2)
User.new.account_expires_at   # => Mon Sep 22 18:43:44 +0200 2008

Finally, it’s also possible to specify a default via an association:

# Has columns: 'name' and 'default_price'
class SuperMarket < ActiveRecord::Base
  has_many :products
end

# Has columns: 'name' and 'price'
class Product < ActiveRecord::Base
  belongs_to :super_market

  default_value_for :price do |product|
    product.super_market.default_price
  end
end

super_market = SuperMarket.create(:name => 'Albert Zwijn', :default_price => 100)
soap = super_market.products.create(:name => 'Soap')
soap.price   # => 100

What about before_validate/before_save?

True, before_validate and before_save does what we want if we’re only interested in filling in a default before saving. However, if one wants to be able to access the default value even before saving, then be prepared to write a lot of code. Suppose that we want to be able to access a new record’s UUID, even before it’s saved. We could end up with the following code:

# In the controller
def create
  @user = User.new(params[:user])
  @user.generate_uuid
  email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
  @user.save!
end

# Model
class User < ActiveRecord::Base
  before_save :generate_uuid_if_necessary

  def generate_uuid
    self.uuid = ...
  end

  private
    def generate_uuid_if_necessary
      if uuid.blank?
        generate_uuid
      end
    end
end

The need to manually call generate_uuid here is ugly, and one can easily forget to do that. Can we do better? Let’s see:

# Controller
def create
  @user = User.new(params[:user])
  email_report_to_admin("#{@user.username} with UUID #{@user.uuid} created.")
  @user.save!
end

# Model
class User < ActiveRecord::Base
  before_save :generate_uuid_if_necessary

  def uuid
    value = read_attribute('uuid')
    if !value
      value = generate_uuid
      write_attribute('uuid', value)
    end
    value
  end

  # We need to override this too, otherwise User.new.attributes won't return
  # a default UUID value. I've never tested with User.create() so maybe we
  # need to override even more things.
  def attributes
    uuid
    super
  end

  private
    def generate_uuid_if_necessary
      uuid  # Reader method automatically generates UUID if it doesn't exist
    end
end

That’s an awful lot of code. Using default_value_for is easier, don’t you think?

What about other plugins?

I’ve only been able to find 2 similar plugins:

Default Value appears to be unmaintained; its SVN link is broken. This leaves only ActiveRecord Defaults. However, it is semantically dubious, which leaves it wide open for corner cases. For example, it is not clearly specified what ActiveRecord Defaults will do when attributes are protected by attr_protected or attr_accessible. It is also not clearly specified what one is supposed to do if one needs a custom initialize method in the model.

I’ve taken my time to thoroughly document default_value_for’s behavior.

Credits

I’ve wanted such functionality for a while now and it baffled me that ActiveRecord doesn’t provide a clean way for me to specify default values. After reading http://groups.google.com/group/rubyonrails-core/browse_thread/thread/b509a2fe2b62ac5/3e8243fa1954a935, it became clear that someone needs to write a plugin. This is the result.

Thanks to Pratik Naik for providing the initial code snippet on which this plugin is based on: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model

If you like this plugin, then please consider donating and/or recommending us:

Hongli Lai Ninh Bui