USE INDEX with Active Record finders 6

Posted by pratik
on Thursday, August 06

MySQL doesn’t always pick the right index for your queries. Hence, sometimes you must tell it which index to use. Consider the example :


Activity.all(:conditions => ['created_at >= ? AND country_id = ?', 10.days.ago, 79])

Running EXPLAIN on the above query :

EXPLAIN SELECT * FROM `activities` WHERE (created_at >= '2009-07-27 12:58:44' AND country_id = 79);

Possible keys : index_activities_on_created_at,index_activities_on_created_at_and_country_id
Using the key : index_activities_on_created_at

As you can see, even though the table has index on both the fields involved in the query – index_activities_on_created_at_and_country_id, MySQL still uses index_activities_on_created_at. You can explicitly ask MySQL to use the index you want by supplying USE INDEX

1
2
SELECT * FROM `activities` USE INDEX(index_activities_on_created_at_and_country_id) 
  WHERE (created_at >= '2009-07-27 12:58:44' AND country_id = 79);

Active Record does not have any finder option to specify the index hint. Hence the solution is to exploit the :from option :

1
2
3
from = "#{quoted_table_name} USE INDEX(index_activities_on_created_at_and_country_id)"
Activity.all(:from => from, 
             :conditions => ['created_at >= ? AND country_id = ?', 10.days.ago, 79])

Stop returning false from your before filters 3

Posted by pratik
on Friday, July 31

In the past version of Rails you had to explicitly return false from before filters to halt the filter chain and make sure the action doesn’t get run. The code looked somewhat like :

1
2
3
4
5
6
7
8
9
10
11
12
class AdminController < ApplicationController
  before_filter :check_admin

  private

  def check_admin
    unless current_user.admin?
      redirect_to home_path
      return false
    end
  end
end

But since Rails 2.0, If you call render, head or redirect_to from a before_filter, the filter chain will be halted. So the above controller will now look like :

1
2
3
4
5
6
7
8
9
class AdminController < ApplicationController
  before_filter :check_admin

  private

  def check_admin
    redirect_to home_path unless current_user.admin?
  end
end

You should go ahead and update all your before filters to reflect this change. return false is nothing but code smell.

Running Rails performance tests on real data 5

Posted by pratik
on Wednesday, July 29

It’s not quite straight forward to run Rails performance tests on real production data. By default, performance tests will use the test database and wipe it before using it, thus making it impossible to put real data in the test database.

To run performance tests on real data:

  • Create a file called performance_test_helper.rb inside your test/ directory with the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# START : HAX HAX HAX
# Load Rails environment in 'test' mode
RAILS_ENV = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
# Re-establish db connection for 'performance' mode
silence_warnings { RAILS_ENV = "performance" }
ActiveRecord::Base.establish_connection
# STOP : HAX HAX HAX

require_dependency 'application'

require 'test/unit'
require 'active_support/test_case'
require 'action_controller/test_case'
require 'action_controller/integration'

require 'performance_test_help'
  • Replace the following lines in performance tests:
1
2
require 'test_helper'
require 'performance_test_help'

with


require 'performance_test_helper'
  • Supply configuration for ‘performance’ environment in your database.yml:
1
2
3
4
5
6
7
8
performance:
  adapter: mysql
  encoding: utf8
  database: database_with_real_data
  pool: 5
  username: root
  password:
  socket: /tmp/mysql.sock

The above steps will also make sure that the performance tests use the database database_with_real_data. Also, the database will not get wiped on every run. You’ll also have to ensure the test database – database_with_real_data – is up-to-date with the schema changes.

Now you can just run your performance tests using the usual rake tasks:

1
2
$ rake test:benchmark
$ rake test:profile

Nested Layouts 10

Posted by pratik
on Tuesday, July 07

Don’t think the nested layouts get any simpler in Rails.

1
2
3
4
5
6
module ApplicationHelper
  def parent_layout(layout)
    @content_for_layout = self.output_buffer
    self.output_buffer = render(:file => "layouts/#{layout}")
  end
end

Now using the parent_layout helper method inside your layouts for nesting :

1
2
3
4
5
# items.html.erb
<h1>Just my items</h1>
<%= yield %>

<% parent_layout 'master' %>

and the parent layout of items – the master layout :

