mincer 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|
[](https://codeclimate.com/repos/52b775836956801ba7000bdb/feed)
|
4
4
|
[](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
|