1 How do sidebar filters look like internally
Let’s have a look at a typical ubiquo sidebar filter implementation (these filters are located in ubiquo_core):
module Ubiquo
module Extensions
module FilterHelpers
class TextFilter < BaseFilter
def configure(options={})
options[:field] = add_filter_prefix(options[:field]) if options[:field]
defaults = {
:field => :filter_text,
:caption => I18n.t('ubiquo.filters.text'),
}
@options = defaults.merge(options)
end
def render
lateral_filter(@options) do |keepable_params|
@context.form_tag(@options[:url_for_options], :method => :get) do
hidden_fields(keepable_params) + \
@context.content_tag(:p) do
@context.text_field_tag(@options[:field], @context.params[@options[:field]]) + "\n" + \
@context.submit_tag(I18n.t('ubiquo.search'))
end
end
end
end
def message
field = @options[:field].to_s
string = !@context.params[field].blank? && @context.params[field]
return unless string
info = @options[:caption].blank? ?
I18n.t('ubiquo.filters.filter_text', :string => string) :
"#{@options[:caption]} '#{string}'"
[info, [field]]
end
end
end
end
end
As we can see our TextFilter class inherits from BaseFilter which provides some common methods needed by our filters.
It’s important to note that we have the view context available through the @context instance variable, so we need to keep this in mind when trying to access vars or methods available only in the view.
We need to implement 3 public methods:
- configure. This method is the one it will be used in the filters_for definition and it must set the @options instance var.
- render. This method should return the rendered filter as a string. Obviously it is invoked during sidebar filter rendering.
- message. This method is used to build the message, on top of listings, that informs of the current filter selection.
It’s important to remember that filters are defined using a FilterSetBuilder. For example if we create a MyCustomFilter filter class, it would be invoked as shown in the example:
# app/helpers/ubiquo/articles_helper.rb
module Ubiquo::ArticlesHelper
def article_filters
filters_for 'Article' do |f|
f.text
f.locale
f.date
f.select :name, @collection
f.boolean :status
f.my_custom :title # <-- Your new filter
end
end
end
2 Building new sidebar filters using the existing ones
Let’s see another example:
module Ubiquo
module Extensions
module FilterHelpers
class BooleanFilter < LinkFilter
def configure(field, options = {})
defaults = {
:field => "filter_#{field}",
:caption => @model.human_attribute_name(field),
:caption_true => I18n.t('ubiquo.filters.boolean_true'),
:caption_false => I18n.t('ubiquo.filters.boolean_false'),
}
@options = defaults.merge(options)
collection = [
OpenStruct.new(:option_id => 0, :name => @options[:caption_false]),
OpenStruct.new(:option_id => 1, :name => @options[:caption_true]),
]
boolean_options = {
:id_field => :option_id,
:name_field => :name,
:collection => collection
}
@options.update(boolean_options)
end
end
end
end
end
As we can see this is a BoolenFilter based on a LinkFilter. It is basically a LinkFilter with a different configuration.
Now if we have a look at the LinkFilter:
module Ubiquo
module Extensions
module FilterHelpers
class LinkFilter < SelectFilter
def render
lateral_filter(@options) do |keepable_params|
filter_field = @options[:field]
@context.content_tag(:div, :id => 'links_filter_content') do
@context.content_tag(:ul) do
@options[:collection].inject('') do |result, object|
css_class = (@context.params[filter_field].to_s) == object.send(@options[:id_field]).to_s ? "on" : "off"
name = object.send(@options[:name_field])
keepable_params.update(filter_field => object.send(@options[:id_field]))
result += @context.content_tag(:li) do
@context.link_to name, keepable_params, :class => css_class
end
end
end
end
end
end
end
end
end
end
We can see that a LinkFilter is basically a SelectFilter but changing the render method.
So building new ubiquo sidebar filters using inheritance should be easy.
3 Testing ubiquo sidebar filters
Since testing sidebar filters involves a lot of setup. A Test helper class has been created to make it easier.
Let’s see an example:
class LinksOrSelectFilterTest < Ubiquo::Extensions::FilterHelpers::UbiquoFilterTestCase
def setup
@filter = LinksOrSelectFilter.new(@model, @context)
@filter.configure(:title,@model.all)
end
test "Should render a link filter with a small collection" do
doc = HTML::Document.new(@filter.render).root
assert_select doc, 'div#links_filter_content a', 3
end
test "Should render a select filter with a bigger collection" do
load_more_test_data
@filter.configure(:title, @model.all)
doc = HTML::Document.new(@filter.render).root
assert_select doc, 'form', 1
assert_select doc, 'select[name=filter_title]', 1
end
test "Should be able to get a message when the filter is set" do
@context.params.merge!({ 'filter_title' => 'my_title_text' })
assert_match /my_title_text/, @filter.message.first
end
private
def load_more_test_data
[
{ :title => 'Yesterday loot was cool',
:description => 'òuch réally?',
:published_at => Date.today,
:status => true
},
{ :title => 'Today is the new yesterday. NIÑA',
:description => 'bah loot',
:published_at => Date.today,
:status => false
},
{ :title => 'Tíred',
:description => 'stop',
:published_at => Date.tomorrow,
:status => false
}
].each { |attrs| @model.create(attrs) }
end
end
Ubiquo::Extensions::FilterHelpers::UbiquoFilterTestCase provides:
- @context view context.
- @model test model.
- Some test data.
All of which is needed to test filters. It is advisable review this class.