Best ruby-on-rails-3 questions in June 2012

How to DRY scope methods used in two different classes?

5 votes

I am using Ruby on Rails 3.2.2 and I would like to retrieve / scope associated objects by "specifying" / "filtering on" an attribute value on those associated objects. That is, at this time I am using the following code:

class Article < ActiveRecord::Base
  def self.search_by_title(search)
    where('articles.title LIKE ?', "%#{search}%")
  end
end

class ArticleAssociation < ActiveRecord::Base
  def self.search_by_article_title(search)
    joins(:article).where('articles.title LIKE ?', "%#{search}%")
  end
end

In the above code the where('articles.title LIKE ?', "%#{search}%") clause is repeated twice and so I thought that it may be improved with the DRY principle: is it possible to use the Article.search_by_title method directly in the ArticleAssociation.search_by_article_title method?


Typical use cases are:

  • ArticleAssociation.search_by_article_title("Sample string")
  • Article.search_by_title("Sample string")

Unless you change the code structure completely, no.

You could do some hacking with lambdas, but that would be more code then the code you're DRYing. There is a such thing as good refactoring, and a such thing as bad refactoring. Unless a piece of very complex or long code is used in 2 or more places, then you can worry about refactoring. Code conventions are important, but for tiny one-method-call things like that its a waste and will probably make your code more cryptic.

Though, I know that it's annoying when people don't answer your question, so here:

class Article < ActiveRecord::Base
  SEARCH_BY_TITLE=lambda {|obj, search| obj.where('articles.title LIKE ?', "%#{search}%")}
  def self.search_by_title(search)
    SEARCH_BY_TITLE.call(self, search)
  end
end

class ArticleAssociation < ActiveRecord::Base
  def self.search_by_article_title(search)
    Article::SEARCH_BY_TITLE.call(joins(:article),search)
  end
end

That just makes a lambda as a constant that performs the where call on a specified object. Both methods just wrap that lambda.

Note: Although this may be considered more elegant, it will decrease performance a lot, as lambdas, closures, and the extra call are expensive in a dynamic language like Ruby. But I don't think that's an issue for you.

ActiveRecord query much slower than straight SQL?

5 votes

I've been working on optimizing my project's DB calls and I noticed a "significant" difference in performance between the two identical calls below:

connection = ActiveRecord::Base.connection()
pgresult = connection.execute(
  "SELECT SUM(my_column)
   FROM table
   WHERE id = #{id} 
   AND created_at BETWEEN '#{lower}' and '#{upper}'")

and the second version:

sum = Table.
      where(:id => id, :created_at => lower..upper).
      sum(:my_column)

The method using the first version on average takes 300ms to execute (the operation is called a couple thousand times total within it), and the method using the second version takes about 550ms. That's almost 100% decrease in speed.

I double-checked the SQL that's generated by the second version, it's identical to the first with exception for it prepending table columns with the table name.

  • Why the slow-down? Is the conversion between ActiveRecord and SQL really making the operation take almost 2x?
  • Do I need to stick to writing straight SQL (perhaps even a sproc) if I need to perform the same operation a ton of times and I don't want to hit the overhead?

Thanks!

A couple of things jump out.

Firstly, if this code is being called 2000 times and takes 250ms extra to run, that's ~0.125ms per call to convert the Arel to SQL, which isn't unrealistic.

Secondly, I'm not sure of the internals of Range in Ruby, but lower..upper may be doing calculations such as the size of the range and other things, which will be a big performance hit.

Do you see the same performance hit with the following?

sum = Table.
      where(:id => id).
      where(:created_at => "BETWEEN ? and ?", lower, upper).
      sum(:my_column)

Ruby gem for text comparison

5 votes

I am looking for a gem that can compare two strings (in this case paragraphs of text) and be able to gauge the likelihood that they are similar in content (with perhaps only a few words rearranged, changed). I believe that SO uses something similar when users submit questions.

I'd probably use something like Diff::LCS:

>> require "diff/lcs"
>> seq1 = "lorem ipsum dolor sit amet consequtor".split(" ")
>> seq2 = "lorem ipsum dolor amet sit consequtor".split(" ")
1.9.3-p194 :010 > Diff::LCS.diff(seq1, seq2).length
 => 2

It uses the longest common subsequence algorithm (the method for using LCS to get a diff is described on the wiki page).