Filtering and searching gets out of hand. Split it off into its own class.
The idea is that we’ll use it like this:
class ArticlesController < ApplicationController
def index
@articles = Article.filtered(params[:article_filters])
end
end
First we’ll need a mock filter. This is an object with a #restrict
method (the
filtering itself), and an equality check (the test magic):
class MockFilter
def initialize
@filters = []
end
def restrict(filters)
@filters = filters
self
end
def ==(other)
other.filters == self.filters
end
protected
attr_reader :filters
end
Once we have this mock, we can write a quick test. Here we’re filtering an article. The test ensures that we pass the right data:
describe Article, 'filtered' do
let(:filter) { MockFilter.new }
let(:filtered_articles) { filter.restrict(filters) }
let(:filters) { {'a' => 'b', 'c' => 'd'} }
around do |example|
@old_filter = Article.filter
Article.filter = filter
example.run
Article.filter = @old_filter
end
subject { FactoryBot.create(:article) }
it "produces filtered articles" do
Article.filtered(filters).should == filtered_articles
end
end
This test sets the class-wide filter (needed because Rails does class-level
programming instead of object-oriented programming), but cleans up afterward.
We pass the ActiveRecord::Relation
given by #scoped
so any existing chains persist.
Given the above test we can write the article class quickly and simply:
class Article < ActiveRecord::Base
cattr_writer :filter
def self.filtered(params)
filter.restrict(params)
end
protected
def self.filter
if defined?(@@filter) && @@filter
@@filter
else
@@filter = ArticleFilter.new(self.scoped)
end
end
end
Finally we can write the filter itself, encapsulating all filtering logic in one place. All the tests (not shown) are boring, tedious, and needed, and also all in one compact place.
class ArticleFilter
def initialize(relation)
@relation = relation
end
def restrict(restrictions)
published! if restrictions.try(:[], :published) == '1'
this_week! if restrictions.try(:[], :recent) == '1'
@relation
end
protected
def published!
where('published')
end
def this_week!
where('created_at > ?', 1.week.ago)
end
def where(*a)
@relation = @relation.where(*a)
end
end
Unobtrusive Ruby helps you avoid fat models.