Rails Plugins: Scope_out and Will_paginate

update: I finally noticed that some people still visit this post. scope_out was great, but it’s been integrated into Rails now. It’s called name_scope and it works right out of the box with will_paginate.

Round 1: scope_out

A plugin I have found immensely useful is the scope_out plugin by John Andrews. The rationale for using the plugin is very nicely summed up by Dan Manges:

  • Your conditions are defined in ONE place.
  • You can extend associations if you want caching.
  • The ‘raw’ with_scope is available, making applying multiple conditions easy.
  • The find and calculate methods are both available.

Use it like so:

class Project < ActiveRecord::Base hasmany :tasks, :dependent => :deleteall end

class Tasks < ActiveRecord::Base belongs_to :project scope_out :active, :conditions => "status = 'active'" end

Now you can do the following:

# All active tasks
@tasks = Task.find_active(:all, :order => 'due_on')

# The active tasks for the project with memoization preserved
@tasks = @project.tasks.active

If you don’t see why that’s useful or understand what the means, read the whole blog post and contrast it with Jamis Buck’s take on associations, which is what originally led me to scopeout. I don’t like Jamis’ idea of defining methods on the associations because there’s some methods I need to use as part of the association OR as just a method on the original class. Also, what if a user also hasmany tasks? You have to redefine what ‘active’ is for that association too.

To install it appears you have to go to the svn repository since it’s not found in the default rails plugin repositories

ruby script/plugin install http://scope-out-rails.googlecode.com/svn/trunk/

and then rename /vendor/plugins/trunk to /vendor/plugins/scope_out.

Round 2: will_paginate

Using scoped out I’d been paginating my conditions with Rails classic pagination

def index
  @task_pages = Paginator.new self, Task.find_active(:all).count, 10, params[:page]
  @tasks = Task.find_active(:all, :order => 'due_on',
  :limit  =>  @person_pages.items_per_page,
  :offset =>  @person_pages.current.offset)
end

But now that pagination isn’t going to be part of the rails core I went looking for what people were going to do and found Err’s will_paginate plugin, which is slicker than what I’ve been doing anyway.

@tasks = Task.paginate :page => params[:page]

But can I still use my scope_out definitions?

@tasks = Task.paginate_active :page => params[:page]
NoMethodError: undefined method `find_all_active' for Task:Class

Not yet. willpaginate implements a method missing bit o’ code so you can do findby’s with an implicit all:

# These are equivalent
@tasks = Task.paginate_by_priority "high", :page => params[:page]
@tasks = Task.paginate_all_by_priority "high", :page => params[:page]

However, the way the regular expression is written messes up scopeout. This is easily fixed by replacing one line of code in vendor/plugins/willpaginate/lib/will_paginate/finder.rb. Replace

finder.sub! /^find/, 'find_all'

on line 91 with

finder.sub! /^find_by/, 'find_all_by'

Now paginateby will still have an implicit all and scopeout definitions will work with the pagination. I’m pretty sure this change doesn’t break anything with the will_paginate plugin, but if anyone finds problems with this let me know.