Put your message here! Contact me for more information
 
 







 

Archive for the ‘Ruby on Rails’ Category


 

If you have to deal with multiple configuration values for different environments, this easy and very simple trick will let you put all these values to an external YML file and have a convenient global hash to access the values. Another benefit of putting config values in a YML file is that you can re-factor shared values into a default block, and override values in environment-specific blocks. You can keep your code DRY, now keep your config DRY as well, instead of littering your different environment config files.

In your environment.rb, add this one line between the Rails::Initializer.run do … end block

Rails::Initializer.run do |config|
  # ... other config code

  SITE_CONFIG = YAML.load_file(  "#{RAILS_ROOT}/config/site_config.yml" )[ ENV['RAILS_ENV'] || RAILS_ENV ]
end

Create a new “site_config.yml” file the /config folder using the following skeleton:

# config/site_config.yml

# the shared configurations.  Override values here in each individual environment.
defaults: &defaults
  twitter_username: your_username
  twitter_password: your_password

  # Blog
  blog_url: http://alexle.net
  blog_feed: http://alexle.net/feed/
  blog_email: 

development:
  <<: *defaults
  # override
  twitter_username: dev_username
  twitter_password: dev_password

slicehost:
  <<: *defaults

test:
  <<: *defaults

staging:
  <<: *defaults

production:
  <<: *defaults

Restart your web server so that the environment.rb file is picked up. Now within your code, you can access the values using

  logger.debug SITE_CONFIG["twitter_username"]

The only gotcha of this method is that we cannot override nested configuration blocks due to the way YML files are interpreted.

# this sample won't work
defaults: &defaults
  twitter:
    username:
    password:

development:
  <<: *defaults

  # this override doesn't work since it replaces the twitter hash in the defaults block with a new
  # hash containing only the "username" key (The "password" key-value is gone!)
  twitter:
    username: dev_username

cheers!

view comments
 

My good friend Stephen Celis convinced me to give Rails.vim a shot for Rails development. My vi skill is not that great yet (e.g. beginner level), so Stephen recommended vimtutor, which does not come pre-installed on my CentOS server. So here’s a quick recap of getting vimtutor installed:

1. Installing “which”, which the vimtutor script uses to detect the vi version and copy the vimtutor script accordingly.

sudo yum install which

2. Installing “vim-enhanced” package, which contains the vimtutor

sudo yum install vim-enhanced

Now you should be able to do “vimtutor” and start a new vimtutor session. Happy h-j-k-l around and stops using the arrow keys :)

view comments
 

Here’s a quick summary to install ImageMagick from source and to handle all the common graphics files:

1) Install necessary libraries for image processing. ImageMagick will rely on these libraries to handle the images.

sudo yum install libjpeg-devel libpng-devel glib2-devel fontconfig-devel zlib-devel libwmf-devel freetype-devel libtiff-devel jasper jasper-devel ghostscript-fonts

jasper and jasper-devel is needed for Jpeg2000. Also ghostscrip-fonts is needed for some components (such as captcha generator).

2) Download the lastest imagemagick from source and install (untar, run ./configure, sudo make, sudo make install) The output of the ./configure command will show all supported file types

3) To validate the supported files format, run

convert -list configure | grep DELEGATES

the result should be something like

> convert -list configure | grep DELEGATES
DELEGATES bzlib fontconfig freetype jpeg jng jp2 lcms png tiff x11 xml wmf zlib

References:

* http://b.lesseverything.com/2007/6/24/setting-up-imagemagick-rmagick-on-redhat-centos

* http://forums.fedoraforum.org/archive/index.php/t-32148.html

view comments
 

For a Rails/SQLServer application I’m working on, I had to deal with pagination with custom queries because of the different joins. The mislav-will_paginate plugin works great for MySQL, but for SQL Server, the paginated query generated by the current SQL Server Adapter (I’m using activerecord-sqlserver-adapter-1.0.0.9250) does not work very well. The current implementation is targetted really for SQL Server 2000 and older versions since these versions do not have support for ROW_NUMBER() method. It is a major pain in the butt to do pagination with these databases. With the newer SQL Sever 2005, the job is a bit easier. Microsoft implemented the ROW_NUMBER() method with a convoluted syntax to have better support for pagination, but it is still a drag because of the weird syntax.

Semergence wrote in his blog about patching the SQLServerAdapter to support pagination. Based on his post, I improved ActiveRecord::ConnectionAdapters::SQLServerAdapter::add_limit_offset! to make the query work in a more general way with free-form queries, e.g. queries ran with the paginate_by_sql() method provided by mislav-will_paginate

