Ditching Mongrel for mod_rails

Sunday, May 25th, 2008

I build a lot of Rails apps on a regular basis and each one I add to my server takes another bite out of my limited resources. The way I’ve traditionally setup a new Rails app was using a Mongrel cluster. I found it to be a lot more reliable and faster than the fcgi approach people use to use (and some still do). The downside to setting up a few dozen Rails apps on your server with each running a Mongrel cluster is that it eats up all your memory. One of my sites is starting to get a lot more traffic than it has been in the past and its putting additional strain on the server. As a result I decided to find an alternative to Mongrel. I’ve tried searching for alternatives in the past but everything sent me back to Mongrel. Until today of course when I came across Jamie Flournoy’s blog about mod_rails.

Excited for an alternative to raising a pack of resource hungry mongrels on my server I installed the gem and tried it out. It was exactly what I was looking for as far as ease of use straight away. All I needed to do was stop a mongrel cluster and simplify its virtual host directive in Apache to leave out the mod_proxy_rewrite and the other wonky rewrite rules. The first app I tested went smoothly but suddenly the server started misbehaving. Resources were being eaten and it wasn’t clear what was doing it because the app I was testing with is behind an Apache password and I’m the only user. I ended up having to turn off the mod_rails to get my system back in control. The problem turned out to be that by default mod_rails tries to test if your virtual host directory is a rails app or not. I have a few apps that I tossed in an instance of WordPress into a blog directory inside my rails app directory. I found it convenient to toss them all into the same directory since its all the same website. As a result mod_rails was doing a ../ check to see if the blog directory was a rails app which it decided it was. That’s where the craziness came in because its a php application. Anyway, the quick solution was to move the blog directory out of the Rails app directory.

Other than that my memory usage is way down. I’ve migrated all my low traffic sites to mod_rails and I’m happy with how they’re performing. There is a little delay on the initial load of the app but subsequent calls are quick because its already loaded. I can wait an extra 2-5 seconds for my low traffic apps to load in exchange for hundreds of extra megs of free memory.

I haven’t moved over my higher traffic money making sites yet and I’m not entirely sure I will until I’ve tested mod_rails a bit more. I’m extremely happy with the results thus far though.

Reducing Rails model callbacks

Tuesday, April 22nd, 2008

I’ve been working with a client to optimize parts of their Rails application. The problem is that a method in the app does some simple updating of a few model objects but because the model has so many relations it goes through a ton of unnecessary callbacks. There are issues related to data concurrency which means you have to do the callbacks but in this particular situation there won’t be any concurrent updates to the data so the callbacks can be omitted. The solution to the problem was trivial when using the save_without_callbacks Rails plugin. Just adding a simple:

[source:ruby]
some_object.skip_callbacks = true
[/source]

before the update_attribute reduced the number of SQL UPDATE queries from 90 to 8. Lesson learned. If you need to skip model relation callbacks on save this plugin is for you. Be careful about data concurrency issues though.

Google Base on Rails

Wednesday, December 19th, 2007

I was surprised to come up empty handed when searching for a Google Base Rails plugin. I wanted something that would allow me to easily create a feed into Google Base using their API. I didn’t find anything so I took the quick and short term solution and created my own Google Base xml feed. Its based on RSS 2.0 so its not incredibly difficult but I could have saved a few minutes if it was already written for me so here it is. My Google Base xml generator in Ruby on Rails. Its not complete and only has the fields that I specifically wanted for my products. Your feed will likely contain other fields so check the Google Base docs for more information on customizing it. You’ll notice that I thought Google Base was going to pull my xml feed when I initially wrote this but it turns out I have to use the API and this is just good for generating the xml file which you then have to manually upload to Google Base.

First, I added this to route.rb
[source language=":ruby"]
map.connect ‘google-base.xml’,
:controller => ‘google’,
:action => ‘base_feed’
[/source]

Then I created controllers/google_controller.rb
[source language=":ruby"]
class GoogleController < ApplicationController
def base_feed
@products = Product.find(:all)
end
end
[/source]