1
2
3
4
5
6
7
# master.html.erb
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head><title>Hello World</title></head>
  <body>
    <%= yield %>
  </body>
</html>

Now, the items layout will always be wrapped under the master layout. Just make sure that the parent_layout call is always on the last line using <%. This technique also works for nesting deeper than a single level.

Announcing ActionRails 11

Posted by pratik
on Thursday, April 23

I’m very happy to announce ActionRails – a new consulting firm jointly owned by Koz (Rails core, therailsway.com author), Mike Gunderloy (Rails documentation/activist team) and myself.

ActionRails is not trying to be yet another consultancy that develops applications from start to finish (though we can talk to you individually about such projects). Rather, we have a distinct set of services, designed to help customers complete successful Rails deployments and helping existing teams improve their Rails skills. Our menu currently offers:

  • Application Evaluation
  • Weekly Code Reviews
  • Developer Hotline

ActionRails has already been working for a few select clients, helping them out with reviewing code, offering course corrections, and evaluating application architecture (among other things). You can hop over to our site for more information, or just drop us a line.

Default Scopes and Inheritance to the rescue 11

Posted by pratik
on Tuesday, March 24

On my one of the current projects, there are two primary models each with a flag called approved. 99% of the front end part deals with only approved items. Unapproved items are usually only in the admin panel side of the story. So I started with using a named_scope called approved:

1
2
3
4
5
6
class Item < ActiveRecord::Base
  has_many :tags

  default_scope :order => 'items.name ASC'
  named_scope :approved, :conditions => { :published => true }
end

And now I’d have to use Item.approved. everywhere in my application. But that became a bit too cumbersome sooner than later. Playing around with this a bit, I came up with the solution using default_scope and the good ol’ inheritance:

1
2
3
4
5
6
7
8
9
10
11
12
class Item < ActiveRecord::Base
  has_many :tags

  default_scope :order => 'items.name ASC'
end

class PublishedItem < Item
  set_table_name 'items'
  set_inheritance_column nil # hax?

  default_scope :conditions => { :published => true }, :order => 'items.name ASC'
end

Checking this on console :

>> p = PublishedItem.first
  SELECT * FROM `items` WHERE (`items`.`published` = 1) ORDER BY items.name ASC LIMIT 1

>> i = Item.first
  SELECT * FROM `items` ORDER BY items.name ASC LIMIT 1

Seems to work just fine.

You could do it the other way around too:

1
2
3
4
5
6
7
8
9
10
11
12
13
class RawItem < ActiveRecord::Base
  set_table_name 'items'
  has_many :tags

  default_scope :order => 'items.name ASC'
end

class Item < RawItem
  set_table_name 'items'
  set_inheritance_column nil # hax?

  default_scope :conditions => { :published => true }, :order => 'items.name ASC'
end

Whichever one works for you.

Please note that the above code is NOT using STI. It’s using set_inheritance_column nil workaround to bypass the Active Record STI stuff and rely just on the ruby inheritance.

My Rails guides 3

Posted by pratik
on Wednesday, March 18

Instead of blogging some of the articles, I decided to write those as Rails guides. So go ahead and check them out !

I also wrote a lot of the Active Record Query Interface guide. Of course my guides aren’t really eligible for the guides hackfest prizes. But hey, you could still win them by writing a new guide !

Rails templates 57

Posted by pratik
on Thursday, December 04

So now that Edge Rails got templates ( Thanks to Jeremy ) I just wanted to give a top level overview.

Templates are simple ruby files containing DSL for adding plugins/gems/initializers etc. to your freshly created Rails project. To apply the template, you need to provide rails generator with location of the template you wish to apply, using -m option :


rails blog -m ~/template.rb

Thanks to the magic of open-uri, the template location can be a URL too :


rails blog -m http://gist.github.com/31208.txt

You can even apply templates to your existing Rails application using rails:template rake task and supplying LOCATION environment variable :


rake rails:template LOCATION=~/template.rb

A very simple template would look like :

1
2
3
4
5
6
7
8
9
# template.rb
run "rm public/index.html"
generate(:scaffold, "person name:string")
route "map.root :controller => 'people'"
rake("db:migrate")

git :init
git :add => "."
git :commit => "-a -m 'Initial commit'"

