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.
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)