Class: Philtre::Filter

Inherits:
Object
  • Object
show all
Defined in:
lib/philtre/filter.rb

Overview

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.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filter_parameters = nil, &custom_predicate_block) ⇒ Filter

Returns a new instance of Filter.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 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

Instance Attribute Details

#filter_parametersObject

Returns the value of attribute filter_parameters.



46
47
48
# File 'lib/philtre/filter.rb', line 46

def filter_parameters
  @filter_parameters
end

#predicatesObject

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



103
104
105
106
# File 'lib/philtre/filter.rb', line 103

def predicates
  # don't mess with the class' minimal set
  @predicates ||= self.class.predicates.clone
end

Class Method Details

.predicatesObject



97
98
99
# File 'lib/philtre/filter.rb', line 97

def self.predicates
  @predicates ||= Predicates.new
end

Instance Method Details

#[](key) ⇒ Object

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



234
235
236
237
# File 'lib/philtre/filter.rb', line 234

def []( key )
  rv = filter_parameters[key]
  rv unless rv.blank?
end

#[]=(key, value) ⇒ Object

easier access for filter_parameters



240
241
242
# File 'lib/philtre/filter.rb', line 240

def []=(key, value)
  filter_parameters[key] = value
end

#call(dataset) ⇒ Object Also known as: apply

return a modified dataset containing all the predicates



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 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

#clone(extra_parameters = {}) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
# 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?Boolean

Returns:

  • (Boolean)


48
# File 'lib/philtre/filter.rb', line 48

def empty?; filter_parameters.empty? end

#expr_for(predicate, field = nil) ⇒ Object

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.



158
159
160
161
162
# 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_hashObject

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



224
225
226
227
228
229
230
# 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

#expressionsObject

The set of expressions from the filter_parameters with values.



91
92
93
94
95
# 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) ⇒ Object

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



214
215
216
217
218
219
220
# 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) ⇒ Object

deallocate any cached lazies



173
174
175
176
177
178
179
# 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_clauseObject

return a possibly empty array of Sequel order expressions



129
130
131
# File 'lib/philtre/filter.rb', line 129

def order_clause
  @order_clause ||= order_expressions.map{|e| e.last}
end

#order_expr(order_predicate) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
# 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_expressionsObject

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



135
136
137
138
139
140
141
142
# 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) ⇒ Object



124
125
126
# File 'lib/philtre/filter.rb', line 124

def order_for( order_field )
  order_hash[order_field]
end

#order_hashObject



144
145
146
# File 'lib/philtre/filter.rb', line 144

def order_hash
  @order_hash ||= Hash[ order_expressions ]
end

#subset(*keys, &select_block) ⇒ Object

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



199
200
201
202
203
204
205
206
207
208
209
# 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) ⇒ Object

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.



150
151
152
# 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) ⇒ Object

for use in forms



165
166
167
# 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) ⇒ Boolean

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.

Returns:

  • (Boolean)


76
77
78
# File 'lib/philtre/filter.rb', line 76

def valued_parameter?( key, value )
  value.is_a?(Array) || !value.blank?
end

#valued_parametersObject

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.



83
84
85
86
87
88
# 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