Include this script in your environment.rb file, or an external file and “require” the file within environment.rb.

  # monkey-patching SQLServerAdapter to support SQL Server 2005-style pagination
  module ActiveRecord
    module ConnectionAdapters
      class SQLServerAdapter
        def add_limit_offset!(sql, options)
          puts sql
          options[:offset] ||= 0
          options_limit = options[:limit] ? "TOP #{options[:limit]}" : ""
          options[:order] ||= if order_by = sql.match(/ORDER BY(.*$)/i)
                                order_by[1]
                              else
                                sql.match('FROM (.+?)\b')[1] + '.id'
                              end
          sql.sub!(/ORDER BY.*$/i, '')
          sql.sub!(/SELECT/i, "SELECT #{options_limit} * FROM ( SELECT ROW_NUMBER() OVER( ORDER BY #{options[:order] } ) AS row_num, ")
          sql << ") AS t WHERE row_num > #{options[:offset]}”
          puts sql
          sql
        end
      end
    end
  end

The method above monkey-patches the SQLServerAdapter by overwriting the add_limit_offset! method.

Here’s a custom query that I used and the transformed result:

Resource.paginate_by_sql([
      %!SELECT  resources.*
        	,skills_count.skill_count
        FROM resources
        	,(
        		SELECT resource_id
        			, COUNT(*) AS skill_count
        		FROM resource_skills
            WHERE meta_skill_id IN (1,2,3,4,5,6,7,8,9,10)
        		GROUP BY resource_id
        	) AS skills_count
        WHERE resources.is_active = ?
          AND resources.id = skills_count.resource_id
        ORDER BY skill_count DESC
      !, true ], :page => page, :per_page => per_page

With :page = 1, :per_page = 2, the resulted SQL is:

SELECT TOP 2 * FROM ( SELECT ROW_NUMBER() OVER( ORDER BY skill_count DESC ) AS row_num, resources.*
 	,skills_count.skill_count
 FROM resources
 	,(
 		SELECT resource_id
 			, COUNT(*) AS skill_count
 		FROM resource_skills
 WHERE meta_skill_id IN (1,2,3,4,5,6,7,8,9,10)
 		GROUP BY resource_id
 	) AS skills_count
 WHERE resources.is_active = 1
 AND resources.id = skills_count.resource_id

 ) AS t WHERE row_num > 0

The will_pagination’s COUNT query is

SELECT COUNT(*) FROM (
 SELECT resources.*
 	,skills_count.skill_count
 FROM resources
 	,(
 		SELECT resource_id
 			, COUNT(*) AS skill_count
 		FROM resource_skills
 WHERE meta_skill_id IN (21,22)
 		GROUP BY resource_id
 	) AS skills_count
 WHERE resources.is_active = 1
 AND resources.id = skills_count.resource_id
 ) AS count_table

The ORDER BY part is automatically removed from the main query (which becomes a sub-select) by the plugin to speed up the query. This in turns sanatizes the sql so that SQL Server doesn’t not complain about nested “ORDER BY” within a sub-select. Neat!

The only catch with the current add_limit_offset! is that it does not support ALIAS-ing, because the aliasing confuses the reqex to parse out the ORDER BY condition in the OVER() part of the query.

For regular find() queries, here’s a sample result

Resource.find(:first)
# original query:  SELECT * FROM resources
# transformed:   SELECT TOP 1 * FROM ( SELECT ROW_NUMBER() OVER( ORDER BY resources.id ) AS row_number, * FROM resources ) AS t WHERE row_num > 0

Hope this helps and cheers!

view comments
 

I wanted to give mechanize a shot for a small prototype I was building.  It’s been a while since I last ran the gem command so of course the first thing it would do was to update itself.  For some reason, it got stuck forever.  It turned out that the latest hpricot gem was trying to build itself from source (! - very strange!) and gem updater got stuck looking for a compatible compiler.

So I downloaded the mechanize gem from Rubyforge instead and ran

gem install mechanize-0.7.7.gem

However, the gem still tried to access the gem index and eventually got stuck somewhere.  The ruby runtime ballooned up to more than 600MB as showed in Task Manager.  I dug up the gem install command and it turns out the gem install can be forced to install without checking the dependencies and without updaing the local index.   So the final gem command is

gem install mechanize-0.7.7.gem –no-ri –no-rdoc -f –no-update-sources

With the –no-update-sources flag, the gem won’t try to go off to http://gems.rubyforge.org and get the latest repository info but expecting there is already a local gem file.

Hope this help someone :)

view comments