Bundler and public applications »

Created at: 19.01.2012 23:24, source: Phusion Corporate Blog, tagged: ruby Ruby on Rails

I think Bundler is a great tool. Its strength lies not in its ability to install all the gems that you’ve specified, but in automatically figuring out a correct dependency graph so that nothing conflicts with each other, and in the fact that it gives you rock-solid guarantees that whatever gems you’re using in development is exactly what you get in production. No more weird gem version conflict errors.

This is awesome for most Ruby web apps that are meant to be used internally, e.g. things like Twitter, Basecamp, Union Station. Unfortunately, this strength also turns in a kind of weakness when it comes to public apps like Redmine and Juvia. These apps typically allow the user to choose their database driver through config/database.yml. However the driver must also be specified inside Gemfile, otherwise the app cannot load it. The result is that the user has to edit both database.yml and Gemfile, which introduces the following problems:

  • The user may not necessarily be a Ruby programmer. The Gemfile will confuse him.
  • The user is not able to use the Gemfile.lock that the developer has provided. This makes installing in deployment mode with the developer-provided Gemfile.lock impossible.

This can be worked around in a very messy form with groups. For example:

group :driver_sqlite do
  gem 'sqlite3'
end

group :driver_mysql do
  gem 'msyql'
end

group :driver_postgresql do
  gem 'pg'
end

And then, if the user chose to use MySQL:

bundle install --without='driver_postgresql driver_sqlite'

This is messy because you have to exclude all the things you don’t want. If the app supports 10 database drivers then the user has to put 9 drivers on the exclusion list.

How can we make this better? I propose supporting conditionals in the Gemfile language. For example:

condition :driver => 'sqlite' do
  gem 'sqlite3'
end

condition :driver => 'mysql' do
  gem 'mysql'
end

condition :driver => 'postgresql' do
  gem 'pg'
end

condition :driver => ['mysql', 'sqlite'] do
  gem 'foobar'
end

The following command would install the mysql and the foobar gems:

bundle install --condition driver=mysql

Bundler should enforce that the driver condition is set: if it’s not set then it should raise an error. To allow for the driver condition to not be set, the developer must explicitly define that the condition may be nil:

condition :driver => nil do
  gem 'null-database-driver'
end

Here, bundle install will install null-database-driver.

With this proposal, user installation instructions can be reduced to these steps:

  1. Edit database.yml and specify a driver.
  2. Run bundle install --condition driver=(driver name)

I’ve opened a ticket for this proposal. What do you think?


more »

Planet Argon is hiring »

Created at: 23.08.2011 07:28, source: Robby on Rails, tagged: Business Ruby on Rails PLANET ARGON jobs rubyonrails planetargon

Hello all!

I’ve been so busy this year travelling (for pleasure and work) and helping grow the company that I’ve not had much to say here. Expect some posts soon!

In the meantime, if you’re looking for a new challenge… Planet Argon is seeking a few more Ruby on Rails developers. Interested? get in touch.


more »

Rails 3 Asset Pipeline & Google Closure »

Created at: 16.08.2011 23:56, source: igvita.com, tagged: Ruby on Rails assets Rails3 sprockets

Rails 3.1.0 is on the horizon and the new Asset Pipeline is the king of the show. If this comes as news, then make sure to check out DHH's keynote from RailsConf, and also read through the excellent edge guide. This new release brings about some of the most developer visible changes in a long time: it both makes the previously "stashed away" assets such as Javascript and CSS first class citizens in our development workflow, and also pushes us into the bold new world of Coffeescript, Sass, and many other similar tools. It's about time!

Asset Pipeline & Preprocessing

Under the hood, there are two major components: Sprockets is responsible for all the asset preprocessing, and there is an additional middleware (mounted under /assets) which manages the actual toolchain. Hence, a request to /assets/application.js will hit the middleware, which will in turn invoke Sprockets, run the minifiers and compressors, and finally return the Javascript file. Good news is, this is all basically invisible.

Let's take a closer look at the default application.js:

// javascript comment
//= require jquery
//= require mylib/stuff.js
//= require_tree otherlib/
 

The goal is to enable Rails to serve a single Javascript file back to our visitors. To accomplish this, we need to tell Rails which files and directories should be included in the output. With that in mind, and as you may have guessed, the //= comment is, in fact, a special directive for Sprockets to include the specified file in the processing of the asset pipeline. Just specify the files, or entire directories and the rest is taken care of. CSS handling is no different.