That’s very self explanatory. Here are the key methods for the template DSL :

gem(name, options = {})

Adds a config.gem entry for the supplied gem to generated application’s config/environment.rb

So if your application depends on bj and hpricot :

1
2
gem "bj"
gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"

Please note that this will NOT install the gems for you. So you may want to run rake gems:install:


rake "gems:install"

And let Rails take care of installing the required gems if they’re not already installed.

plugin(name, options = {})

Installs a plugin to the generated application.

Plugin can be installed from Git :


plugin 'authentication', :git => 'git://github.com/foor/bar.git'

You can even install plugins as git submodules :


plugin 'authentication', :git => 'git://github.com/foor/bar.git', :submodule => true

Please note that you need to git :init before you can install a plugin as a submodule

Or use plain old SVN :


plugin 'wtfsvn' :svn => 'svn://crap.com/wtf/trunk'

initializer(filename, data = nil, &block)

Adds an initializer to the generated application’s config/initializers directory.

So personally, I like using Object#not_nil? and Object#not_blank? :

1
2
3
4
5
6
7
8
9
10
11
initializer 'bloatlol.rb', <<-CODE
class Object
  def not_nil?
    !nil?
  end
  
  def not_blank?
    !blank?
  end
end
CODE

Similarly lib() creates a file in lib/ directory and vendor() creates a file in vendor/ directory. There is also file(), which accepts a relative path from RAILS_ROOT and creates all the directories/file needed :

1
2
3
4
file 'app/components/foo.rb', <<-CODE
class Foo
end
CODE

That’ll create app/components directory and put foo.rb in there.

rakefile(filename, data = nil, &block)

Creates a new rake file under lib/tasks with the supplied tasks :

1
2
3
4
5
6
7
8
9
rakefile("bootstrap.rake") do
  <<-TASK
    namespace :boot do
      task :strap do
        puts "i like boots!"
      end
    end
  TASK
end

And that creates lib/tasks/bootstrap.rake with a boot:strap rake task!

generate(what, args)

Runs the supplied rails generator with given arguments. For example, I love to scaffold some whenever I’m playing with Rails :


generate(:scaffold, "person", "name:string", "address:text", "age:number")

run(command)

Executes an arbitrary command. Just like the backticks. My main use case is to remove public/index.html :


run "rm public/index.html"

rake(command, options = {})

So you scaffolded, but who’s gonna run the db:migrate rake task !? Here’s who :


rake "db:migrate"

Simple enough.

You can also run rake tasks in a different rails environment :


rake "db:migrate", :env => 'production'

Or even use sudo :


rake "gems:install", :sudo => true

route(routing_code)

This adds a routing entry to config/routes.rb file. In above steps, we generated a person scaffold and also removed public/index.html. Now to make PeopleController#index as the default page for the application :


route "map.root :controller => :person"

Voila!

inside(dir)

I have my edge rails lying at ~/commit-rails/rails. So every time i have to manually symlink edge from my new app. But now :

1
2
3
inside('vendor') do
  run "ln -s ~/commit-rails/rails rails"
end

So inside() runs the command from the given directory.

ask(question)

ask gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding :

1
2
3
4
5
6
7
lib_name = ask("What do you want to call the shiny library ?")
lib_name << ".rb" unless lib_name.index(".rb")

lib lib_name, <<-CODE
class Shiny
end
CODE

yes?(question) or no?(question)

And you can even ask questions from templates and decide the flow based on user’s answer. Lets say you want to freeze rails only if the user want to :


rake("rails:freeze:gems") if yes?("Freeze rails gems ?")

no?(question) acts just the opposite.

git(:must => “-a love”)

As we all love git/hub, Rails templates let you do the git stuff too !

1
2
3
git :init
git :add => "."
git :commit => "-a -m 'Initial commit'"

And bort ?

Here’s what a bort template would look like :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# bort.rb
inside('vendor') do
  run "ln -s ~/commit-rails/rails rails"
end

plugin 'rspec', 
  :git => 'git://github.com/dchelimsky/rspec.git'
plugin 'rspec-rails', 
  :git => 'git://github.com/dchelimsky/rspec-rails.git'
plugin 'exception_notifier', 
  :git => 'git://github.com/rails/exception_notification.git'
