Rob Olson's blog
This morning Yehuda Katz posted a response to my previous post, Technique for extending a method from a module, showing how a more modular organization of the Person class would allow for a solution that does not require a crazy meta programming hack. The idea is that by extracting the method we want to decorate into an ancestor class, Ruby makes it a lot easier to do what we want.
Previously I was aware that there were other ways I could structure the host class to make the module's job easier but I did not try that because but I was writing the code with the knowledge that I would only be in control of one side of the equation, the module. The host class was going to be written by the end-user of the Rubygem the module was to be packaged in. Since I did not want to try dictate how the end-user structured the host class I ended up adding a lot of complexity to the module. The goal became how to write the module in a such a way that the class would "just work" upon including Teacher without requiring any additional steps to be taken. Asking the user to create an AbstractPerson class that contained their initialize method and then creating a subclass felt like an obtrusive request to make through a README that would ultimate negatively impact the user's experience with the library.
Shortly after I put that blog post up I got this tweet from Josh Susser:
Update: Read the follow-up post Second thoughts on initializing modules
I was recently presented the problem of appending to the initialize method from a module that was being included. To do this it would need to override the class's initialize method with my own but keep the functionality of the original initialize method.
Whenever I need to do something in Ruby that I know will require some experimentation I like to move outside of my application and reproduce the problem in a simple way. For this problem I created a Person class that mixes in a Teacher module.
module Teacher
def initialize
puts "initializing teacher"
end
end
class Person
include Teacher
def initialize
puts "initializing person"
end
end
The goal is to get the following output when a Person object is created:
> Person.new
initializing teacher
initializing person
The basic program fails as expected; Teacher.new prints "initializing person" because Person's initialize is trumping Teacher's. Our immediate goal is to replace Person's initialize with Teacher's but in a way that preserves the original initialize method. By using alias_method we can create a copy of the original initialize method that we can call later.
Probably my favorite feature of the Solr full text search engine and the acts_as_solr plugin is a feature called boosting. Boosting is a great tool that gives you the ability to wield some influence over how the results that are returned are going to be ordered. When boosting is applied properly the quality of the search results appears improve dramatically even though the same results are being returned, just in a different order. There are two different kinds of boosting that you need to be aware of: column boosting and document boosting.
Field Boosting
Field or column boosting allows you to specify that if a query matches on a boosted field, give that more weight than usual. In the app I am working on, I added a field boost to the name attribute because I want results that have the query string in the name to appear before those results that have it somewhere in their description or as a tag. Here is an example of how to do a field boost when using acts_as_solr.
acts_as_solr :fields => [{:name => {:boost => 3.0}}, :description, :tags]
Document Boosting
A document boost should be utilized there is a way of quantifying one result as being better than another result, regardless of the query. For example, there are two entries in my database that both have a tag of "twitter client": Twitterific and Twitterfon. In the iPhone App Store, Twitterfon has a higher popularity rating than Twitterific so I want Twitterfon to appear above Twitterific if someone searches for "twitter client" within the app. To specify document boosting based on the app store popularity field I can pass a Proc object to acts_as_solr (rdoc) and return the member field that holds the popularity rating. A great thing about the Proc object is that I can execute any ruby code inside of it that I want. This is useful if the popularity score is not directly stored in the database and must be calculated on the fly.
acts_as_solr :fields => [:name, :description, :tags],
:boost => Proc.new { |item| item.popularity_score.to_f }
Closing
If you are using Solr at all it is important to be aware of what boosting can accomplish. When using multiple boosts, finding the right boost values to produce the best search results is a bit of black magic. I have found that after achieving "pretty good" results the law of diminishing returns comes into play and slows down progress. With a single boost it is much easier because there is only one variable in play.
During the process of upgrading a project from Rails 2.2.2 to Rails 2.3.2 several of our tests were breaking with the error:
Missing host to link to! Please provide :host parameter or set default_url_options[:host]
This error was most commonly occurring in model specs where we had mixed in ActionController::UrlWriter in order to get access the named routes (e.g. invitation_path) inside of the model class. I believe this change in a behavior is the result of this patch to Rails but I am not certain. Interestingly the code falls apart in the tests but it still works fine within the browser.
With the assistance of Adam Milligan we were able to find an acceptable way to handle setting the default_url_options in the test environment.
# app/models/invitation.rb
class Invitation < ActiveRecord::Base
include ActionController::UrlWriter
...
end
# spec/models/invitation_spec.rb
describe "Invitation" do
before(:all) do
Invitation.default_url_options[:host] = 'localhost'
end
after(:all) do
Invitation.default_url_options[:host] = nil
end
...
end
As I wrap up I want take a moment a properly shame myself for generating urls in the model. There is definitely a good argument that you should not be using named_routes in your models and I am eager to agree. Rails makes it hard to do for a reason and if you find yourself ever explicitly including UrlWriter take a step back and think the problem over. You may find yourself needlessly going down the wrong path and a different approach is in order.
One property of the Ruby object model and object oriented programming in general is that a subclass of an object automatically inherits all of the methods of its superclass. Classes can further expand the number of methods available by mixing in a Module, or several.
Because of mixins and subclassing even a class that has declared just a few methods can actually have hundreds of methods on it. In Ruby, all classes subclass Object by default which declares a hefty 45 methods, guaranteeing you to have at least that many. Out of the box in 1.8.7, a Ruby String object has 176 instance methods. If you are programming on top of the Rails framework, ActiveSupport adds 98 methods bringing the total to 274!
On numerous occasions I have needed to see what methods are available on an object I am working with I will type the following in irb.
myobject.methods - Object.instance_methods
This prints out a large array of instance methods with the methods inherited from Object removed from the list. This is useful but what if the object I am working with mixed in several modules and I am left with a list of over a hundred methods? It would be great to view which Class or Module each method came from. Well, actually there's a gem for that.™
Looksee
Looksee is a new gem by George Ogata that examines the method lookup path of any object. To use it add require 'looksee/shortcuts' to your ~/.irbrc. This will add a lp ("lookup path") method to your irb environment. When passed an object lp prints out a colored display showing where each of an object's methods lives.
Ask for Help
"We are attempting to upgrade one of our projects using Fixture Scenarios to Rails 2.3.2. When we attempt to run our tests we get errors about a corrupt fixture file. Is anyone successfully using Fixture Scenarios with Rails 2.3?"
Interesting Things
It is really easy to declare an additional route just for use in a controller test. All that is needed is to recall ActionController::Routing::Routes.draw at the top of your spec file. One situation in which this can be useful is if you are creating a new controller just for testing purposes.
class DummiesController < ApplicationController before_filter :require_profile def index end end ActionController::Routing::Routes.draw do |map| map.resources :dummies end describe DummiesController do ... end
Ask for Help
"When attempting to upload files with the aws-s3 gem I am receiving a lot of timeouts. This seems to happen with both small and large files. Has anyone run into this before?"
It was hypothesized that this could be the result of a slow internet connection and saturating the upload stream. Does anyone know of a fix for s3 timeouts?
Ask for Help
"Is there a good way to temporarily redefine a method on a controller during a functional test?"
Reopening a controller and overriding a method affects all tests in a suite. Is there a good way to redefine a controller method for a single test?
Interesting Things
- Rails 2.3.3 was released yesterday. It is a minor point release but a notable new feature is a faster decoding backend for JSON.
- The Evening with Palm webOS event is tonight at 6:30!
- There is a new mailing list for the Jasmine Javascript testing framework.
- Braid gotcha be careful when using Braid to checkout a specific branch of a Git repository. If you checkout a repository with Braid, and then later decide that you want to switch to a different branch (i.e. going from master to 2-3-stable with Rails) doing a
braid remove vendor/railsis not sufficient! The reason is when you add a external with Braid it also adds a remote branch in your Git repository. If you re-add the external, the old remote will be reused, even if you specify a different branch. To avoid this, remove the remote in addition to removing the external. To view your remotes rungit remoteand remove a remote withgit remove rm some/remote/name.
Weirdness with using serialized with Single Table Inheritance in Rails
If you have a class that uses a serialized categories attribute like this:
class Wibble < ActiveRecord::Base
serialized :categories
end
Interesting Things
Prior to RSpec 1.2.7, render_template in rspec-rails had a bug where render_template('new') would pass if 'newer' was rendered (or anything that started with 'new'). Internally render_template was converting the string argument to a regular expression which was allowing 'new' to positively match 'newer' even though it was not an exact match. In RSpec-Rails 1.2.7 this bug has been fixed.