Google Closure + Dependency Management

Turns out, Google's Closure library has a similar, but different, built-in dependency management system:

<script src="closure-library/closure/goog/base.js"></script>
<script>
  goog.require('goog.dom');
</script>
<script>
  var newHeader = goog.dom.createDom('h1', {}, 'Hello world!');
</script>
 

Closure's DOM manipulation (goog.dom) is an independent set of libraries, which are loaded by Closure at runtime when we call goog.require. Now, the dynamic loading is convenient in development mode, but it would be both slow and costly in production! To address this, Closure also ships with a separate Python script to preprocess your Javascript to generate the single asset file:

scan hello.js for requires and output to hello-calc.js
closure-library/closure/bin/calcdeps.py -i hello.js -p closure-library/ -o script > hello-calc.js

In other words, the net effect is the same as that provided by the built-in Asset Pipeline, but it requires manual intervention and third party tools - we can do better.

Extending Rails 3 Asset Pipeline: Closure support

The great news is, Sprockets allows us to easily modify and add new directive processors into our pipeline: there is no reason why we should be limited to //= directives only! Closure's goog.require's are effectively the same mechanism, hence we can extract these calls, load the right files, and make Closure a first class citizen in Rails! To do that, we can define a simple Closure Preprocessor:

class ClosureDependenciesProcessor < Tilt::Template
  def evaluate(context, locals, &block)
    context.require_asset 'goog/base'
 
    data.lines.each do |line|
      # scan the JS file for goog.require calls
      if line.match('goog\\.require\\s*\\(\\s*[\\'\\"]([^\\)]+)[\\'\\"]\\s*\\)')
        goog, mod, sub = $1.split(".")
        sub = mod if sub.nil?
 
        dep = [goog, mod, sub].compact.join("/").downcase
        context.require_asset(dep)
      end
    end
 
    data
  end
end
 

With that in place, we can define a new Railtie to register the new Javascript preprocessor, package it as a gem, and we're in business! Add the gem to your Gemfile, download the closure library, and you are good to go.

Rails 3 API's

The final closure-sprockets gem is almost trivial and is even easier to setup within your own project. Having said that, this is only true because of the extensibility provided by both the Sprockets preprocessors and the Railtie integration - kudos to the Rails 3 team for putting together these great API's. Finally, thanks to Josh for hand-holding me through the process of learning about Sprockets and the new Asset Pipeline!


more »

Rendering Rails 3.1 assets to string »

Created at: 14.08.2011 21:31, source: Phusion Corporate Blog, tagged: Ruby on Rails

The upcoming Rails 3.1 will come with a powerful asset pipeline, which is a framework for allowing developers to preprocess, concatenate, minify and compress Javascripts and CSS files. Javascripts can be written in CofeeScript, which is compiled on-the-fly to Javascript. Similarly, CSS can be written in Sass or SCSS, which are compiled on-the-fly to plain old CSS. Many people have already written about this topic.

Today we ran into a situation in which we wanted to render an SCSS file to a string so that we can put it in a JSON document. The way to do this is non-obvious. First try:

render :template => 'assets/stylesheets/api.css'

Rails complained that it cannot find this file in its search path, which only includes ‘app/views’.

Next try involved using the :file option because it also searches in Rails.root:

render :file => 'app/assets/stylesheets/api.css'

This time it successfully found the file, but couldn’t render it because there’s no :scss template engine. Apparently the asset pipeline does not integrate into the Rails template engine framework.

After some code digging, it turned out that asset serving is completely powered by Sprockets. The Rack middleware responsible for serving the /assets is Sprockets::Server, which looks up asset information using a Sprockets::Environment object. This object also handles rendering. The Rails integration allows such an object to be accessed through the YourApp::Application.assets method.

So the correct way to render an asset to a string is as follows:

YourApp::Application.assets.find_asset('api.css').body

Here, YourApp is your application’s module name as found in config/application.rb.


more »

default_value_for 1.0.5 released: default attribute values for ActiveRecord models »

Created at: 11.08.2011 01:32, source: Phusion Corporate Blog, tagged: Ruby on Rails Software

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.


more »