plugin 'open_id_authentication', 
  :git => 'git://github.com/rails/open_id_authentication.git'
plugin 'asset_packager', 
  :git => 'http://synthesis.sbecker.net/pages/asset_packager'
plugin 'role_requirement', 
  :git => 'git://github.com/timcharper/role_requirement.git'
plugin 'restful-authentication', 
  :git => 'git://github.com/technoweenie/restful-authentication.git'
 
gem 'mislav-will_paginate', :version => '~> 2.2.3', 
  :lib => 'will_paginate',  :source => 'http://gems.github.com'
gem 'rubyist-aasm'
gem 'ruby-openid'
 
rake("gems:install", :sudo => true)

generate("authenticated", "user session")
generate("rspec")

Shamelessly inspired/yanked from Jeremy’s templates repo

Save that code in bort.rb and :


[lifo@null Rails]$ ruby ~/commit-rails/rails/railties/bin/rails bortapp -m bort.rb

Contribute

If you like this template stuff and want to share your templates with the rest of us, please contribute to Jeremy’s rails-templates project – which will be a collection of Rails templates.

And as usual, any bugs/feature requests can go to Rails lighthouse

UPDATE 1 Add an example of installing plugins as a git submodule. Thanks to Peter Cooper for the patch. UPDATE 2 Add an example of rake rails:template LOCATION=foo task

Rails meets Sinatra #2 - Mix n' Match 9

Posted by pratik
on Sunday, November 16

In my previous post, we saw how to mount a sinatra application at a specific location for a regular application. But using Rack::Cascade, we can take that solution to a whole new height. That is :

  • Put sinatra code in any of your regular Rails controllers.
  • No need to mount at Sinatra at a specific URI.
  • Have Sinatra work for any URI, gracefully fallback to Rails if no Sinatra method matches the path.
  • Use your models/libraries etc. in both Rails and Sinatra.

So your controllers can look like :

1
2
3
4
5
6
7
8
class HomeController < ApplicationController
  get '/' do
    Item.count.to_s
  end
  
  def index
  end
end

and even :

1
2
3
4
5
6
7
8
9
class SiteController < ApplicationController
  get '/:name' do
    Site.find_by_name(params[:name]).html
  end

  def new
  end

end

And environment.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RAILS_GEM_VERSION = '2.2.0' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  config.gem "sinatra"
  config.time_zone = 'UTC'
  config.action_controller.session = { :session_key => '_monkey_session',  :secret => 'whatever' }
end

set :env, :production
disable :run, :reload

# Remove trailing slash from URIs reaching Sinatra
before { request.env['PATH_INFO'].gsub!(/\/$/, '') if request.env['PATH_INFO'] != '/' }

# Checking AR Connections back to the pool
after { ActiveRecord::Base.clear_active_connections! }

# Preload controllers with Sinatra code
require 'home_controller'
require 'site_controller'

And most important, the rack setup file :

1
2
3
4
5
6
7
8
9
require File.dirname(__FILE__) + '/config/environment'
require 'thin'
  
app = Rack::Builder.new {
  use Rails::Rack::Static
  run Rack::Cascade.new([Sinatra.application, ActionController::Dispatcher.new])
}.to_app
 
Rack::Handler::Thin.run app, :Port => 3000, :Host => "0.0.0.0"

And start your server !

[lifo@219 monkey]$ ruby racked.rb

Rails meets Sinatra 11

Posted by pratik
on Monday, November 10

For real !

It was pleasantly simple to get Rails + Sinatra run in the same process.

First of all, put your Sinatra application inside RAILS_ROOT. My Sinatra app is called tiny :

1
2
3
4
5
6
7
8
# RAILS_ROOT/tiny.rb
require 'sinatra'

before { request.env['PATH_INFO'].gsub!(/\/$/, '') }

get '' do
  'Hello Sinatra!'
end

And then do the rack dance – racked.rb :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# RAILS_ROOT/racked.rb
require File.dirname(__FILE__) + '/config/environment'
require 'thin'
 
# Sinatra stuff
require 'tiny'
 
# Make sinatra play nice
set :env, :production
disable :run, :reload
 
