mincer 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +2 -1
  4. data/README.md +111 -64
  5. data/lib/mincer/action_view/sort_helper.rb +7 -7
  6. data/lib/mincer/base.rb +1 -1
  7. data/lib/mincer/config.rb +29 -0
  8. data/lib/mincer/core_ext/string.rb +5 -0
  9. data/lib/mincer/processors/cache_digest/processor.rb +54 -0
  10. data/lib/mincer/processors/helpers.rb +15 -0
  11. data/lib/mincer/processors/pagination/processor.rb +63 -0
  12. data/lib/mincer/processors/pg_json_dumper/processor.rb +69 -0
  13. data/lib/mincer/processors/pg_search/processor.rb +148 -0
  14. data/lib/mincer/processors/pg_search/sanitizer.rb +61 -0
  15. data/lib/mincer/processors/pg_search/search_engines/array.rb +41 -0
  16. data/lib/mincer/processors/pg_search/search_engines/base.rb +56 -0
  17. data/lib/mincer/processors/pg_search/search_engines/fulltext.rb +58 -0
  18. data/lib/mincer/processors/pg_search/search_engines/trigram.rb +41 -0
  19. data/lib/mincer/processors/pg_search/search_statement.rb +31 -0
  20. data/lib/mincer/processors/sorting/processor.rb +113 -0
  21. data/lib/mincer/version.rb +1 -1
  22. data/lib/mincer.rb +31 -31
  23. data/mincer.gemspec +0 -2
  24. data/spec/lib/mincer/action_view/sort_helper_spec.rb +11 -0
  25. data/spec/lib/mincer/base_spec.rb +15 -0
  26. data/spec/lib/mincer/config_spec.rb +7 -0
  27. data/spec/lib/{processors/cache_digest_spec.rb → mincer/processors/cache_digest/processor_spec.rb} +2 -9
  28. data/spec/lib/{processors/paginate_spec.rb → mincer/processors/pagination/processor_spec.rb} +43 -17
  29. data/spec/lib/{processors/pg_json_dumper_spec.rb → mincer/processors/pg_json_dumper/processor_spec.rb} +2 -6
  30. data/spec/lib/mincer/processors/pg_search/processor_spec.rb +268 -0
  31. data/spec/lib/mincer/processors/pg_search/sanitizer_spec.rb +38 -0
  32. data/spec/lib/mincer/processors/pg_search/search_engines/array_spec.rb +83 -0
  33. data/spec/lib/mincer/processors/pg_search/search_engines/fulltext_spec.rb +101 -0
  34. data/spec/lib/mincer/processors/pg_search/search_engines/trigram_spec.rb +91 -0
  35. data/spec/lib/mincer/processors/sorting/processor_spec.rb +181 -0
  36. data/spec/mincer_config.rb +38 -0
  37. data/spec/spec_helper.rb +40 -4
  38. data/spec/support/postgres_adapter.rb +12 -3
  39. data/spec/support/sqlite3_adapter.rb +3 -0
  40. metadata +42 -45
  41. data/lib/mincer/processors/cache_digest.rb +0 -50
  42. data/lib/mincer/processors/paginate.rb +0 -41
  43. data/lib/mincer/processors/pg_json_dumper.rb +0 -51
  44. data/lib/mincer/processors/search.rb +0 -34
  45. data/lib/mincer/processors/sort.rb +0 -59
  46. data/spec/lib/processors/search_spec.rb +0 -77
  47. data/spec/lib/processors/sort_spec.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5e7dd6365cd2d30f20a4595d0c5200254069203c
4
- data.tar.gz: d0ed94870519e2b8942ada6e8a832d036b4ab94e
3
+ metadata.gz: 8cf265c8b6c7d273af1a0b224a9a775a920aa662
4
+ data.tar.gz: 2979c71e82775a3d6f7d5b49eac0e7e022501f2c
5
5
  SHA512:
6
- metadata.gz: 64bdc85e7bfb0fb9f5be0cbd9e151c9da26360d3fdd01024ba5a7fcd9fcba320def5fa53ee171b6bac818ef3b50668664fc1631589fbb38a0577743b7d70cfb4
7
- data.tar.gz: eee05868aa5857630101de2a085dcaf5275390b2a248c3ea5fe50c1226458cd8c5e932637f975c7add37f468a57ef3f427f67a7cbeacee3a59274b010507fb20
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
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'codeclimate-test-reporter', require: nil
7
+ gem 'rspec', '~> 2.14.1'
8
8
  gem 'simplecov'
9
+ gem 'coveralls', require: false
9
10
  end
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/repos/52b775836956801ba7000bdb/badges/ec5d5862e4b89d10695c/gpa.png)](https://codeclimate.com/repos/52b775836956801ba7000bdb/feed)
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(usefull for caching)](#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
- `employess` as you would use any ActiveRecord::Relation object:
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
- Now lets's look what more can we do with this object
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). In order to use pagination you need to include one of them
70
- to your `Gemfile`. Example of using pagination
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 `Micner` objects will use pagination, even if no arguments are passed. To set default values for pagination please refer to `kaminari` or `will_paginate` documentation.
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` and order `ASC`. To change defaults you can override
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 atttributes from original scope, in our example `Employee.attribute_name`.
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 attribue was used for search.
170
- Generated url will be enchanced with `sort` and `order` attributes.
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
- Currently Mincer uses [Textacular](https://github.com/textacular/textacular) for search. This sets alot of restrictions:
176
- 1. Works only with postgres
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
- This will use `simple_search`, and if it will return no entries Mincer will run `fuzzy_search`. For more details on what
185
- is the difference between them, plese look refer to `textacular` github [page](https://github.com/textacular/textacular).
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 allowes you to dump query result to JSON using [Postgres JSON Functions](http://www.postgresql.org/docs/9.3/static/functions-json.html)
191
- Didn't had time to do benchmarks - but its' extremely fast.
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 usefull for cache invalidation on your views when you are using custom queries. We will modify a bit example:
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. Changing sort html classes
252
- 2. Changing default arguments(sort, order, pattern, page, per_page..)
253
- 3. Disabling some processors for all Mincer objects
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
- 'DESC'
15
+ 'desc'
16
16
  elsif collection.sort_order.to_s.downcase == 'desc'
17
- 'ASC'
17
+ 'asc'
18
18
  else
19
- 'ASC'
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.upcase == 'ASC'
31
- 'sorted order_down'
32
- elsif collection.sort_order.upcase == 'DESC'
33
- 'sorted order_up'
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,5 @@
1
+ class String
2
+ def to_sql
3
+ self.to_s
4
+ end
5
+ end
@@ -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)