Conventions and API for filterrific scopes: Philosophy: auto generate simple scopes. Any customization is done by overriding the scope. Give lots of examples and annotated code. List of exposed scopes. On first request, load and cache them so that all meta-code has had a chance to run (e.g. to dynamically generate scopes). Only filters defined in filterrific will be exposed via URL. Other scopes will not be accessible CONFIG =============================================================================== * persist_in_session * default_label_method (for view helper selects, radio buttons, checkboxes), default => :name Examples CVIMS: * with_publishing_status: custom, looks at state as well as date * search_query * sorted_by: extracts direction, applies lower(...) to string columns * with_tags IN * authored_by belongs_to, { :author_id => [*author_ids] } PTS: * has_list_options :defaults => { :publication_date_min => lambda { Date.new(Date.today.year, 1, 1).to_s(:calendar_display_area) }, :publication_date_max => lambda { Date.new(Date.today.year + 3, 12, 31).to_s(:calendar_display_area) }, :sorted_by => 'publication_date_asc', :with_language => Language.all.map(&:id), :with_programme => Programme.all.map(&:id), :with_publication_status => [1, 2, 5, 7], :with_publication_type => PublicationType.all.map(&:id) - [PublicationType.find_partner.nil_or(:id)], :with_sales_item_status => %w[sales_item non_sales_item], :with_commercial_title_status => %w[commercial_title non_commercial_title] } * interesting method: condition_publication_date_for_named_scope (allows pseudo procs for dates). helpful because procs cannot be marshalled * # have to use :include instead of :joins for :roles. If I use :joins, it does an inner join and # returns duplicates for publications. Same for :with_role_type scope. # roles is a habtm relation with publication. named_scope :with_person, lambda{ |person_ids| { :conditions => ["roles.person_id IN (?)", [*person_ids].map(&:to_i)], :include => :roles } } * named_scope :with_commercial_title_status, lambda{ |statuses| v = ['1 = 2'] # never true, used if no checkbox is checked ("0") v << 'publications.service_category_id = 1' if statuses.include?("commercial_title") v << 'publications.service_category_id != 1' if statuses.include?("non_commercial_title") { :conditions => v.join(' OR ') } } * named_scope :sorted_by, lambda { |sort_option| # every sorting should have a set of secondary sort keys. We append those for each case where applicable. secondary_sort_keys = "publications.publication_date asc, lower(publications.short_title) asc, publications.id asc" direction = (sort_option =~ /desc$/) ? 'desc' : 'asc' case sort_option.to_s when /^extent_/ { :order => "publications.extent #{direction}" } when /^language_/ { :order => "lower(languages.name) #{direction}, #{secondary_sort_keys}", :joins => :language } when /^PM_/ { :order => "lower(publications.project_manager_name) #{direction}, #{secondary_sort_keys}" } when /^programme_/ { :order => "lower(programmes.name) #{direction}, #{secondary_sort_keys}", :joins => :programme } else raise(ArgumentError, "Invalid sort option: #{sort_option.inspect}") end } TIRO * filterrific :defaults => { :sorted_by => 'most_recent', :on_or_after => lambda { Time.zone.now.beginning_of_month.to_s(:date) }, :billable => true } * scope :billable, lambda{ |yes_or_no| case yes_or_no when "yes", true where('activities.is_billable = ?', true) when "no", false where('activities.is_billable = ?', false) else # no setting, don't return any conditions end } * scope :for_person, lambda { |person| where("projects.person_id = ?", person.id).joins(:project) } * scope :for_date_range, lambda{ |dates| where('activities.date <= ? AND activities.date >= ?', dates.max, dates.min) } * scope :on_or_after, lambda{ |date| d = Date.parse(date) if date.is_a?(String) where('activities.date >= ? ', d) } CANDO * saved searches * named_scope :search_query, lambda{ |query| # Matches using LIKE. LIKE is case INsensitive with MySQL (Development), # however it is case sensitive with PostGreSQL (Heroku) # To make it work in both worlds, I force everything to lower case. return {} unless query.is_a?(String) # condition query string, parse into individual keywords terms = query.downcase.split(/\s+/) # replace "*" with "%" for wildcard searches terms = terms.map { |e| e.gsub('*', '%') } { :conditions => [ terms.map { |term| "lower(conference_participations._first_name) LIKE ? OR lower(conference_participations._last_name) LIKE ?" }.join(' AND '), *(terms.map { |e| e.downcase } * 2) ] } } STRATADOCS * list starts with self as AR proxy