app = Rack::Builder.new {
  use Rails::Rack::Static
 
  # Anything urls starting with /tiny will go to Sinatra
  map "/tiny" do
    run Sinatra.application
  end
 
  # Rest with Rails
  map "/" do
    run ActionController::Dispatcher.new
  end
}.to_app
 
Rack::Handler::Thin.run app, :Port => 3000, :Host => "0.0.0.0"

Line 25 ensures your Sinatara application is used for all the urls starting from /tiny. Any url not starting from /tiny will go to the Rails application.

Now start your server :

$ ruby racked.rb

If you go to http://0.0.0.0:3000/tiny, you’ll be greeted with ‘Hello from sinatra!’. And your Rails application will continue to work as expected for everything else. Voila! There you have the fastest ‘hello world’ Ruby framework embedded with Rails :)

Please note that Rails’ built in rack adapter isn’t perfect and you might find a glitch or two. All in good time

UPDATE 1: Prettify Sinatra code and remove monkey patches, thanks to the feedback from Blake Mizerany. Also add a before filter to sinatra app for removing trailing slash.

ActiveSupport::Rescuable 3

Posted by pratik
on Monday, November 03

In Rails 2.2, controller’s rescue_from has been extracted to ActiveSupport as ActiveSupport::Rescuable module. Relevant commits are 964dfc and 259a7a. This allows you to use rescue_from functionality in class as a cleaner way of handling exceptions.

Now you can just include ActiveSupport::Rescuable in your class and start using rescue_with_handler(exception) from your rescue blocks :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Armageddon < StandardError
end

class Earth
  include ActiveSupport::Rescuable

  rescue_from Armageddon, :with => :nuke_the_rock

  def life
    raise Armageddon
  rescue Exception => e
    rescue_with_handler(e)
  end

  def nuke_the_rock
    puts "snafu"
  end
end

e = Earth.new
e.life

The above code will produce output “snafu”. You can also check code/docs if you wanna know more.

Thread safety for your Rails 34

Posted by pratik
on Friday, October 24

Rails 2.2 marks the first release of thread safe Rails. But “thread safety” alone, without any context, doesn’t mean shit. When people say Rails is “thread safe” ( or otherwise ), they usually refer to the dispatching process of Rails. Before 2.2, Rails dispatching looked like :

1
2
3
@@guard.synchronize do
  dispatch_unlocked
end

And now it looks somewhat like :


dispatch_unlocked

Long story short, Rails can now serve multiple requests in more than one ruby threads ( or native threads if you’re on JRuby ) parallelly. Charles Nutter has done a good job of explaining the details here.

Should you give a flying fuck ?

You totally should if :

  • You’re using JRuby
  • You’re bold enough to play around with bleeding edge Neverblock stuff
  • Your application has a lot of long running processes, which are not heavy on blocking IO ( this would be rare I imagine )

You totally should NOT if :

  • You’re using Event based mongrel, thin or any of the event based web server in production. Event based servers don’t use Threads, so it just doesn’t matter.
  • You CBA

You may have heard a bunch of hype about how threads make everything 100x faster, this is far from the truth. Don’t believe everything the hype merchants want to sell you, test your application first and see if it helps.

Koz’s comments sums it up nicely :

I think the more interesting issue to consider is whether your application will benefit from ‘threaded dispatching’ at all.

The performance of green threads in ruby is kind of disappointing, as are the number of different options which block the interpreter. IO, regexps, calling most native libraries, etc. Odds are with matz’s ruby you’re infinitely better off using passenger + ruby enterprise edition than ruby threads.

JRuby is another matter altogether, and it’s jruby users who should be most excited about this stuff, and the most willing to help us iron out any last bugs.

Prepare your mongrels first

Currently, you’ll need to manually patch Mongrel’s built in Rails handler for testing multithreaded dispatching. I’ve submitted a patch to mongrel and hopefully there’ll be a new gem version of mongrel soon. In the mean time, monkey patch FTW.

How to enable multi threaded dispatching ?

Just put the following lines in your production.rb


config.threadsafe!

However, that’s not enough. There are some consequences if you have never made sure to write thread safe code. They are, however, simple to fix. Usually.

Ruby’s require is not atomic

What this means is, if in Thread A you require a file named whatever.rb in which defines a class called Whatever, the class Whatever can be visible from Thread B even before Thread A has finished loading whatever.rb. And because of this ruby behavior, Rails now preloads everything inside app directory.

