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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48b963be79455d1a78fb2ba3ba717e7797a1c68b
4
- data.tar.gz: 38811ae437f8dd4e35d338e7abcd1a50988df393
3
+ metadata.gz: e760eb485129e914fb5ac87ab37ded1217ba6114
4
+ data.tar.gz: 99cba1c3544b4b7f294ceeb9f0fe25535e9943a1
5
5
  SHA512:
6
- metadata.gz: 9eaae3dda688bbb12f58fd56ce8850e5570354e8ed232bcff89614617c97dd73cd46018322559ddc5288c51a5e52425d7b5f3492deb89ac8898249a7b1fb995b
7
- data.tar.gz: fd7512ad6cc69e82410e525a517db83657050f6f40bdb0bc0741e97b507239e68ce2918622ded82012f7a74a0c5503f51c5c7177ee67f3fc4fa7bac0eceb1002
6
+ metadata.gz: 09778cda72e7c9fb49542418647e8610c787a0ec5c79176773accc5a89d92593347c6347a76fe518aff83c1f432b78a29ef3b2ee5d14e464476fe61c0dfd0851
7
+ data.tar.gz: b77528eea0f446b5f832a44696c32f74777c375512210ad6412e9173017a746672ec881a905ca68b196c0d78e8cc9f92f510dc91ce2eb6b9a5add5cfe789493a
data/.gitignore CHANGED
@@ -20,3 +20,5 @@ Guardfile
20
20
  atlassian-ide-plugin.xml
21
21
  .rspec
22
22
  spec/database.yml
23
+ .ruby-gemset
24
+ .ruby-version
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
- TODO: Write a gem description
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("#{sort_attr} #{order_attr}")
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
- (@mincer.allowed_sort_attributes.include?(@args['sort']) && @args['sort']) || @mincer.send(:default_sort_attribute)
20
+ @mincer.allowed_sort_attributes.include?(@args['sort']) && @args['sort']
17
21
  end
18
22
 
19
23
  def order_attr
@@ -1,7 +1,7 @@
1
1
  module Mincer
2
2
 
3
3
  def self.version
4
- Gem::Version.new '0.1.0'
4
+ Gem::Version.new '0.1.1'
5
5
  end
6
6
 
7
7
  module VERSION #:nodoc:
@@ -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 UNIQUE, text STRING)')
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.0
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: 2013-12-23 00:00:00.000000000 Z
11
+ date: 2014-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord