class Philtre::Filter

Parse the predicates on the end of field names, and round-trip the search fields between incoming params, controller and views. So,

filter_parameters = {
  birth_year: ['2012', '2011'],
  title_like: 'sir',
  order: ['title', 'name_asc', 'birth_year_desc'],
}

Philtre.new( filter_parameters ).apply( Personage.dataset ).sql

should result in

SELECT * FROM "personages" WHERE (("birth_year" IN ('2012', '2011')) AND ("title" ~* 'bar')) ORDER BY ("title" ASC, "name" ASC, "date" DESC)

TODO pass a predicates: parameter in here to specify a predicates object.

Attributes

filter_parameters[RW]
predicates[W]

Public Class Methods

new( filter_parameters = nil, &custom_predicate_block ) click to toggle source
# File lib/philtre/filter.rb, line 28
def initialize( filter_parameters = nil, &custom_predicate_block )
  # This must be a new instance of Hash, because sometimes
  # HashWithIndifferentAccess is passed in, which breaks things in here.

  # Don't use symbolize_keys because that creates a dependency on ActiveSupport
  # have to iterate anyway to convert keys to symbols
  @filter_parameters =
  if filter_parameters
    filter_parameters.each_with_object(Hash.new){|(k,v),ha| ha[k.to_sym] = v}
  else
    {}
  end

  if block_given?
    predicates.extend_with &custom_predicate_block
  end
end
predicates() click to toggle source
# File lib/philtre/filter.rb, line 97
def self.predicates
  @predicates ||= Predicates.new
end

Public Instance Methods

[]( key ) click to toggle source

easier access for filter_parameters return nil for nil and '' and []

# File lib/philtre/filter.rb, line 234
def []( key )
  rv = filter_parameters[key]
  rv unless rv.blank?
end
[]=(key, value) click to toggle source

easier access for filter_parameters

# File lib/philtre/filter.rb, line 240
def []=(key, value)
  filter_parameters[key] = value
end
apply( dataset )
Alias for: call
call( dataset ) click to toggle source

return a modified dataset containing all the predicates

# File lib/philtre/filter.rb, line 51
def call( dataset )
  # mainly for Sequel::Model
  dataset = dataset.dataset if dataset.respond_to? :dataset

  # clone here so later order! calls don't mess with a Model's default dataset
  dataset = expressions.inject(dataset.clone) do |dataset, filter_expr|
    dataset.filter( filter_expr )
  end

  # preserve existing order if we don't have one.
  if order_clause.empty?
    dataset
  else
    # There might be multiple orderings in the order_clause
    dataset.order *order_clause
  end
end
Also aliased as: apply
clone( extra_parameters = {} ) click to toggle source
Calls superclass method
# File lib/philtre/filter.rb, line 181
def clone( extra_parameters = {} )
  new_filter = super()

  # and explicitly clone these because they may well be modified
  new_filter.filter_parameters = filter_parameters.clone
  new_filter.predicates = predicates.clone

  extra_parameters.each do |key,value|
    new_filter[key] = value
  end

  new_filter
end
empty?() click to toggle source
# File lib/philtre/filter.rb, line 48
def empty?; filter_parameters.empty? end
expr_for( predicate, field = nil ) click to toggle source

turn the expression at predicate into a Sequel expression with field, having the value for predicate. Will be nil if the predicate has no value in valued_parameters. Will always be a Sequel::SQL::Expression.

# File lib/philtre/filter.rb, line 158
def expr_for( predicate, field = nil )
  unless (value = valued_parameters[predicate]).blank?
    to_expr( predicate, value, field )
  end
end
expr_hash() click to toggle source

hash of keys to expressions, but only where there are values.

# File lib/philtre/filter.rb, line 224
def expr_hash
  vary = valued_parameters.map do |key, value|
    [ key, to_expr(key, value) ]
  end

  Hash[ vary ]
end
expressions() click to toggle source

The set of expressions from the filter_parameters with values.

# File lib/philtre/filter.rb, line 91
def expressions
  valued_parameters.map do |key, value|
    to_expr(key, value)
  end