config.threadsafe! also disables automatic loading by ActiveSupport::Dependencies.

ActiveSupport::Dependencies uses ruby’s const_missing hook to load files automatically for you, whenever possible. For example, if you have following file inside your application’s lib/ directory :

1
2
3
4
5
6
# hello.rb
class Hello
  def world
    "hello world"
  end
end

Rails has traditionally saved you the trouble of requring that file manually inside your application. Whenever you access Hello ( Hello.new for example ) constant for the first time, ActiveSupport::Dependencies loads hello.rb for you automatically. Note that this is only possible if the file name matches the class name that it defines.

But as this behavior is disabled when you calls config.threadsafe!, you’ll now need to require the file hello.rb manually before Rails starts serving the requests ( typically inside environment.rb or an initializer ).

Alternatively, you can just add lib/ directory to eager load paths. The following inside production.rb will do that :


config.eager_load_paths << "#{RAILS_ROOT}/lib"

And that will make Rails preload everything inside lib/ directory.

Don’t mess with class variables

Imagine your controller having a code that does :

1
2
3
4
5
6
7
8
class HomeController < ApplicationController
  @@visits = 0
  
  def index
    @@visits += 1
    render :text => @@visits
  end
end

This code is not safe if you enable multi threaded dispatching. All your instance methods ( actions in case of controllers ) should only read global values ( $vars, @@vars, class instance variables ) and never modify them.

Here’s a better example which would explains the consequences as well :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HomeController < ApplicationController
  before_filter :set_site
  
  def index
  end
  
  private
  
  def set_site
    @site = Site.find_by_subdomain(request.subdomains.first)
    if @site.layout?
      self.class.layout(@site.layout_name)
    else
      self.class.layout('default_lay')
    end
  end
end

What happens here is :

  • Before filter set_site uses subdomain to populate @site instance variable
  • It also sets the layout to @site.layout_name is not nil

Imagine your application has two possible subdomains :

  • foo – has a layout called ‘foo_lay’
  • bar – has no layout. Uses default layout ‘default_lay’

When you call self.class.layout(value), Rails will store the value inside a class variable @@layout, which causes a race condition if called from multiple instance methods in different threads. Wikipedia page will do a better job of explaining what is a race condition if you have never bothered about it before.

Let us assume that two users are accessing the application : UserA and UserB. UserA’s request is served by Thread1 and UserB’s request is served by Thread2. Here, numbers also represent the order in which these events occur :

  1. Thread1 : UserA visits http://foo.site.com/home
  2. Thread1 : HomeController#set_site calls self.class.layout(@site.layout_name)
  3. Thread1 : This sets HomeController#@@layout to ‘foo_lay’
  4. Thread2 : UserB visits http://bar.site.com/home
  5. Thread2 : HomeController#set_site calls self.class.layout(‘default_lay’)
  6. Thread2 : This sets HomeController#@@layout to ‘default_lay’
  7. Thread1 : Request is done executing action code. Time to send back the response to UserA.
  8. Thread1 : Rails calls HomeController#render
  9. Thread1 : HomeController#render uses the value of HomeController#@@layout to render the final output html
  10. Thread1 : As the value of HomeController#@@layout was modified by #6 to ‘default_lay’, #9 will uses ‘default_lay’ even if the expected layout was ‘foo_lay’

The thread safe way to write this code is :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class HomeController < ApplicationController
  before_filter :set_site
  layout :site_layout

  def index
  end
  
  private
  
  def set_site
    @site = Site.find_by_subdomain(request.subdomains.first)
  end
  
  def site_layout
    if @site.layout?
      @site.layout_name
    else
      'default_lay'
    end
  end
end

When you use layout :site_layout, Rails will use the return value of site_layout instance method to determine the layout, which makes it a thread safe way. Please note that this is not the same as calling layout ‘something’. If you pass a string to the class method layout, Rails will use the passed value as the layout.

( Example inspired from Dynamic Layouts Railscast )

Getting dirty with Thread.current

if you must, you can always use Thread local variables as the last resort. Ruby provides you with a magical hash Thread.current[] inside any executing thread, where you can store variables accessible anywhere from inside that specific thread. Really, you can check this docs