And finally, I create views/google/base_feed.rxml
[source language=":ruby"]
xml.instruct! :xml, :version=>"1.0"
xml.rss(:version=>"2.0", ‘xmlns:g’ => "http://base.google.com/ns/1.0"){
xml.channel{
xml.title("My Site products")
xml.link("http://brianmcquay.com/google-base.xml")
xml.description("My products are better than yours. You can touch them but I have to charge.")
xml.language(‘en-us’)
for product in @products
xml.item do
xml.title(product.name)
xml.link(product_url(product.id.to_s))
xml.description(product.description)
for photo in product.pictures
xml.tag! "g:image_link", photo_url(photo.id)
end
for category in product.categories
xml.tag! "g:product_type", category.name
end
xml.tag! "g:price", product.retail_price.to_s
xml.tag! "g:id", product.id.to_s
xml.tag! "g:payment_accepted", "Visa"
xml.tag! "g:payment_accepted", "MasterCard"
xml.tag! "g:tax_region", "Hawaii"
end
end
}
}
[/source]

There are obviously calls to helper methods in the base_feed.rxml file like product_url and photo_url. I use those so I can easily generate pretty seo urls anywhere I need them. You’ll need to replace those with however you create your urls.

This should suffice for at most 31 days when all the products I just added will expire in Google Base. I doubt I’ll bother creating a Google Base Rails plugin unless I see a noticeable increase in traffic and sales so don’t hold your breath.

Rails form select integer drop-down helper method

Friday, October 12th, 2007

I’ve often come across situations while developing Rails apps where I just want a simple integer drop-down box. The default Rails helpers for selects and its options aren’t really geared for something simple like that. I don’t want to have to create a collection of integers and pass them into blocks or any other ridiculous workaround in my views. I want them clean and simple. I created a helper function which allows you to easily create integer drop downs. Just toss this in your application_helper.rb.

[source:ruby]
def select_with_integer_options (object, column, start, stop, default = nil)
output = “


end
[/source]

And in your view simply call:

[source:ruby]
< %= select_with_integer_options (:someobject, :someattribute, 1, 20) %>
[/source]

And you have yourself an integer dropdown from 1-20. I tried to make the options and select formatting and id/name conventions the same as the rest of the Rails select/option helper methods to keep things consistent.

one-to-many associations made easy with ActiveScaffold

Friday, September 7th, 2007

I just dove in and started using ActiveScaffold for a new project. There was a little learning curve since I was doing that along with using RESTful Rails. I just started with a simple 1-many association. I setup my 2 models as usual with has_many :ads and belongs_to :affiliate. Then I created two controllers that just had something like:

[source language=":ruby"]
class AdController < ApplicationController
active_scaffold
layout ‘main’
end

class AffiliateController < ApplicationController
active_scaffold
layout ‘main’
end
[/source]

And finally added this to my routes:

[source language=":ruby"]
map.resources :ad, :active_scaffold => true
map.resources :affiliate, :active_scaffold => true
[/source]

When I went to http://localhost:3000/affiliate I was just amazed to see it actually worked. It let me create my affiliate and add ads to that affilaite on the fly. All my crud operations already done without having to manually link them in the controller like I had been doing in previous projects. I’m not sure how well its going to scale with the project in the long run but it certainly is an improvement over the traditional Rails scaffolding and I highly recommend giving it a try.

class_table_inheritance with acts_as_taggable

Friday, August 24th, 2007

If I have:

[source language=":ruby"]
class Product < ActiveRecord::Base
acts_as_taggable
end
[/source]

[source language=":ruby"]
class Subproduct < Product
class_table_inheritance
end
[/source]

[source language=":ruby"]
def find_tagged_with!(list)
find_by_sql([
"SELECT #{table_name}.* FROM #{table_name} " +
"WHERE #{table_name}.#{primary_key} in (" +
" SELECT taggable_id FROM taggings, tags " +
" WHERE tags.id=taggings.tag_id " +
" AND taggings.taggable_type = ? " +
" AND tags.name IN (?) " +
" GROUP BY taggable_id " +
" HAVING count(tags.id) >= ? " +
" )",
acts_as_taggable_options[:taggable_type], list, list.to_a.length
])
end
[/source]

The problem is that:

[source language=":ruby"]
Subproduct.find_tagged_with! (‘test’)
[/source]

returns all the Products tagged with ‘test’ rather than all Subproducts. So I tried:

[source language=":ruby"]
class Product < ActiveRecord::Base
end
[/source]

