philtre 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +6 -5
- data/.travis.yml +7 -0
- data/README.md +169 -12
- data/Rakefile +8 -0
- data/TODO +0 -0
- data/lib/philtre.rb +59 -2
- data/lib/philtre/core_extensions.rb +31 -0
- data/lib/philtre/empty_expression.rb +9 -0
- data/lib/philtre/filter.rb +232 -0
- data/lib/philtre/grinder.rb +195 -0
- data/lib/philtre/place_holder.rb +41 -0
- data/lib/philtre/predicate_dsl.rb +25 -0
- data/lib/philtre/predicate_splitter.rb +40 -0
- data/lib/philtre/predicates.rb +109 -0
- data/lib/philtre/sequel_extensions.rb +30 -0
- data/lib/philtre/version.rb +2 -2
- data/philtre.gemspec +17 -10
- data/spec/dataset_spec.rb +57 -0
- data/spec/filter_spec.rb +502 -0
- data/spec/grinder_spec.rb +180 -0
- data/spec/predicate_splitter_spec.rb +54 -0
- data/tasks/console.rake +10 -0
- metadata +112 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 581e7addf0188f66a5a341f6dd205e3eb6b93a7e
|
4
|
+
data.tar.gz: 58a47bc162546a980faa4f17f5b28e75b0b86e9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a7cb668887750dfc955373d5cdc2a1f54ca06b56e9322217c29fbc3a7f4d0b598d142e7e25cfb91f80d8cb4e4642b3196b748d32a499c03edd4541bfcd2e41b
|
7
|
+
data.tar.gz: 86d1ef5d892b136cafbd8e0ac63ef45e799ee268fc93900e11c52409ac5e0cae5db221184915b6db70bba07125a60c39114ecf426467ef3cf9086ef930a1ebf8
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,12 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# philtre [](http://badge.fury.io/rb/philtre)
|
2
2
|
|
3
|
-
|
3
|
+
It's the [Sequel](http://sequel.jeremyevans.net) equivalent for Ransack, Metasearch, Searchlogic. If
|
4
|
+
this doesn't make you fall in love, I don't know what will :-p
|
4
5
|
|
5
|
-
|
6
|
-
If this doesn't make you fall in love, I don't know what will.
|
7
|
-
|
8
|
-
Yeah, I know. Corny naming. Filter => Philtre. But it lets you mix things up
|
9
|
-
and something awesome comes out the other side.
|
6
|
+
See philtre-rails for rails integration.
|
10
7
|
|
11
8
|
## Installation
|
12
9
|
|
@@ -14,6 +11,10 @@ Add this line to your application's Gemfile:
|
|
14
11
|
|
15
12
|
gem 'philtre'
|
16
13
|
|
14
|
+
Or for all the rails integration goodies
|
15
|
+
|
16
|
+
gem 'philtre-rails'
|
17
|
+
|
17
18
|
And then execute:
|
18
19
|
|
19
20
|
$ bundle
|
@@ -22,15 +23,171 @@ Or install it yourself as:
|
|
22
23
|
|
23
24
|
$ gem install philtre
|
24
25
|
|
25
|
-
## Usage
|
26
|
+
## Basic Usage
|
27
|
+
|
28
|
+
Parse the predicates on the end of field names, and modify a Sequel::Dataset
|
29
|
+
to retrieve matching rows.
|
30
|
+
|
31
|
+
So, using a fairly standard rails-style parameter hash:
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
filter_parameters = {
|
35
|
+
birth_year: ['2012', '2011'],
|
36
|
+
title: 'bar',
|
37
|
+
order: ['title', 'name_asc', 'birth_year_desc'],
|
38
|
+
}
|
39
|
+
|
40
|
+
# This would normally be a real Sequel::Dataset
|
41
|
+
personages_dataset = Sequel.mock[:personages]
|
42
|
+
|
43
|
+
philtre = Philtre.new( filter_parameters ).apply( personages_dataset ).sql
|
44
|
+
```
|
45
|
+
|
46
|
+
should result in (formatting added here for clarity)
|
47
|
+
|
48
|
+
``` SQL
|
49
|
+
SELECT *
|
50
|
+
FROM "personages"
|
51
|
+
WHERE
|
52
|
+
(("birth_year" IN ('2012', '2011'))
|
53
|
+
AND
|
54
|
+
("title" = 'bar'))
|
55
|
+
ORDER BY ("title" ASC, "name" ASC, "date" DESC)
|
56
|
+
```
|
57
|
+
|
58
|
+
## Predicates
|
59
|
+
|
60
|
+
```{title: 'sir'}``` is fine when you want to match on string equality. But
|
61
|
+
there are all kinds of other things you need to do. For example
|
62
|
+
|
63
|
+
```{title_like: 'sir', age_gt: 10}``` is for a where clause ```title ~* 'sir' and age > 10```
|
64
|
+
|
65
|
+
There are a range of predefined predicates, mostly borrowed from the other search gems:
|
66
|
+
|
67
|
+
```
|
68
|
+
gt
|
69
|
+
gte, gteq
|
70
|
+
lt
|
71
|
+
lte, lteq
|
72
|
+
eq
|
73
|
+
not_eq
|
74
|
+
matches, like
|
75
|
+
not_blank
|
76
|
+
like_all
|
77
|
+
like_any
|
78
|
+
```
|
79
|
+
|
80
|
+
## Custom Predicates
|
81
|
+
|
82
|
+
There are two ways:
|
83
|
+
|
84
|
+
1) You can also define your own by creating a Filter with a block:
|
85
|
+
|
86
|
+
``` ruby
|
87
|
+
philtre = Philtre.new filter_parameters do
|
88
|
+
def tagged_by_id(tag_ids)
|
89
|
+
Tag.db[:projects_tags]
|
90
|
+
.select(:personage_id)
|
91
|
+
.filter(tag_id: tag_ids, :project_id => :personage__id )
|
92
|
+
.exists
|
93
|
+
end
|
94
|
+
|
95
|
+
def really_fancy(tag_ids)
|
96
|
+
# do some really fancy SQL here
|
97
|
+
end
|
98
|
+
|
99
|
+
# etc...
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Now you can pass the filter_parameter hash ```{tagged_by_id: 45}```.
|
104
|
+
|
105
|
+
The result of a predicate block should be a ```Sequel::SQL::Expression``` (ie
|
106
|
+
one of Sequel's hash expressions in the simplest case) which will work instead
|
107
|
+
of its named placeholder. That is, if the placeholder is inside a SELECT
|
108
|
+
clause it worked work to give in an ORDER BY.
|
109
|
+
|
110
|
+
2) You could also inherit from ```Philtre::Filter``` and override
|
111
|
+
```#predicates```. And optionally override ```Philtre.new``` (which is just a
|
112
|
+
factory method on ```module Philtre```) to return the instance of your class.
|
113
|
+
|
114
|
+
## Advanced usage
|
115
|
+
|
116
|
+
There is also the ```Philtre::Grinder``` class which can insert placeholders into
|
117
|
+
your ```Sequel::Dataset``` definition, and then substitute those once it has the
|
118
|
+
parameter hash. Effectively this makes it a SQL macro engine.
|
119
|
+
|
120
|
+
Why so complicated? Well, it's really handy when you need to use aggregate
|
121
|
+
queries, and apply different values in the parameter hash to where clauses
|
122
|
+
inside and the outside of the aggregation. For example, give me a list of all
|
123
|
+
stores in a particular region who share of total sales was more than some
|
124
|
+
percentage. Yes, you can also use window functions to deal with that
|
125
|
+
particular query.
|
126
|
+
|
127
|
+
``` ruby
|
128
|
+
# This would normally be a real Sequel::Dataset
|
129
|
+
stores_dataset = Sequel.mock[:stores]
|
130
|
+
|
131
|
+
# parameterise it with placeholders
|
132
|
+
parameterised_dataset = stores_dataset.filter( :region.lieu, :sales_gt.lieu, :manager.lieu )
|
133
|
+
|
134
|
+
filter_parameters = {
|
135
|
+
region: 'The Bundus',
|
136
|
+
sales_gt: 10,
|
137
|
+
order: ['store_name', 'sales_desc'],
|
138
|
+
}
|
139
|
+
|
140
|
+
# generate the SQL you need
|
141
|
+
parameterised_dataset.grind( Philtre.new( filter_parameters ) ).sql
|
142
|
+
```
|
143
|
+
|
144
|
+
will result in
|
145
|
+
|
146
|
+
``` SQL
|
147
|
+
SELECT *
|
148
|
+
FROM stores
|
149
|
+
WHERE ((region = 'The Bundus') AND (sales > 10))
|
150
|
+
```
|
151
|
+
|
152
|
+
Notice that the manager part of the where clause is absent because
|
153
|
+
filter_parameters didn't have a manager key.
|
154
|
+
|
155
|
+
Look at the sql generated by parameterised_dataset and you'll see the placeholders
|
156
|
+
marked by SQL comments, so you can debug the Giant SQL Statement more easily. You
|
157
|
+
might also want to find a command-line SQL pretty printer (eg ``fsqlf```) and use it to produce
|
158
|
+
readable SQL instead of a very long hard-to-read string.
|
159
|
+
|
160
|
+
If you don't like the monkey-patching of Symbol with #lieu, you can use
|
161
|
+
several other ways to generate the placeholders. ```Philtre::PlaceHolder.new```
|
162
|
+
is canonical in that all the other possibilities use it.
|
163
|
+
|
164
|
+
## Highly Advanced Usage
|
165
|
+
|
166
|
+
Sometimes method chaining gets ugly. So you can say
|
167
|
+
|
168
|
+
``` ruby
|
169
|
+
store_id_range = 20..90
|
170
|
+
parameterised_dataset = stores_dataset.rolled do
|
171
|
+
where :region.lieu, :sales_gt.lieu, :manager.lieu
|
172
|
+
where store_id: store_id_range
|
173
|
+
select_append db[:products].join(:stores, :store_id => :id ).select(:product_name)
|
174
|
+
end
|
175
|
+
```
|
176
|
+
|
177
|
+
Notice that values outside the block are accessible inside, _without_
|
178
|
+
the need for a block parameter. This uses Ripar under the cover and indirects
|
179
|
+
the binding lookup, so may result in errors that you won't expect.
|
180
|
+
|
181
|
+
## Specs
|
182
|
+
|
183
|
+
Nothing fancy. Just:
|
26
184
|
|
27
|
-
|
28
|
-
dataset = filter.apply YourModel.dataset
|
185
|
+
$ rspec spec
|
29
186
|
|
30
187
|
## Contributing
|
31
188
|
|
32
|
-
1. Fork it (
|
189
|
+
1. Fork it ( http://github.com/djellemah/philtre/fork )
|
33
190
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
34
191
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
35
192
|
4. Push to the branch (`git push origin my-new-feature`)
|
36
|
-
5. Create
|
193
|
+
5. Create new Pull Request
|
data/Rakefile
CHANGED
data/TODO
ADDED
File without changes
|
data/lib/philtre.rb
CHANGED
@@ -1,5 +1,62 @@
|
|
1
|
-
require
|
1
|
+
require 'philtre/filter.rb'
|
2
|
+
require 'philtre/grinder.rb'
|
2
3
|
|
4
|
+
# The high-level interface to Philtre. There are several ways
|
5
|
+
# to use it:
|
6
|
+
# 1. Philtre.new
|
7
|
+
# philtre = Philtre.new name: 'Moustafa'
|
8
|
+
# 1. Philtre
|
9
|
+
# philtre = Philtre dataset: some_dataset, age_gt: 21
|
10
|
+
# philtre = Philtre dataset: some_dataset, with {age_gt: 21}
|
11
|
+
# 1. Philtre.filter
|
12
|
+
# philtre = Philtre.filter dataset: some_dataset, name: 'Moustafa', age_gt: 21
|
13
|
+
# philtre = Philtre.filter dataset: some_dataset, with: {name: 'Moustafa', age_gt: 21}
|
3
14
|
module Philtre
|
4
|
-
#
|
15
|
+
# Just a factory method that calls Filter.new
|
16
|
+
#
|
17
|
+
# philtre = Philtre.new params[:filter]
|
18
|
+
def self.new( *filter_parameters, &blk )
|
19
|
+
Filter.new *filter_parameters, &blk
|
20
|
+
end
|
21
|
+
|
22
|
+
# This is the high-level, easy-to-read smalltalk-style interface
|
23
|
+
# params:
|
24
|
+
# - dataset is a Sequel::Model or a Sequel::Dataset
|
25
|
+
# - with is the param hash (optional, or just use hash-style args)
|
26
|
+
#
|
27
|
+
# for x-ample, in rails you could do
|
28
|
+
#
|
29
|
+
# @personages = Philtre.filter dataset: Personage, with: params[:filter]
|
30
|
+
#
|
31
|
+
# or even
|
32
|
+
#
|
33
|
+
# @personages = Philtre.filter dataset: Personage, name: 'Dylan', age_gt: 21, age_lt: 67
|
34
|
+
#
|
35
|
+
def self.filter( dataset: nil, with: {}, **kwargs )
|
36
|
+
new(with.merge kwargs).apply(dataset)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create a grinder with the parameters, and
|
40
|
+
# use it on the dataset. Return the result.
|
41
|
+
#
|
42
|
+
# dataset should have placeholders, otherwise calling this
|
43
|
+
# method just warms your cpu.
|
44
|
+
def self.grind( dataset: nil, with: {}, **kwargs )
|
45
|
+
filter = new(with.merge kwargs)
|
46
|
+
Philtre::Grinder.new(filter).transform(dataset)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
require 'philtre/core_extensions.rb'
|
51
|
+
|
52
|
+
# And this is the even higher-level smalltalk-style interface
|
53
|
+
#
|
54
|
+
# Philtre dataset: Personage, with: params[:filter]
|
55
|
+
module Kernel
|
56
|
+
private
|
57
|
+
def Philtre( dataset: nil, with: {}, **kwargs )
|
58
|
+
Philtre.filter dataset: dataset, with: with, **kwargs
|
59
|
+
end
|
60
|
+
|
61
|
+
alias philtre Philtre
|
5
62
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# several ways to create placeholders in Sequel statements
|
2
|
+
module Kernel
|
3
|
+
private
|
4
|
+
def PlaceHolder( name, sql_field = nil, bt = caller )
|
5
|
+
Philtre::PlaceHolder.new name, sql_field, bt = caller
|
6
|
+
end
|
7
|
+
|
8
|
+
alias_method :Lieu, :PlaceHolder
|
9
|
+
end
|
10
|
+
|
11
|
+
class Symbol
|
12
|
+
def lieu( sql_field = nil )
|
13
|
+
Lieu self, sql_field, caller
|
14
|
+
end
|
15
|
+
|
16
|
+
def place_holder( sql_field = nil )
|
17
|
+
PlaceHolder self, sql_field, caller
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
unless Hash.instance_methods.include? :slice
|
22
|
+
class Hash
|
23
|
+
# return a hash containing only the specified keys
|
24
|
+
def slice( *other_keys )
|
25
|
+
other_keys.inject(Hash.new) do |hash, key|
|
26
|
+
hash[key] = self[key] if has_key?( key )
|
27
|
+
hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Philtre
|
2
|
+
# used when transforming to unaltered or partially
|
3
|
+
# altered datasets
|
4
|
+
class EmptyExpression < Sequel::SQL::Expression
|
5
|
+
# sometimes this is returned in place of an empty array
|
6
|
+
def empty?; true; end
|
7
|
+
def to_s_append( ds, s ); end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
Sequel.extension :blank
|
4
|
+
|
5
|
+
require 'philtre/predicate_splitter'
|
6
|
+
require 'philtre/predicate_dsl'
|
7
|
+
require 'philtre/predicates'
|
8
|
+
|
9
|
+
module Philtre
|
10
|
+
# Parse the predicates on the end of field names, and round-trip the search fields
|
11
|
+
# between incoming params, controller and views.
|
12
|
+
# So,
|
13
|
+
#
|
14
|
+
# filter_parameters = {
|
15
|
+
# birth_year: ['2012', '2011'],
|
16
|
+
# title_like: 'sir',
|
17
|
+
# order: ['title', 'name_asc', 'birth_year_desc'],
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Philtre.new( filter_parameters ).apply( Personage.dataset ).sql
|
21
|
+
#
|
22
|
+
# should result in
|
23
|
+
#
|
24
|
+
# SELECT * FROM "personages" WHERE (("birth_year" IN ('2012', '2011')) AND ("title" ~* 'bar')) ORDER BY ("title" ASC, "name" ASC, "date" DESC)
|
25
|
+
#
|
26
|
+
# TODO pass a predicates: parameter in here to specify a predicates object.
|
27
|
+
class Filter
|
28
|
+
def initialize( filter_parameters = nil, &custom_predicate_block )
|
29
|
+
# This must be a new instance of Hash, because sometimes
|
30
|
+
# HashWithIndifferentAccess is passed in, which breaks things in here.
|
31
|
+
# Don't use symbolize_keys because that creates a dependency on ActiveSupport
|
32
|
+
@filter_parameters =
|
33
|
+
if filter_parameters
|
34
|
+
# preserve 2.0 compatibility
|
35
|
+
filter_parameters.inject({}){|ha,(k,v)| ha[k.to_sym] = v; ha}
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
|
40
|
+
if block_given?
|
41
|
+
predicates.extend_with &custom_predicate_block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :filter_parameters
|
46
|
+
|
47
|
+
def empty?; filter_parameters.empty? end
|
48
|
+
|
49
|
+
# return a modified dataset containing all the predicates
|
50
|
+
def call( dataset )
|
51
|
+
# mainly for Sequel::Model
|
52
|
+
dataset = dataset.dataset if dataset.respond_to? :dataset
|
53
|
+
|
54
|
+
# clone here so later order! calls don't mess with a Model's default dataset
|
55
|
+
dataset = expressions.inject(dataset.clone) do |dataset, filter_expr|
|
56
|
+
dataset.filter( filter_expr )
|
57
|
+
end
|
58
|
+
|
59
|
+
# preserve existing order if we don't have one.
|
60
|
+
if order_clause.empty?
|
61
|
+
dataset
|
62
|
+
else
|
63
|
+
# There might be multiple orderings in the order_clause
|
64
|
+
dataset.order *order_clause
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
alias apply call
|
69
|
+
|
70
|
+
# Values in the parameter list which are not blank, and not
|
71
|
+
# an ordering. That is, parameters which will be used to generate
|
72
|
+
# the filter expression.
|
73
|
+
def valued_parameters
|
74
|
+
filter_parameters.select do |key,value|
|
75
|
+
key.to_sym != :order && (value.is_a?(Array) || !value.blank?)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# The set of expressions from the filter_parameters with values.
|
80
|
+
def expressions
|
81
|
+
valued_parameters.map do |key, value|
|
82
|
+
to_expr(key, value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.predicates
|
87
|
+
@predicates ||= Predicates.new
|
88
|
+
end
|
89
|
+
|
90
|
+
# Hash of predicate names to blocks. One way to get custom predicates is
|
91
|
+
# to subclass filter and override this.
|
92
|
+
def predicates
|
93
|
+
# don't mess with the class' minimal set
|
94
|
+
@predicates ||= self.class.predicates.clone
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_writer :predicates
|
98
|
+
|
99
|
+
def order_expr( order_predicate )
|
100
|
+
return if order_predicate.blank?
|
101
|
+
|
102
|
+
splitter = PredicateSplitter.new( order_predicate, nil )
|
103
|
+
case
|
104
|
+
when splitter === :asc
|
105
|
+
Sequel.asc splitter.field
|
106
|
+
when splitter === :desc
|
107
|
+
Sequel.desc splitter.field
|
108
|
+
else
|
109
|
+
Sequel.asc splitter.field
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def order_for( order_field )
|
114
|
+
order_hash[order_field]
|
115
|
+
end
|
116
|
+
|
117
|
+
# return a possibly empty array of Sequel order expressions
|
118
|
+
def order_clause
|
119
|
+
@order_clause ||= order_expressions.map{|e| e.last}
|
120
|
+
end
|
121
|
+
|
122
|
+
# Associative array (not a Hash) of names to order expressions
|
123
|
+
# TODO this should just be a hash
|
124
|
+
def order_expressions
|
125
|
+
@order_expressions ||=
|
126
|
+
[filter_parameters[:order]].flatten.map do |order_predicate|
|
127
|
+
next if order_predicate.blank?
|
128
|
+
expr = order_expr order_predicate
|
129
|
+
[expr.expression, expr]
|
130
|
+
end.compact
|
131
|
+
end
|
132
|
+
|
133
|
+
def order_hash
|
134
|
+
@order_hash ||= Hash[ order_expressions ]
|
135
|
+
end
|
136
|
+
|
137
|
+
# turn a filter_parameter key => value into a Sequel::SQL::Expression subclass
|
138
|
+
# field will be the field name ultimately used in the expression. Defaults to key.
|
139
|
+
def to_expr( key, value, field = nil )
|
140
|
+
Sequel.expr( predicates[key, value, field] )
|
141
|
+
end
|
142
|
+
|
143
|
+
# turn the expression at predicate into a Sequel expression with
|
144
|
+
# field, having the value for predicate. Will be nil if the
|
145
|
+
# predicate has no value in valued_parameters.
|
146
|
+
# Will always be a Sequel::SQL::Expression.
|
147
|
+
def expr_for( predicate, field = nil )
|
148
|
+
unless (value = valued_parameters[predicate]).blank?
|
149
|
+
to_expr( predicate, value, field )
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# for use in forms
|
154
|
+
def to_h(all=false)
|
155
|
+
filter_parameters.select{|k,v| all || !v.blank?}
|
156
|
+
end
|
157
|
+
|
158
|
+
attr_writer :filter_parameters
|
159
|
+
protected :filter_parameters=
|
160
|
+
|
161
|
+
# deallocate any cached lazies
|
162
|
+
def initialize_copy( *args )
|
163
|
+
super
|
164
|
+
@order_expressions = nil
|
165
|
+
@order_hash = nil
|
166
|
+
@order_clause = nil
|
167
|
+
end
|
168
|
+
|
169
|
+
def clone( extra_parameters = {} )
|
170
|
+
new_filter = super()
|
171
|
+
|
172
|
+
# and explicitly clone these because they may well be modified
|
173
|
+
new_filter.filter_parameters = filter_parameters.clone
|
174
|
+
new_filter.predicates = predicates.clone
|
175
|
+
|
176
|
+
extra_parameters.each do |key,value|
|
177
|
+
new_filter[key] = value
|
178
|
+
end
|
179
|
+
|
180
|
+
new_filter
|
181
|
+
end
|
182
|
+
|
183
|
+
# return a new filter including only the specified filter parameters/predicates.
|
184
|
+
# NOTE predicates are not the same as field names.
|
185
|
+
# args to select_block are the same as to filter_parameters, ie it's a Hash
|
186
|
+
# TODO should use clone
|
187
|
+
def subset( *keys, &select_block )
|
188
|
+
subset_params =
|
189
|
+
if block_given?
|
190
|
+
filter_parameters.select &select_block
|
191
|
+
else
|
192
|
+
filter_parameters.slice( *keys )
|
193
|
+
end
|
194
|
+
subset = self.class.new( subset_params )
|
195
|
+
subset.predicates = predicates.clone
|
196
|
+
subset
|
197
|
+
end
|
198
|
+
|
199
|
+
# return a subset of filter parameters/predicates,
|
200
|
+
# but leave this object without the matching keys.
|
201
|
+
# NOTE does not operate on field names.
|
202
|
+
def extract!( *keys, &select_block )
|
203
|
+
rv = subset( *keys, &select_block )
|
204
|
+
rv.to_h.keys.each do |key|
|
205
|
+
filter_parameters.delete( key )
|
206
|
+
end
|
207
|
+
rv
|
208
|
+
end
|
209
|
+
|
210
|
+
# hash of keys to expressions, but only where
|
211
|
+
# there are values.
|
212
|
+
def expr_hash
|
213
|
+
vary = valued_parameters.map do |key, value|
|
214
|
+
[ key, to_expr(key, value) ]
|
215
|
+
end
|
216
|
+
|
217
|
+
Hash[ vary ]
|
218
|
+
end
|
219
|
+
|
220
|
+
# easier access for filter_parameters
|
221
|
+
# return nil for nil and '' and []
|
222
|
+
def []( key )
|
223
|
+
rv = filter_parameters[key]
|
224
|
+
rv unless rv.blank?
|
225
|
+
end
|
226
|
+
|
227
|
+
# easier access for filter_parameters
|
228
|
+
def []=(key, value)
|
229
|
+
filter_parameters[key] = value
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|