The following code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
threads = []

threads << Thread.new do
  Thread.current[:hello] = 1
  sleep 2
  puts "From T1 : #{Thread.current[:hello]}"
end

threads << Thread.new do
  Thread.current[:hello] = 10
  puts "From T2 : #{Thread.current[:hello]}"
end

threads.each {|t| t.join }

will produce :

1
2
From T2 : 10
From T1 : 1

You might have seen this in use in with any current_user hacks : Here or here. But it’s still a hack.

If you’re familiar with Rails source ( of interested in being familiar ), you can find Rails using Thread.current[] at several places : Thread.current[:time_zone] or Thread.current[‘query_cache’]. I18 gem uses Thread.current[:locale] to store the value of locale specific to the thread.

But as I said earlier, Thread.current should be used as a last resort only.

Good ol’ Mutex

There is always the big fat mutex which can be slapped around a piece of code that you want to execute exclusively per thread. You should check the wikipedia page if you’re looking for some explanation :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class HomeController < ApplicationController
  @@lock = Mutex.new

  def index
    @@lock.synchronize do
      thread_unsafe_code
    end
  end
  
  private
  
  def thread_unsafe_code
    if @@something == 'hello'
      do_hello
    elsif @@something == 'world'
      do_world
    else
      @@something = 'nothing'
    end
  end
end

This ensures that only one thread can be executing thread_unsafe_code() method at any given point in time. Other threads will block and wait indefinitely for the executing thread to release the lock acquired by @@lock.synchronize.

Common concerns

Adam Hooper raised three valid concerns :

  • What is the likelihood that there is broken code in the Rails core, despite the word “threadsafe!”? If Java frameworks (engineered in a language which, unlike Ruby, was built from the ground up with threads in mind) can provide premonitions, is it not safe to assume Rails is painfully buggy in this regard?

Chances of thread unsafe code being in Rails are close to none. There have never been anything inherently thread unsafe about Rails codebase. If some people had you think otherwise, you listened to the wrong bunch of people/FUD. We’ve had a list of thread unsafe code inside Rails for a long time, and it was a small list.

However, thread safety is like a Random Number Generator – You can never be sure

  • All existing plug-ins are thread-unsafe until proven otherwise, right? (And IMO Rails developers should broadcast this at the top of their voices, because promising thread-safety and relying on other people to provide it is shooting oneself in the foot….)

Jeremy says : No. They are not. Most, in fact, are probably threadsafe. Your claiming this is a major issue is a fairly good indicator that you don’t actually know the core issues with thread safety in Ruby/Rails.

Me : That’s not true. At least all the plugins that I use, are thread safe. Having said that, you should never use a plugin without getting yourself familiar with it’s code base.

  • Does anybody think thread-safe Rails will ever be suitable for production use? It’s hard enough to convince a project manager to consider it already, with its current, much, much, much, MUCH simpler model.

Short answer, don’t jump the ship if you can’t be bothered about ensuring your code is thread safe. Always stick to “Simplest thing that works” motto IMO. You could just spend some time researching if running multithreaded Rails is going to benefit your application/business at all or not and evaluate that against the risk/time involved.

But that doesn’t make threadsafe Rails unsuitable for production use. It makes your specific application/team unsuitable for using thread-safe Rails in production mode. Multi threaded programming has never been easy. However, if you write good OO code, thread safety usually comes for free.

UPDATES :
  1. Added Mutex section.
  2. Added ‘Common concerns’
  3. Added ‘Should you give a flying fuck ?’
  4. Added ‘Prepare your mongrels first’

Sanity for free 5

Posted by pratik
on Sunday, October 05

Just an experiment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require 'erb'

class ActionView::Base
  def _copy_ivars_from_controller_with_sanity
    variables = _copy_ivars_from_controller_without_sanity
    variables.each do |v|
      ivar = instance_variable_get(v)
      if ivar.is_a?(Array)
        values = ivar.map do |subivar|
          subivar.respond_to?(:to_sanity) ? subivar.to_sanity : subivar
        end
        instance_variable_set(v, values)
      elsif ivar.respond_to?(:to_sanity)
        instance_variable_set(v, ivar.to_sanity)
      end
    end
  end

  alias_method_chain :_copy_ivars_from_controller, :sanity
end