[source language=":ruby"]
class Subproduct < Product
class_table_inheritance
acts_as_taggable
end
[/source]

but that does the same thing. The problem boils down to the find_tagged_with! method using acts_as_taggable_options[:taggable_type] which is defined as class_name_of_active_record_descendant elsewhere in the acts_as_taggable plugin. The solution is to rewrite find_tagged_with! to :

[source language=":ruby"]
def find_tagged_with!(list)
find_by_sql([
"SELECT #{table_name}.* FROM #{table_name} " +
"WHERE #{table_name}.#{primary_key} in (" +
" SELECT taggable_id FROM taggings, tags " +
" WHERE tags.id=taggings.tag_id " +
" AND taggings.taggable_type = ? " +
" AND tags.name IN (?) " +
" GROUP BY taggable_id " +
" HAVING count(tags.id) >= ? " +
" )",
self.class.to_s, list, list.to_a.length
])
end
[/source]

This should work even for classes that aren’t using class table inheritance since it’ll just use the class name.

Refs: http://wiki.rubyonrails.org/rails/pages/ActsAsTaggablePluginHowto

Class table inheritance in Ruby on Rails

Friday, August 3rd, 2007

In my previous post I wrote about trying to get class table inheritance working in Rails. I passed by the hack and went with the plugin which ended up not really working right. I revisited the class table inheritance hack and actually got it working. I pretty much just dumped that code into a plugin so all you have to do is:

spaghetti.rb
[source language=":ruby"]
class Spaghetti < Noodle
class_table_inheritance
end
[/source]

noodle.rb
[source language=":ruby"]
class Noodle < ActiveRecord::Base
end
[/source]

One thing that did trip me up at first was that the spaghettis table can’t have an ‘id’ column for a primary key or you end up with some strange stack overflow errors. The noodles table also needs a ‘type’ varchar column to store the class names of the rows its storing. Even pagination works when you explicitly state what tables you’re conditions are using as in:

[source language=":ruby"]
@spaghetti_pages, @spaghettis = paginate :spaghettis,
:conditions => [ "noodles.length_in_inches = (?)", 10],
:per_page => 10
[/source]

Download the class_table_inheritance plugin

nil.[] error when using Rails migrations

Thursday, August 2nd, 2007

I just had a little trouble with Rails migrations. I was trying to add a column to a database table in the migration using:

[source language=":ruby"]add_column :condos, :product_id, :int, :null => false, :limit => ’11′[/source]

The syntax seems ok at first glance but it resulted in the following error:

[source language=":ruby"]
– add_column("condos", "product_id", "int")
rake aborted!
You have a nil object when you didn’t expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
/usr/lib64/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/schema_statements.rb:272:in `type_to_sql’
/usr/lib64/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/schema_statements.rb:122:in `add_column’
[/source]

The type_to_sql should have given it away to me but I overlooked it at first and spun my wheels trying to figure out what was wrong. As it turned out :int is not right and I need to use :integer instead.

Parsing CSV files sent via form post in Ruby on Rails

Thursday, May 31st, 2007

I’m not sure why it took me a while to figure this out but it did. The Ruby CSV documentation is really weak and really only explains how to read from a file. I googled around and couldn’t find anyone else talking about how to parse a CSV file sent via a form post (StringIO). I didn’t want to save it to a temp file just so I could follow the CSV docs examples. Here’s what I eventually got to work. Its so short and simple I feel silly for not figuring it out sooner.


parsed_file = CSV::Reader.parse(params[:dump][:file])
parsed_file.each do |row|
p row[0]
end

Monitary precision of a Ruby float

Wednesday, May 16th, 2007

I looked around and couldn’t seem to find a way to easily change the precision of a Ruby Float object to 2 decimals so I can use it as a dollar amount. I feel bad about posting this because I borrowed some of the code from another site but lost the link to it. Instead of extending the Float class I just wrote a function to do it for me. It takes a Float object and returns string. I returned a string instead of a Float because I’m using it in the view and outputting the result anyway. Anyway, here’s the helper function.


def monetize(number)
splitnum = number.to_s.split(".")[0]
scale = number.to_s.split(".")[1][0..1]
while scale.length < 2
scale = scale + '0'
end
"#{splitnum}.#{scale}"
end