mincer 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +233 -2
- data/lib/mincer/processors/search.rb +1 -1
- data/lib/mincer/processors/sort.rb +6 -2
- data/lib/mincer/version.rb +1 -1
- data/spec/lib/processors/search_spec.rb +5 -0
- data/spec/support/sqlite3_adapter.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e760eb485129e914fb5ac87ab37ded1217ba6114
|
4
|
+
data.tar.gz: 99cba1c3544b4b7f294ceeb9f0fe25535e9943a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 09778cda72e7c9fb49542418647e8610c787a0ec5c79176773accc5a89d92593347c6347a76fe518aff83c1f432b78a29ef3b2ee5d14e464476fe61c0dfd0851
|
7
|
+
data.tar.gz: b77528eea0f446b5f832a44696c32f74777c375512210ad6412e9173017a746672ec881a905ca68b196c0d78e8cc9f92f510dc91ce2eb6b9a5add5cfe789493a
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,14 @@
|
|
3
3
|
[![Code Climate](https://codeclimate.com/repos/52b775836956801ba7000bdb/badges/ec5d5862e4b89d10695c/gpa.png)](https://codeclimate.com/repos/52b775836956801ba7000bdb/feed)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/mincer.png)](http://badge.fury.io/rb/mincer)
|
5
5
|
|
6
|
-
|
6
|
+
Mincer is an ActiveRecord::Relation wrapper that applies usefull features to your queries. It can:
|
7
|
+
|
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)
|
13
|
+
|
7
14
|
|
8
15
|
## Installation
|
9
16
|
|
@@ -20,8 +27,232 @@ Or install it yourself as:
|
|
20
27
|
$ gem install mincer
|
21
28
|
|
22
29
|
## Usage
|
30
|
+
Lets assume we have 2 models
|
31
|
+
|
32
|
+
class Employee < ActiveRecord::Base
|
33
|
+
belongs_to :company
|
34
|
+
end
|
35
|
+
|
36
|
+
class Company < ActiveRecord::Base
|
37
|
+
has_many :employees
|
38
|
+
end
|
39
|
+
|
40
|
+
Lets create class EmployeesListQuery class that will inherit from Mincer::Base, and instantiate it
|
41
|
+
|
42
|
+
class EmployeesListQuery < Mincer::Base
|
43
|
+
# method should always return relation
|
44
|
+
def build_query(relation, args)
|
45
|
+
custom_select = <<-SQL
|
46
|
+
employees.id,
|
47
|
+
employees.full_name as employee_name,
|
48
|
+
companies.name as company_name
|
49
|
+
SQL
|
50
|
+
relation.joins(:company).select(custom_select)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
employees = EmployeesListQuery.new(Employee)
|
55
|
+
|
56
|
+
`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
|
+
|
59
|
+
<% employees.each do |employee| %>
|
60
|
+
<%= employee.employee_name %>
|
61
|
+
<%= employee.company_name %>
|
62
|
+
<% end %>
|
63
|
+
|
64
|
+
|
65
|
+
Now lets's look what more can we do with this object
|
66
|
+
|
67
|
+
<a name="pagination"/>
|
68
|
+
### 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
|
71
|
+
|
72
|
+
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
|
+
To disable pagination you can use class method `skip_pagination!`:
|
77
|
+
|
78
|
+
class EmployeesListQuery < Mincer::Base
|
79
|
+
skip_pagination!
|
80
|
+
|
81
|
+
# method should always return relation
|
82
|
+
def build_query(relation, args)
|
83
|
+
custom_select = <<-SQL
|
84
|
+
employees.id,
|
85
|
+
employees.full_name as employee_name,
|
86
|
+
companies.name as company_name
|
87
|
+
SQL
|
88
|
+
relation.joins(:company).select(custom_select)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
<a name="sort"/>
|
93
|
+
### Sorting
|
94
|
+
|
95
|
+
Example of using sorting:
|
96
|
+
|
97
|
+
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
|
100
|
+
them like this
|
101
|
+
|
102
|
+
class EmployeesListQuery < Mincer::Base
|
103
|
+
# method should always return relation
|
104
|
+
def build_query(relation, args)
|
105
|
+
custom_select = <<-SQL
|
106
|
+
employees.id,
|
107
|
+
employees.full_name as employee_name,
|
108
|
+
companies.name as company_name
|
109
|
+
SQL
|
110
|
+
relation.joins(:company).select(custom_select)
|
111
|
+
end
|
112
|
+
|
113
|
+
def default_sort_attribute
|
114
|
+
'employee_name'
|
115
|
+
end
|
116
|
+
|
117
|
+
def default_sort_order
|
118
|
+
'DESC'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
To disable sorting use class method `skip_sorting!` like this:
|
123
|
+
|
124
|
+
class EmployeesListQuery < Mincer::Base
|
125
|
+
skip_sorting!
|
126
|
+
|
127
|
+
# method should always return relation
|
128
|
+
def build_query(relation, args)
|
129
|
+
custom_select = <<-SQL
|
130
|
+
employees.id,
|
131
|
+
employees.full_name as employee_name,
|
132
|
+
companies.name as company_name
|
133
|
+
SQL
|
134
|
+
relation.joins(:company).select(custom_select)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
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:
|
141
|
+
|
142
|
+
def allowed_sort_attributes
|
143
|
+
super + %w{employee_name company_name}
|
144
|
+
end
|
145
|
+
This will allow to sort by all Employee attributes + `employee_name` and `company_name`
|
146
|
+
|
147
|
+
Or restrict like this:
|
148
|
+
|
149
|
+
def allowed_sort_attributes
|
150
|
+
%w{employee_name}
|
151
|
+
end
|
152
|
+
in this example sorting allowed only by `employee_name`.
|
153
|
+
|
154
|
+
#### ActionView
|
155
|
+
|
156
|
+
If you are using Rails or bare ActionView there are few helper methods at your disposal to help generating links
|
157
|
+
for sorting. Currently there are 2 methods available: `sort_url_for` and `sort_class_for`.
|
158
|
+
|
159
|
+
Example of usage in HAML:
|
160
|
+
|
161
|
+
%ul
|
162
|
+
%li{ :class => (sort_class_for employees, 'id') }
|
163
|
+
= link_to 'ID', sort_url_for(employees, 'id')
|
164
|
+
%li{ :class => (sort_class_for employees, 'employee_name') }
|
165
|
+
= link_to 'Employee', sort_url_for(employees, 'employee_name')
|
166
|
+
%li{ :class => (sort_class_for employees, 'company_name') }
|
167
|
+
= 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
|
+
<a name="search"/>
|
173
|
+
### Search
|
174
|
+
|
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.
|
179
|
+
|
180
|
+
Example of usage:
|
181
|
+
|
182
|
+
employees = EmployeesListQuery.new(Employee, {'pattern' => 'whatever'})
|
183
|
+
|
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).
|
186
|
+
|
187
|
+
<a name="json"/>
|
188
|
+
### JSON generation
|
189
|
+
|
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.
|
192
|
+
|
193
|
+
Pros:
|
194
|
+
|
195
|
+
1. Speed
|
196
|
+
2. No extra dependencies(you don't need any other JSON generators)
|
197
|
+
|
198
|
+
Cons:
|
199
|
+
|
200
|
+
1. Works only with postgres version >= 9.2
|
201
|
+
2. If you are using ruby methods to generate some fields - you won't be able to use them in Mincer objects(Carrierwave image_urls, resource urls). You will have to duplicate logic inside postgres select query.
|
202
|
+
|
203
|
+
To dump query result to json string you have to call `to_json` on Mincer object:
|
204
|
+
|
205
|
+
EmployeesListQuery.new(Employee).to_json
|
206
|
+
|
207
|
+
In our example it will return something like this
|
208
|
+
|
209
|
+
"[{\"id\":1,\"employee_name\":\"John Smith\",\"company_name\":\"Microsoft\"},{\"id\":2,\"employee_name\":\"Jane Smith\",\"company_name\":\"37 Signals\"}]"
|
210
|
+
|
211
|
+
In addition you can pass option `root` to `to_json` method if you need to include root to json string:
|
212
|
+
|
213
|
+
EmployeesListQuery.new(Employee).to_json(root: 'employees')
|
214
|
+
# returns
|
215
|
+
"{\"employees\":[{\"id\":1,\"employee_name\":\"John Smith\",\"company_name\":\"Microsoft\"},{\"id\":2,\"employee_name\":\"Jane Smith\",\"company_name\":\"37 Signals\"}]}"
|
216
|
+
|
217
|
+
<a name="digest"/>
|
218
|
+
### Digest
|
219
|
+
|
220
|
+
Digest is very usefull for cache invalidation on your views when you are using custom queries. We will modify a bit example:
|
221
|
+
|
222
|
+
class EmployeesListQuery < Mincer::Base
|
223
|
+
digest! %w{employee_updated_at company_updated_at}
|
224
|
+
|
225
|
+
def build_query(relation, args)
|
226
|
+
custom_select = <<-SQL
|
227
|
+
employees.id,
|
228
|
+
employees.full_name as employee_name,
|
229
|
+
companies.name as company_name,
|
230
|
+
employees.updated_at as employee_updated_at,
|
231
|
+
companies.updated_at as company_updated_at
|
232
|
+
SQL
|
233
|
+
relation.joins(:company).select(custom_select)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
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
|
+
|
239
|
+
EmployeesListQuery.new(Employee).digest # "\\x20e93b4dc5e029130f3d60d697137934"
|
240
|
+
|
241
|
+
To generate digest you need to install extension 'pgcrypto'. If you use Rails, please use migration for that
|
242
|
+
|
243
|
+
enable_extension 'pgcrypto'
|
244
|
+
|
245
|
+
or run `CREATE EXTENSION IF NOT EXISTS pgcrypto;`
|
246
|
+
|
247
|
+
|
248
|
+
## TODO
|
249
|
+
|
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
|
254
|
+
2. Create rails generators.
|
23
255
|
|
24
|
-
TODO: Write usage instructions here
|
25
256
|
|
26
257
|
## Contributing
|
27
258
|
|
@@ -9,7 +9,7 @@ module Mincer
|
|
9
9
|
if Mincer.postgres? && !textacular?
|
10
10
|
warn 'You must include "textacular" to your Gemfile to use search'
|
11
11
|
@relation
|
12
|
-
elsif Mincer.postgres? && @args['pattern']
|
12
|
+
elsif Mincer.postgres? && @args['pattern'].present?
|
13
13
|
@relation.basic_search(@args['pattern']).presence || @relation.fuzzy_search(@args['pattern'])
|
14
14
|
else
|
15
15
|
@relation
|
@@ -7,13 +7,17 @@ module Mincer
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def apply
|
10
|
-
relation = @relation.order(
|
10
|
+
relation = @relation.order(sort_string)
|
11
11
|
@mincer.sort_attribute, @mincer.sort_order = relation.try(:order_values).try(:first).try(:split)
|
12
12
|
relation
|
13
13
|
end
|
14
14
|
|
15
|
+
def sort_string
|
16
|
+
sort_attr ? "#{sort_attr} #{order_attr}, #{@mincer.default_sort_attribute}" : "#{@mincer.default_sort_attribute} #{order_attr}"
|
17
|
+
end
|
18
|
+
|
15
19
|
def sort_attr
|
16
|
-
|
20
|
+
@mincer.allowed_sort_attributes.include?(@args['sort']) && @args['sort']
|
17
21
|
end
|
18
22
|
|
19
23
|
def order_attr
|
data/lib/mincer/version.rb
CHANGED
@@ -33,6 +33,11 @@ describe ::Mincer::Processors::Search do
|
|
33
33
|
query = subject.new(ActiveRecordModel, { 'pattern' => 'Bingo' })
|
34
34
|
query.to_a.count.should eq(1)
|
35
35
|
end
|
36
|
+
|
37
|
+
it 'avoids search when pattern is an empty string or spaces' do
|
38
|
+
query = subject.new(ActiveRecordModel, { 'pattern' => ' ' })
|
39
|
+
query.to_a.count.should eq(3)
|
40
|
+
end
|
36
41
|
end
|
37
42
|
|
38
43
|
|
@@ -1,4 +1,4 @@
|
|
1
1
|
def setup_basic_sqlite3_table
|
2
2
|
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
3
|
-
ActiveRecord::Base.connection.execute('CREATE TABLE active_record_models (id INTEGER
|
3
|
+
ActiveRecord::Base.connection.execute('CREATE TABLE active_record_models (id INTEGER PRIMARY KEY, text STRING)')
|
4
4
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mincer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Krasinsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|