module Sanity
  class Cleaner
    Object.instance_methods.each do |m|
      delegate m, :to => :@model unless m =~ /^__/
    end
    
    def initialize(model)
      @model = model
    end

    def method_missing(method_id, *arguments, &block)
      if @model.class.sanitizable_columns.include?(method_id)
        value = @model.send(method_id)
        arguments.first == false ? value : ERB::Util.h(value)
      else
        @model.send(method_id, *arguments, &block)
      end
    end
  end

  module Model
    def self.included(base)
      base.send :include, InstanceMethods
      base.send :extend, ClassMethods
    end
    
    module InstanceMethods
      def to_sanity
        Cleaner.new(self)
      end
    end
    
    module ClassMethods
      def sanitizable_columns
        @sanitizable_columns ||= content_columns.find_all {|c| c.text? }.map(&:name).map(&:to_sym)
      end
    end
  end
end

ActiveRecord::Base.send :include, Sanity::Model

And then..

1
2
<%= @item.name # sanitized name %>
<%= @item.name(false) # unsanitized name  %>

I know, too many methods are missing, edge cases and what not. But hey, it’s just an idea/experiment.

Rails does not block on file uploads 5

Posted by pratik
on Saturday, September 13

I’ve been quite annoyed with the ignorance of people who keep telling every other person that Rails/Mongrel blocks on file upload.

Here are the steps to realize Rails does not block on file uploads :

  • Follow these steps :
1
2
3
null:Rails lifo$ rails fileuploads
null:Rails lifo$ cd fileuploads/
null:fileuploads lifo$ script/generate controller upload index

Make your RAILS_ROOT/app/views/upload/index.html.erb look something like below :

1
2
3
4
5
<% form_tag({}, { :multipart => true }) do %>
  <label for="file">File to Upload</label>
  <%= file_field_tag "file" %>
  <%= submit_tag %>
<% end %>

Now, generate a big fat file ( YMMV ) for testing purpose :


null:fileuploads lifo$ dd if=/dev/random of=out.txt bs=1000000 count=100
  1. Now start the server with script/server
  2. Open http://localhost:3000/upload in two tabs
  3. Start uploading that big fat file in first tab
  4. Switch the tab to second. And start pressing refresh button !!!

In all fairness, you may notice a momentary spike in memory/cpu usage while dealing with very large file uploads ( we’re talking a good 100s of MB here ) But believing that Rails process is blocked for the entire time it takes a user to upload the file from the browser, is outrageous.

Improve Rails Documentation 8

Posted by pratik
on Wednesday, March 19

This is almost a cross post from my email to rails-core mailing list :

Lately there had been many tiny doc patches at trac, which could easily escape our normal verification cycle. On top of that, we’re entering the whole new git-era of social development which eliminates the notion of “core” in a way.

Our aim is to make it really easy to get your doc patches in main repository. So to move forward, I’ve setup a git repository at GitHub : http://github.com/lifo/doc-rails/tree

I plan to give commit access to everyone who in the past have written a decent doc patch. Or anyone who is interested in writing one ( and can prove the quality of it by submitting at least one patch via whatever method ). And I’ll be syncing it with main Rails repository every week or so, after verifying all the changes.

So if you’re interested, you could email me directly or even easier is to catch me on IRC ( nick : lifo ). And once I add you to the doc-rails github project, you can commit directly to the repository without forking it ( and not bother about chasing anyone to pull your changes ).

Due to nature of this project/experiment, we’re gonna have to follow a strict set of guidelines for committers :

  • You are only allowed to change only docs and absolutely nothing else.
  • You should spell check your changes.
  • You shouldn’t add an entry to CHANGELOG. ( I will be adding changelog entries when syncing with main repo )
  • If you’re introducing a whole new style of doc writing, you should verify it with me first before you commit it.

Violation of any rule will result in lose of commit rights.

This is just a start. Moving forward, we could even do hackfest for writing docs and probably try to convince caboose doc fund holders to support this move in any way possible. But it all really depends on how things work out with doc-rails.

So anyone who has ever complained about quality of Rails API docs, now is your chance.

The project received a very positive response on the first day. We got whopping 8 patches !!

Now that you’re at it, you should follow Evan Weaver’s advise and throw some love all around ;-)