Install Sphinx Search on Ubuntu (Almost All) 10

Posted by Piyush Gupta on April 13, 2010

If you’ve graduated from using Ferret you may have heard of the joys of Sphinx Search. Regardless of which plugin you use (there are several) you’ll need to install Sphinx itself. If you’re running Ubuntu Intrepid Ibex then these instructions are for you. That said, this will work on Debian or Ubuntu Hardy as well.

1. Update and Grab dependencies. Run these commands in order to get the files you need to install Sphinx.

sudo apt-get update
sudo apt-get dist-upgrade
sudo apt-get install build-essential
sudo apt-get install libmysqlclient15-dev

2. Download Sphinx. You can download the latest code from their website.

3.Untar the source and prep. Here’s where it gets a bit complicated. You’ll need to extract the source, change into the directory and configure Sphinx. Do that with these commands.

tar xvzf sphinx-0.9.8.1.tar.gz<br />cd sphinx-0.9.8.1/<br />./configure --with-mysql-includes=/usr/include/mysql --with-mysql-libs=/usr/lib/mysql<br />

4. Make and Install Sphinx Run the standard linux commands to install Sphinx.

make<br />sudo make install<br />

That’s it! Now you can grab your plugins and start up the server.

Technorati Tags: , , , , , , , ,

“Avoid multiple level nested resource routes” 1

Posted by Piyush Gupta on April 03, 2010

While generating RESTful routes in rails, it is easy to get carried away and generate many levels of nested resources for every level of has_many associations. For instance, I recently wrote something like this in my routes file:

  # in config/routes.rb
  map.resources :first_resources do |first|
    first.resources :second_resources do |second|
      second.resources :third_resources
    end
  end

Here FirstResouce has_many SecondResources and SecondResource has_many ThirdResources. Now imagine how you would get the path to edit the third level resource. You’d have to write something like this:

edit_first_resource_second_resource_third_resource_path(
@first_resource, @second_resource, @third_resource)

Yes, that’s how bad it would look when the routes are written for the innermost resource in the routes.

When I found myself doing something similar a few days ago, I decided to look for a better way to do this, and it amazes me how I missed this simple (and universally used) approach to writing nested resource routes.

Let’s take the example of a school where each course would have many batches, and each batch would have many exams.

# app/models/course.rb
class Course < ActiveRecord::Base
  has_many :batches
end

# app/models/batch.rb
class Batch < ActiveRecord::Base
  belongs_to :course
  has_many :exams
end

# exam.rb
class Exam < ActiveRecord::Base
  belongs_to :batch
end

Writing the routes with two levels of nesting would give me something like this:

  # config/routes.rb
  map.resources :courses do |course|
    course.resources :batches do |batch|
      batch.resources :exams
    end
  end

The route to edit an exam object @exam (belonging to batch @batch which in turn belongs to course @course) would look like this:

edit_course_batch_exam_path(@course, @batch, @exam)

This is way too long and rather than make it easy to understand the path, it is going to make it even more confusing when somebody tries to understand the path. The url is going to be something like http://domain.com/courses/1/batches/1/exams/1/edit.

