mincer 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/spilin/mincer.png)](https://travis-ci.org/spilin/mincer)
|
3
|
-
[![Code Climate](https://codeclimate.com/
|
3
|
+
[![Code Climate](https://codeclimate.com/github/spilin/mincer.png)](https://codeclimate.com/github/spilin/mincer)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/spilin/mincer/badge.png)](https://coveralls.io/r/spilin/mincer)
|
4
5
|
[![Gem Version](https://badge.fury.io/rb/mincer.png)](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)
|