mincer 0.1.2 → 0.2.0
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/.travis.yml +3 -0
- data/Gemfile +2 -1
- data/README.md +111 -64
- data/lib/mincer/action_view/sort_helper.rb +7 -7
- data/lib/mincer/base.rb +1 -1
- data/lib/mincer/config.rb +29 -0
- data/lib/mincer/core_ext/string.rb +5 -0
- data/lib/mincer/processors/cache_digest/processor.rb +54 -0
- data/lib/mincer/processors/helpers.rb +15 -0
- data/lib/mincer/processors/pagination/processor.rb +63 -0
- data/lib/mincer/processors/pg_json_dumper/processor.rb +69 -0
- data/lib/mincer/processors/pg_search/processor.rb +148 -0
- data/lib/mincer/processors/pg_search/sanitizer.rb +61 -0
- data/lib/mincer/processors/pg_search/search_engines/array.rb +41 -0
- data/lib/mincer/processors/pg_search/search_engines/base.rb +56 -0
- data/lib/mincer/processors/pg_search/search_engines/fulltext.rb +58 -0
- data/lib/mincer/processors/pg_search/search_engines/trigram.rb +41 -0
- data/lib/mincer/processors/pg_search/search_statement.rb +31 -0
- data/lib/mincer/processors/sorting/processor.rb +113 -0
- data/lib/mincer/version.rb +1 -1
- data/lib/mincer.rb +31 -31
- data/mincer.gemspec +0 -2
- data/spec/lib/mincer/action_view/sort_helper_spec.rb +11 -0
- data/spec/lib/mincer/base_spec.rb +15 -0
- data/spec/lib/mincer/config_spec.rb +7 -0
- data/spec/lib/{processors/cache_digest_spec.rb → mincer/processors/cache_digest/processor_spec.rb} +2 -9
- data/spec/lib/{processors/paginate_spec.rb → mincer/processors/pagination/processor_spec.rb} +43 -17
- data/spec/lib/{processors/pg_json_dumper_spec.rb → mincer/processors/pg_json_dumper/processor_spec.rb} +2 -6
- data/spec/lib/mincer/processors/pg_search/processor_spec.rb +268 -0
- data/spec/lib/mincer/processors/pg_search/sanitizer_spec.rb +38 -0
- data/spec/lib/mincer/processors/pg_search/search_engines/array_spec.rb +83 -0
- data/spec/lib/mincer/processors/pg_search/search_engines/fulltext_spec.rb +101 -0
- data/spec/lib/mincer/processors/pg_search/search_engines/trigram_spec.rb +91 -0
- data/spec/lib/mincer/processors/sorting/processor_spec.rb +181 -0
- data/spec/mincer_config.rb +38 -0
- data/spec/spec_helper.rb +40 -4
- data/spec/support/postgres_adapter.rb +12 -3
- data/spec/support/sqlite3_adapter.rb +3 -0
- metadata +42 -45
- data/lib/mincer/processors/cache_digest.rb +0 -50
- data/lib/mincer/processors/paginate.rb +0 -41
- data/lib/mincer/processors/pg_json_dumper.rb +0 -51
- data/lib/mincer/processors/search.rb +0 -34
- data/lib/mincer/processors/sort.rb +0 -59
- data/spec/lib/processors/search_spec.rb +0 -77
- data/spec/lib/processors/sort_spec.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cf265c8b6c7d273af1a0b224a9a775a920aa662
|
4
|
+
data.tar.gz: 2979c71e82775a3d6f7d5b49eac0e7e022501f2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e54d841658cd07e2366f13a66454d58c54882a96dbfbd277ade65653392c43257d0934f7bd193e3cf71afd7830a5adcc1ef3e89c95f40735a29c7c5c61824c6
|
7
|
+
data.tar.gz: 973a998ff2a27db653605b8448047c484720468eea2f2ae8fd8a33122a93e1f1652bd9aa663f6f494ccf23d3303b0a592820690a1aebd2c6aa4c43aba77d4b7a
|
data/.travis.yml
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
+
- 2.1.1
|
3
4
|
- 2.0.0
|
4
5
|
- 1.9.3
|
5
6
|
- ruby-head
|
@@ -7,6 +8,8 @@ rvm:
|
|
7
8
|
before_script:
|
8
9
|
- psql -c 'create database mincer;' -U postgres
|
9
10
|
- psql -d mincer -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' -U postgres
|
11
|
+
- psql -d mincer -c 'CREATE EXTENSION IF NOT EXISTS unaccent;' -U postgres
|
12
|
+
- psql -d mincer -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;' -U postgres
|
10
13
|
|
11
14
|
addons:
|
12
15
|
postgresql: "9.3"
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# Mincer
|
2
2
|
[](https://travis-ci.org/spilin/mincer)
|
3
|
-
[](https://codeclimate.com/github/spilin/mincer)
|
4
|
+
[](https://coveralls.io/r/spilin/mincer)
|
4
5
|
[](http://badge.fury.io/rb/mincer)
|
5
6
|
|
6
7
|
Mincer is an ActiveRecord::Relation wrapper that applies usefull features to your queries. It can:
|
7
8
|
|
8
|
-
[Paginate](#pagination)
|
9
|
-
[Sort](#sort)
|
10
|
-
[Search](#search)
|
11
|
-
[Dump to Json(Using postgres >= 9.2)](#json)
|
12
|
-
[Generate digest(
|
9
|
+
[Paginate](#pagination)
|
10
|
+
[Sort](#sort)
|
11
|
+
[Search](#search)
|
12
|
+
[Dump to Json(Using postgres >= 9.2)](#json)
|
13
|
+
[Generate digest(useful for caching)](#digest)
|
13
14
|
|
14
15
|
|
15
16
|
## Installation
|
@@ -32,57 +33,59 @@ Lets assume we have 2 models
|
|
32
33
|
class Employee < ActiveRecord::Base
|
33
34
|
belongs_to :company
|
34
35
|
end
|
35
|
-
|
36
|
+
|
36
37
|
class Company < ActiveRecord::Base
|
37
38
|
has_many :employees
|
38
39
|
end
|
39
|
-
|
40
|
+
|
40
41
|
Lets create class EmployeesListQuery class that will inherit from Mincer::Base, and instantiate it
|
41
42
|
|
42
43
|
class EmployeesListQuery < Mincer::Base
|
43
44
|
# method should always return relation
|
44
45
|
def build_query(relation, args)
|
45
46
|
custom_select = <<-SQL
|
46
|
-
employees.id,
|
47
|
-
employees.full_name as employee_name,
|
47
|
+
employees.id,
|
48
|
+
employees.full_name as employee_name,
|
48
49
|
companies.name as company_name
|
49
50
|
SQL
|
50
51
|
relation.joins(:company).select(custom_select)
|
51
52
|
end
|
52
53
|
end
|
53
|
-
|
54
|
+
|
54
55
|
employees = EmployeesListQuery.new(Employee)
|
55
56
|
|
56
57
|
`employees` will delegate all methods, that it can't find on itself, to relation objects. This means you can use
|
57
|
-
`
|
58
|
+
`employees` as you would use any ActiveRecord::Relation object:
|
58
59
|
|
59
60
|
<% employees.each do |employee| %>
|
60
61
|
<%= employee.employee_name %>
|
61
62
|
<%= employee.company_name %>
|
62
63
|
<% end %>
|
63
|
-
|
64
64
|
|
65
|
-
|
65
|
+
|
66
|
+
Now lets look what more can we do with this object
|
66
67
|
|
67
68
|
<a name="pagination"/>
|
68
69
|
### Pagination
|
69
|
-
Mincer supports [kaminari](https://github.com/amatsuda/kaminari) and [will_paginate](https://github.com/mislav/will_paginate).
|
70
|
-
to
|
70
|
+
Mincer supports [kaminari](https://github.com/amatsuda/kaminari) and [will_paginate](https://github.com/mislav/will_paginate).
|
71
|
+
In order to use pagination you need to include one of them
|
72
|
+
in your `Gemfile`. Example of using pagination
|
71
73
|
|
72
74
|
employees = EmployeesListQuery.new(Employee, {'page' => 2, 'per_page' => 10})
|
73
|
-
|
74
|
-
By default all `
|
75
|
+
|
76
|
+
By default all `Mincer` objects will use pagination, even if no arguments are passed.
|
77
|
+
To set default values for pagination please refer to `kaminari` or `will_paginate` documentation.
|
75
78
|
|
76
79
|
To disable pagination you can use class method `skip_pagination!`:
|
77
80
|
|
78
81
|
class EmployeesListQuery < Mincer::Base
|
79
82
|
skip_pagination!
|
80
|
-
|
83
|
+
|
81
84
|
# method should always return relation
|
82
85
|
def build_query(relation, args)
|
83
86
|
custom_select = <<-SQL
|
84
|
-
employees.id,
|
85
|
-
employees.full_name as employee_name,
|
87
|
+
employees.id,
|
88
|
+
employees.full_name as employee_name,
|
86
89
|
companies.name as company_name
|
87
90
|
SQL
|
88
91
|
relation.joins(:company).select(custom_select)
|
@@ -95,21 +98,21 @@ To disable pagination you can use class method `skip_pagination!`:
|
|
95
98
|
Example of using sorting:
|
96
99
|
|
97
100
|
employees = EmployeesListQuery.new(Employee, {'sort' => 'employee_name', 'order' => 'DESC'})
|
98
|
-
|
99
|
-
By default all Mincer objects will sort by attribute `id`
|
101
|
+
|
102
|
+
By default all Mincer objects will sort by attribute `id` in `ASC` order. To change defaults you can override
|
100
103
|
them like this
|
101
104
|
|
102
105
|
class EmployeesListQuery < Mincer::Base
|
103
106
|
# method should always return relation
|
104
107
|
def build_query(relation, args)
|
105
108
|
custom_select = <<-SQL
|
106
|
-
employees.id,
|
107
|
-
employees.full_name as employee_name,
|
109
|
+
employees.id,
|
110
|
+
employees.full_name as employee_name,
|
108
111
|
companies.name as company_name
|
109
112
|
SQL
|
110
113
|
relation.joins(:company).select(custom_select)
|
111
114
|
end
|
112
|
-
|
115
|
+
|
113
116
|
def default_sort_attribute
|
114
117
|
'employee_name'
|
115
118
|
end
|
@@ -117,18 +120,18 @@ them like this
|
|
117
120
|
def default_sort_order
|
118
121
|
'DESC'
|
119
122
|
end
|
120
|
-
end
|
121
|
-
|
123
|
+
end
|
124
|
+
|
122
125
|
To disable sorting use class method `skip_sorting!` like this:
|
123
126
|
|
124
127
|
class EmployeesListQuery < Mincer::Base
|
125
128
|
skip_sorting!
|
126
|
-
|
129
|
+
|
127
130
|
# method should always return relation
|
128
131
|
def build_query(relation, args)
|
129
132
|
custom_select = <<-SQL
|
130
|
-
employees.id,
|
131
|
-
employees.full_name as employee_name,
|
133
|
+
employees.id,
|
134
|
+
employees.full_name as employee_name,
|
132
135
|
companies.name as company_name
|
133
136
|
SQL
|
134
137
|
relation.joins(:company).select(custom_select)
|
@@ -136,15 +139,15 @@ To disable sorting use class method `skip_sorting!` like this:
|
|
136
139
|
end
|
137
140
|
|
138
141
|
Mincer will validate `sort` and `order` params and will not allow to sort by attributes that do not exist.
|
139
|
-
Default white list consists of all
|
140
|
-
You can expand the list overriding `allowed_sort_attributes` list like this:
|
142
|
+
Default white list consists of all attributes from original scope, in our example `Employee.attribute_name`.
|
143
|
+
You can expand the list by overriding `allowed_sort_attributes` list like this:
|
141
144
|
|
142
145
|
def allowed_sort_attributes
|
143
146
|
super + %w{employee_name company_name}
|
144
147
|
end
|
145
148
|
This will allow to sort by all Employee attributes + `employee_name` and `company_name`
|
146
149
|
|
147
|
-
Or restrict like this:
|
150
|
+
Or restrict it like this:
|
148
151
|
|
149
152
|
def allowed_sort_attributes
|
150
153
|
%w{employee_name}
|
@@ -157,7 +160,7 @@ If you are using Rails or bare ActionView there are few helper methods at your d
|
|
157
160
|
for sorting. Currently there are 2 methods available: `sort_url_for` and `sort_class_for`.
|
158
161
|
|
159
162
|
Example of usage in HAML:
|
160
|
-
|
163
|
+
|
161
164
|
%ul
|
162
165
|
%li{ :class => (sort_class_for employees, 'id') }
|
163
166
|
= link_to 'ID', sort_url_for(employees, 'id')
|
@@ -165,32 +168,76 @@ Example of usage in HAML:
|
|
165
168
|
= link_to 'Employee', sort_url_for(employees, 'employee_name')
|
166
169
|
%li{ :class => (sort_class_for employees, 'company_name') }
|
167
170
|
= link_to 'Company', sort_url_for(employees, 'company_name')
|
168
|
-
|
169
|
-
In this example `li` will receive `class="sorted order_down"` or `class="sorted order_up"` if this
|
170
|
-
Generated url will be
|
171
|
+
|
172
|
+
In this example `li` will receive `class="sorted order_down"` or `class="sorted order_up"` if this attribute was used for search.
|
173
|
+
Generated url will be enhanced with `sort` and `order` attributes.
|
171
174
|
|
172
175
|
<a name="search"/>
|
173
176
|
### Search
|
174
177
|
|
175
|
-
|
176
|
-
|
177
|
-
2. You have to include `textacular` to your Gemfile
|
178
|
-
3. You have to install postgres extension that [Textacular](https://github.com/textacular/textacular) uses for searching.
|
178
|
+
Mincer borrowed allot of search logic from [PgSearch](https://github.com/Casecommons/pg_search).
|
179
|
+
Currently search only works with postgres.
|
179
180
|
|
180
181
|
Example of usage:
|
181
182
|
|
182
183
|
employees = EmployeesListQuery.new(Employee, {'pattern' => 'whatever'})
|
183
184
|
|
184
|
-
|
185
|
-
|
185
|
+
By default search will be performed on all text/string columns of current model. If you want to explicitly set searchable columns or you can do so using `pg_search` method:
|
186
|
+
|
187
|
+
pg_search [{ :columns => %w{employees.full_name companies.name} } ]
|
188
|
+
|
189
|
+
By default search will use [unaccent] to ignore accent marks. You can read more about `unaccent` [here](http://www.postgresql.org/docs/current/static/unaccent.html)
|
190
|
+
You need to enable `unaccent` extension. If you use Rails, please use migration for that:
|
191
|
+
|
192
|
+
enable_extension 'unaccent'
|
193
|
+
|
194
|
+
or run `CREATE EXTENSION IF NOT EXISTS unaccent;`
|
195
|
+
|
196
|
+
If by any chance you need to disable `unaccent`:
|
197
|
+
|
198
|
+
pg_search [{ :columns => %w{employees.full_name companies.name} }, :ignore_accent => false ]
|
199
|
+
|
200
|
+
If you set `any_word` attribute to true - search will return all items containing any word in the search terms.
|
201
|
+
|
202
|
+
pg_search [{ :columns => %w{employees.full_name companies.name} }, :any_word => true ]
|
203
|
+
|
204
|
+
If you set `ignore_case` attribute to true - search will ignore case.
|
205
|
+
|
206
|
+
pg_search [{ :columns => %w{employees.full_name companies.name} }, :ignore_case => true ]
|
207
|
+
|
208
|
+
If you set `param_name` attribute to any other string - this string will be used to extract search term from params(Default param_name = 'patern').
|
209
|
+
|
210
|
+
pg_search [{ :columns => %w{employees.full_name companies.name} }, :param_name => 's']
|
211
|
+
employees = EmployeesListQuery.new(Employee, {'s' => 'whatever'})
|
212
|
+
|
213
|
+
There are 3 search engines you can use: `trigram`, `fulltext` and `array`.
|
214
|
+
You can specify which one to use, along with other options like this:
|
215
|
+
|
216
|
+
pg_search [{ :columns => %w{employees.full_name companies.name}, :engines => [:fulltext, :trigram] ,:ignore_case => true, :threshold => 0.5, :dictionary => :english }]
|
217
|
+
You can also add several search statements:
|
218
|
+
|
219
|
+
pg_search [
|
220
|
+
{ :columns => %w{employees.full_name}, :engines => [:fulltext, :trigram] ,:ignore_case => true},
|
221
|
+
{ :columns => %w{employees.tags}, :engines => [:array] ,:ignore_case => true, :any_word => true, param_name: 'tag'}
|
222
|
+
]
|
223
|
+
|
224
|
+
employees = EmployeesListQuery.new(Employee, {'patern' => 'whatever', 'tag' => 'fired'})
|
225
|
+
In this Mincer will search for all employees that are fired OR patern matches full_name. You can use additional option `join_with: :and`. To specify that you need only employees whith matching full name and tag
|
226
|
+
|
227
|
+
pg_search [
|
228
|
+
{ :columns => %w{employees.full_name}, :engines => [:fulltext, :trigram] ,:ignore_case => true},
|
229
|
+
{ :columns => %w{employees.tags}, :engines => [:array] ,:ignore_case => true, :any_word => true, param_name: 'tag'}
|
230
|
+
], join_with: :and
|
231
|
+
|
232
|
+
You can read details on search engines here: [Trigram](http://www.postgresql.org/docs/9.3/static/pgtrgm.html), [Fulltext](http://www.postgresql.org/docs/9.3/static/textsearch.html)
|
186
233
|
|
187
234
|
<a name="json"/>
|
188
235
|
### JSON generation
|
189
236
|
|
190
|
-
Mincer
|
191
|
-
Didn't had time to do
|
237
|
+
Mincer allows you to dump query result to JSON using [Postgres JSON Functions](http://www.postgresql.org/docs/9.3/static/functions-json.html)
|
238
|
+
Didn't had time to do benchmarking, but it's extremely fast.
|
192
239
|
|
193
|
-
Pros:
|
240
|
+
Pros:
|
194
241
|
|
195
242
|
1. Speed
|
196
243
|
2. No extra dependencies(you don't need any other JSON generators)
|
@@ -203,31 +250,31 @@ Cons:
|
|
203
250
|
To dump query result to json string you have to call `to_json` on Mincer object:
|
204
251
|
|
205
252
|
EmployeesListQuery.new(Employee).to_json
|
206
|
-
|
207
|
-
In our example it will return something like this
|
253
|
+
|
254
|
+
In our example it will return something like this
|
208
255
|
|
209
256
|
"[{\"id\":1,\"employee_name\":\"John Smith\",\"company_name\":\"Microsoft\"},{\"id\":2,\"employee_name\":\"Jane Smith\",\"company_name\":\"37 Signals\"}]"
|
210
|
-
|
257
|
+
|
211
258
|
In addition you can pass option `root` to `to_json` method if you need to include root to json string:
|
212
|
-
|
259
|
+
|
213
260
|
EmployeesListQuery.new(Employee).to_json(root: 'employees')
|
214
261
|
# returns
|
215
262
|
"{\"employees\":[{\"id\":1,\"employee_name\":\"John Smith\",\"company_name\":\"Microsoft\"},{\"id\":2,\"employee_name\":\"Jane Smith\",\"company_name\":\"37 Signals\"}]}"
|
216
263
|
|
217
|
-
<a name="digest"/>
|
264
|
+
<a name="digest"/>
|
218
265
|
### Digest
|
219
266
|
|
220
|
-
Digest is very
|
267
|
+
Digest is very useful for cache invalidation on your views when you are using custom queries. We will modify a bit example:
|
221
268
|
|
222
269
|
class EmployeesListQuery < Mincer::Base
|
223
270
|
digest! %w{employee_updated_at company_updated_at}
|
224
|
-
|
271
|
+
|
225
272
|
def build_query(relation, args)
|
226
273
|
custom_select = <<-SQL
|
227
|
-
employees.id,
|
228
|
-
employees.full_name as employee_name,
|
274
|
+
employees.id,
|
275
|
+
employees.full_name as employee_name,
|
229
276
|
companies.name as company_name,
|
230
|
-
employees.updated_at as employee_updated_at,
|
277
|
+
employees.updated_at as employee_updated_at,
|
231
278
|
companies.updated_at as company_updated_at
|
232
279
|
SQL
|
233
280
|
relation.joins(:company).select(custom_select)
|
@@ -237,20 +284,20 @@ Digest is very usefull for cache invalidation on your views when you are using c
|
|
237
284
|
In this example we will use 2 updated_at timestamps to generate digest. Whenever one of them will change - digest will change also. To get digest you should use method `digest` on Mincer model
|
238
285
|
|
239
286
|
EmployeesListQuery.new(Employee).digest # "\\x20e93b4dc5e029130f3d60d697137934"
|
240
|
-
|
287
|
+
|
241
288
|
To generate digest you need to install extension 'pgcrypto'. If you use Rails, please use migration for that
|
242
|
-
|
289
|
+
|
243
290
|
enable_extension 'pgcrypto'
|
244
|
-
|
291
|
+
|
245
292
|
or run `CREATE EXTENSION IF NOT EXISTS pgcrypto;`
|
246
293
|
|
247
294
|
|
248
295
|
## TODO
|
249
296
|
|
250
|
-
1. Create general configuration for Mincer that would allow:
|
251
|
-
1.
|
252
|
-
2.
|
253
|
-
3.
|
297
|
+
1. Create general configuration for Mincer that would allow to:
|
298
|
+
1. Change sort html classes
|
299
|
+
2. Change default arguments(sort, order, pattern, page, per_page..)
|
300
|
+
3. Disable some processors for all Mincer objects
|
254
301
|
2. Create rails generators.
|
255
302
|
|
256
303
|
|
@@ -12,11 +12,11 @@ module Mincer
|
|
12
12
|
def opposite_order_for(collection, attribute)
|
13
13
|
return nil unless collection.sort_attribute == attribute.to_s
|
14
14
|
if collection.sort_order.to_s.downcase == 'asc'
|
15
|
-
'
|
15
|
+
'desc'
|
16
16
|
elsif collection.sort_order.to_s.downcase == 'desc'
|
17
|
-
'
|
17
|
+
'asc'
|
18
18
|
else
|
19
|
-
'
|
19
|
+
'asc'
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -27,10 +27,10 @@ module Mincer
|
|
27
27
|
# <tt>attribute</tt> - Attribute that will be used to sort table
|
28
28
|
def sort_class_for(collection, attribute)
|
29
29
|
return nil unless collection.sort_attribute == attribute.to_s
|
30
|
-
if collection.sort_order.
|
31
|
-
|
32
|
-
elsif collection.sort_order.
|
33
|
-
|
30
|
+
if collection.sort_order.downcase == 'asc'
|
31
|
+
::Mincer.config.sorting.asc_class
|
32
|
+
elsif collection.sort_order.downcase == 'desc'
|
33
|
+
::Mincer.config.sorting.desc_class
|
34
34
|
else
|
35
35
|
''
|
36
36
|
end
|
data/lib/mincer/base.rb
CHANGED
@@ -7,7 +7,7 @@ module Mincer
|
|
7
7
|
|
8
8
|
# Builds query object
|
9
9
|
def initialize(scope, args = {})
|
10
|
-
@scope, @args, @relation = scope, args, build_query(scope, args)
|
10
|
+
@scope, @args, @relation = scope, ::ActiveSupport::HashWithIndifferentAccess.new(args), build_query(scope, args)
|
11
11
|
execute_processors
|
12
12
|
end
|
13
13
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# This should be extracted and moved to gem
|
2
|
+
module Mincer
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
yield(config)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.config
|
9
|
+
@config ||= ::Mincer::Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class Configuration
|
13
|
+
|
14
|
+
def add(processor, config_class)
|
15
|
+
define_config_accessors(processor, config_class)
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_config_accessors(processor, config_class)
|
19
|
+
class_eval <<-ACCESORS, __FILE__
|
20
|
+
def #{processor}
|
21
|
+
@#{processor} ||= #{config_class}.new
|
22
|
+
block_given? ? yield(@#{processor}) : @#{processor}
|
23
|
+
end
|
24
|
+
ACCESORS
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Mincer
|
2
|
+
module Processors
|
3
|
+
module CacheDigest
|
4
|
+
class Processor
|
5
|
+
|
6
|
+
def initialize(mincer)
|
7
|
+
@mincer, @args, @relation = mincer, mincer.args, mincer.relation
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply
|
11
|
+
@relation
|
12
|
+
end
|
13
|
+
|
14
|
+
def digest
|
15
|
+
Mincer.connection.execute(digest_sql).first.values.first
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def digest_sql
|
21
|
+
<<-SQL
|
22
|
+
SELECT digest(#{digest_columns_as_sql}, 'md5') as digest
|
23
|
+
FROM (#{@relation.connection.unprepared_statement { @relation.to_sql }}) as digest_q
|
24
|
+
SQL
|
25
|
+
end
|
26
|
+
|
27
|
+
def digest_columns_as_sql
|
28
|
+
@mincer.class.digest_columns.map { |column| "string_agg(digest_q.#{column}::text, '')" }.join(' || ')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
module Options
|
34
|
+
extend ActiveSupport::Concern
|
35
|
+
|
36
|
+
def digest
|
37
|
+
CacheDigest::Processor.new(self).digest
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def digest!(*digest_columns)
|
42
|
+
@digest_columns = digest_columns
|
43
|
+
end
|
44
|
+
|
45
|
+
def digest_columns
|
46
|
+
@digest_columns || []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
::Mincer.add_processor(:cache_digest)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Mincer
|
2
|
+
module Processors
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
def join_expressions(expressions, join_with)
|
6
|
+
case join_with
|
7
|
+
when :and then Arel::Nodes::And.new(expressions)
|
8
|
+
when :or then expressions.inject { |accumulator, expression| Arel::Nodes::Or.new(accumulator, expression) }
|
9
|
+
else expressions.inject { |accumulator, expression| Arel::Nodes::InfixOperation.new(join_with, accumulator, expression) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Mincer
|
2
|
+
module Processors
|
3
|
+
module Pagination
|
4
|
+
class Processor
|
5
|
+
def initialize(mincer)
|
6
|
+
@mincer, @args, @relation = mincer, mincer.args, mincer.relation
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply
|
10
|
+
if self.class.kaminari?
|
11
|
+
@relation.page(page).per(per_page)
|
12
|
+
elsif self.class.will_paginate?
|
13
|
+
@relation.paginate(page: page, per_page: per_page)
|
14
|
+
else
|
15
|
+
warn 'To enable pagination please add kaminari or will_paginate to your Gemfile'
|
16
|
+
@relation
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.kaminari?
|
21
|
+
defined?(::Kaminari)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.will_paginate?
|
25
|
+
defined?(::WillPaginate)
|
26
|
+
end
|
27
|
+
|
28
|
+
def page
|
29
|
+
@args[::Mincer.config.pagination.page_param_name]
|
30
|
+
end
|
31
|
+
|
32
|
+
def per_page
|
33
|
+
@args[::Mincer.config.pagination.per_page_param_name]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Options
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def skip_pagination!
|
42
|
+
active_processors.delete(Mincer::Processors::Pagination::Processor)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Configuration
|
48
|
+
include ActiveSupport::Configurable
|
49
|
+
|
50
|
+
config_accessor :page_param_name do
|
51
|
+
(::Mincer::Processors::Pagination::Processor.kaminari? && ::Kaminari.config.param_name) || :page
|
52
|
+
end
|
53
|
+
|
54
|
+
config_accessor :per_page_param_name do
|
55
|
+
:per_page
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
::Mincer.add_processor(:pagination)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Mincer
|
2
|
+
module Processors
|
3
|
+
module PgJsonDumper
|
4
|
+
class Processor
|
5
|
+
|
6
|
+
def initialize(mincer, options = {})
|
7
|
+
@mincer, @args, @relation, @options = mincer, mincer.args, mincer.relation, options
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply
|
11
|
+
@relation
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_json
|
15
|
+
if dump_supported?
|
16
|
+
Mincer.connection.execute(json_query).first['json']
|
17
|
+
else
|
18
|
+
warn 'To dump data to json with postgres you need to use postgres server version >= 9.2'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def dump_supported?
|
25
|
+
Mincer.postgres? && (Mincer.connection.send(:postgresql_version) >= 90200)
|
26
|
+
end
|
27
|
+
|
28
|
+
def json_query
|
29
|
+
if @options[:root]
|
30
|
+
json_query_with_root(@options[:root])
|
31
|
+
else
|
32
|
+
basic_json_query
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def base_sql
|
37
|
+
@mincer.sql
|
38
|
+
end
|
39
|
+
|
40
|
+
# Query for basic json generation. Ex: [{'id': 1}, {...}]
|
41
|
+
def basic_json_query(root = 'json')
|
42
|
+
<<-SQL
|
43
|
+
SELECT COALESCE(array_to_json(array_agg(row_to_json(subq))), '[]') AS #{root}
|
44
|
+
FROM (#{base_sql}) as subq
|
45
|
+
SQL
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generates json with root. Ex: If root = 'items' resulting json will be { 'items' => [...] }
|
49
|
+
def json_query_with_root(root)
|
50
|
+
<<-SQL
|
51
|
+
SELECT row_to_json(t) as json FROM ( #{basic_json_query(root)} ) as t
|
52
|
+
SQL
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
module Options
|
58
|
+
extend ActiveSupport::Concern
|
59
|
+
|
60
|
+
def to_json(options = {})
|
61
|
+
PgJsonDumper::Processor.new(self, options).to_json
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
::Mincer.add_processor(:pg_json_dumper)
|