The best solution in this case is to nest the resources to just one level so that batches a nested within courses (e.g. http://domain.com/courses/1/batches) and exams are nested only within batches (e.g. http://domain.com/batches/1/exams). Using a rails shortcut to define nested routes, we could write the routes like this:

 # in config/routes.rb
  map.resources :courses, :has_many => :batches
  map.resources :batches, :has_many => :exams

Now if you wanted to edit an exam resource, you could just write edit_batch_exam(@batch, @exam) without having to worry about specifying the course object in the route. If you needed the course object within the exams controller, all you need to do is write a before filter that loads the batch and course as shown here:

# app/controllers/exams_controller.rb
class ExamsController < ApplicationController
  before_filter :load_batch_and_course

  # RESTful actions

  private
  def load_batch_and_course
    @batch = Batch.find(params[:batch_id])
    @course = @batch.course
  end
end

Since we can get the course_id from the batch, there is no need for us to have the course_id in the route. This makes the routes much easier to understand.

Have you come across any situation where nesting more than one level is absolutely necessary? (I couldn't imagine any such situation off the top of my head.) How many levels of nested resources are okay with you? Do leave a comment and tell me what you think.

Technorati Tags: , , , ,

“Notes from the field upgrading to Rails 3″ 1

Posted by Piyush Gupta on April 02, 2010

I will be testing out the Rail3 upgrader and post a follow up article.

During the Rails 3 bug mash I decided to see what happens when I migrate a Rails 2.3.5 app to Rails 3.

Much has changed, the upgrade path is not trivial. Nonetheless, this site is now running Rails 3 with nginx and Ruby 1.9.2 head. So you can feel it in action.

Learn to love bundler

The proper way to manage dependencies in Rails 3 is using a new gem called bundler. This gem gives the gem command an extra command namely: gem bundle

When you run gem bundle it will determine all the correct dependencies for the files specified in a file called Gemfile

This is the Gemfile (which is in the Rails.root directory) for this site uses:

gem "rails", :git => "git://github.com/rails/rails.git"
gem "arel", :git => "git://github.com/rails/arel.git"
gem "authlogic", :git => 'git://github.com/binarylogic/authlogic.git'
gem "ruby-openid", :require_as => "openid"
gem "uuidtools"
gem "hpricot"
gem "bluecloth"
gem "diff-lcs", :require_as => "diff/lcs"
gem "liquid"
gem "rdiscount"
gem 'sanitize'
gem 'will_paginate'
gem 'haml'
gem 'mysql'
gem 'memcached'

Yehuda Katz has written extensively about the bundler. In a nutshell, dependencies using the old config.gem way of doing things in Rails 2 is fundamentally broken. Bundler fixes this. It creates a directory where all the dependencies exist. Meaning unlike Rails 2, each application in Rails 3 is meant to have a full copy of all the gems it depends on.

After this Gemfile is created you can run gem bundle to pull in all your dependencies. You can use this trick with other ruby apps as well, bundler is totally reusable. Bundler is how you vendor Rails and how you grab all your plugins.

So, to summarize step one: grab all your config.gem lines in your environment.rb file, and place them in your Gemfile, remove the config. and replace :lib with :require_as, require rails from github at the top.

The voodoo boot process of Rails 2 has been replaced

Take a minute and have a look at your Rails 2 boot.rb file. Its long and complicated.

Compare it with this sites boot.rb file:

# config/boot.rb
require File.expand_path('../../vendor/gems/environment', __FILE__)
# you can even require portions of rails instead of the whole kaboodle
require 'rails/all'
# since there is no config.gem anymore, require stuff you need to star your app here.
require 'authlogic'
require 'will_paginate'
require 'rdiscount'
require 'uuidtools'
require 'openid'

You also need a new environment.rb file, the old one contained lots of configuration data, instead environment.rb in rails 3 only contains a simple step:

require File.expand_path('../application', __FILE__)
CommunityTracker::Application.initialize!

You will need to set up an application

Gone are the days of Rails::Initializer and Rails.boot Rails 3 is serious about the move to rack, the best practice is to have an application.rb file that defines what it is your application does. This is the current sites one:

# config/application.rb
module CommunityTracker
  class Application < Rails::Application

    # Specify gems that this application depends on and have them installed with rake gems:install
    config.time_zone = 'UTC'
    config.action_mailer.delivery_method = :smtp
    config.action_mailer.smtp_settings = {:address => "localhost", :port => 25, :domain => "localhost"}

    config.middleware.use "RequestCache"
  end
end

if defined?(OpenID)
  OpenID::Util.logger = RAILS_DEFAULT_LOGGER
end

ActionView::Base.field_error_proc = Proc.new{|html_tag, instance| %(<spancolor: black; background-color: rgb(160, 255, 255);">field-with-errors">#{html_tag}</span>)}
require "haml"
require "haml/template"
Haml::Template.options[:format] = :html4
Haml::Template.options[:escape_html] = false

Most of your old code from environment.rb can stay here but you will have to get rid of all those config.gem lines and use bundler for that.

It’s a Rack application sir

You need a new config.ru file in your Rails.root

# config.ru
require ::File.expand_path('../config/environment',  __FILE__)
run CommunityTracker::Application.instance

This is the rack up file for your application, Rails 3 takes rack very seriously.

Your config files are broken

All the files in config/environments need some special handling:

# production.rb
CommunityTracker::Application.configure do
  config.action_mailer.default_url_options = {:host => 'community-tracker.com'}
  config.cache_classes = true
  config.action_controller.consider_all_requests_local = false
  config.action_controller.perform_caching             = true
  config.action_view.cache_template_loading            = true
end

Note the new block.

No more RAILS_ENV, RAILS_ROOT etc.

If you want to avoid a ton of pesky warnings, better move to using Rails.root and Rails.env today, rails 2.3.5 supports this syntax, the old RAILS_ENV is deprecated and it nags you to change it.

XSS protection is everywhere

Rails 3 tries to protect you from all sort of nasty cross site scripting. This protection is baked in pretty deep. Gone are the days you have to remember to escape a string using the h method. Instead Rails 3 assumes all the strings are unsafe, the various view helpers are hooked up to perform escaping for you.

Every string knows if it is safe for html rendering or not. (from active support)

class String
  attr_accessor :_rails_html_safe
  alias html_safe? _rails_html_safe

  def html_safe!
    @_rails_html_safe = true
    self
  end

  def html_safe
    dup.html_safe!
  end
#...
end

So, if for example you would like to link to a bold string use html_safe!

link_to "<b>google</b>", "http://google.com" #results in an escaped &lt;b&gt;
# this works
link_to "<b>google</b>".html_safe!, "http://google.com"

This issue will affect any large scale Rails app.

Rail 3 is much more strict.

In Rails 2 you could get away with having your helper functions defined in the wrong file, this is no longer the case in Rails 3. This is a good thing, it forced me to better organize my helpers.

Ruby 1.9.2 head is surprisingly stable and compatible

You can see the list of gems I’m using, its quite extensive. Most of the gems I tried work in 1.9.2 and the ones that do not have usually not been updated for years. 2010 may be the year people really start moving to 1.9.

Weird issues

Obviously not everything is perfect, Rails 3 is pre-release software which is not meant to be running in production. I noticed a few notable strange issues:

  1. When requiring gems in a lib sometimes the whole request just crashes, I have not figured out exactly how or why this happens.
  2. I can not run my application in development mode, I use rack middleware, and something about my middleware is wrong and Rails refuses to reload it in development mode.
  3. No rspec, rspec does not work on Rails 3, the rumor is that it will be supported in the rspec 2 time frame.
  4. The logger is a bit rough, the logging in rails 2 seemed a bit more consistent.
  5. This issue is likely to affect anyone using the open-id gem.

Technorati Tags: , , , ,