end
extract!( *keys, &select_block ) click to toggle source

return a subset of filter parameters/predicates, but leave this object without the matching keys. NOTE does not operate on field names.

# File lib/philtre/filter.rb, line 214
def extract!( *keys, &select_block )
  rv = subset( *keys, &select_block )
  rv.to_h.keys.each do |key|
    filter_parameters.delete( key )
  end
  rv
end
initialize_copy( *args ) click to toggle source

deallocate any cached lazies

Calls superclass method
# File lib/philtre/filter.rb, line 173
def initialize_copy( *args )
  super
  @order_expressions = nil
  @order_hash = nil
  @order_clause = nil
  @valued_parameters = nil
end
order_clause() click to toggle source

return a possibly empty array of Sequel order expressions

# File lib/philtre/filter.rb, line 129
def order_clause
  @order_clause ||= order_expressions.map{|e| e.last}
end
order_expr( order_predicate ) click to toggle source
# File lib/philtre/filter.rb, line 110
def order_expr( order_predicate )
  return if order_predicate.blank?

  splitter = PredicateSplitter.new( order_predicate, nil )
  case
  when splitter === :asc
    Sequel.asc splitter.field
  when splitter === :desc
    Sequel.desc splitter.field
  else
    Sequel.asc splitter.field
  end
end
order_expressions() click to toggle source

Associative array (not a Hash) of names to order expressions TODO this should just be a hash

# File lib/philtre/filter.rb, line 135
def order_expressions
  @order_expressions ||=
  [filter_parameters[:order]].flatten.map do |order_predicate|
    next if order_predicate.blank?
    expr = order_expr order_predicate
    [expr.expression, expr]
  end.compact
end
order_for( order_field ) click to toggle source
# File lib/philtre/filter.rb, line 124
def order_for( order_field )
  order_hash[order_field]
end
order_hash() click to toggle source
# File lib/philtre/filter.rb, line 144
def order_hash
  @order_hash ||= Hash[ order_expressions ]
end
predicates() click to toggle source

Hash of predicate names to blocks. One way to get custom predicates is to subclass filter and override this.

# File lib/philtre/filter.rb, line 103
def predicates
  # don't mess with the class' minimal set
  @predicates ||= self.class.predicates.clone
end
subset( *keys, &select_block ) click to toggle source

return a new filter including only the specified filter parameters/predicates. NOTE predicates are not the same as field names. args to select_block are the same as to filter_parameters, ie it's a Hash TODO should use clone

# File lib/philtre/filter.rb, line 199
def subset( *keys, &select_block )
  subset_params =
  if block_given?
    filter_parameters.select &select_block
  else
    filter_parameters.slice( *keys )
  end
  subset = self.class.new( subset_params )
  subset.predicates = predicates.clone
  subset
end
to_expr( key, value, field = nil ) click to toggle source

turn a filter_parameter key => value into a Sequel::SQL::Expression subclass field will be the field name ultimately used in the expression. Defaults to key.

# File lib/philtre/filter.rb, line 150
def to_expr( key, value, field = nil )
  Sequel.expr( predicates[key, value, field] )
end
to_h(all=false) click to toggle source

for use in forms

# File lib/philtre/filter.rb, line 165
def to_h(all=false)
  filter_parameters.select{|k,v| all || !v.blank?}
end
valued_parameter?( key, value ) click to toggle source

called by valued_parameters to generate the set of expressions. This returns true for a value that show up in the set of expressions, false otherwise.

Intended to be overridden if necessary.

# File lib/philtre/filter.rb, line 76
def valued_parameter?( key, value )
  value.is_a?(Array) || !value.blank?
end
valued_parameters() click to toggle source

Values in the parameter list which are not blank, and not an ordering. That is, parameters which will be used to generate the filter expression.

# File lib/philtre/filter.rb, line 83
def valued_parameters
  @valued_parameters ||= filter_parameters.select do |key,value|
    # :order is special, it must always be excluded
    key.to_sym != :order && valued_parameter?(key,value)
  end
end