ransack 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce6d8c3f3a255efe1c665afc046f8b03c5ff907f
4
- data.tar.gz: cca91cd37e10bbcf3c69df6553a4b09cdfc606d8
3
+ metadata.gz: 0db00f7b95f26e2128972c34185a796559165d06
4
+ data.tar.gz: f1ae294b2bafceeb8a15429f19451cb52aadfe6a
5
5
  SHA512:
6
- metadata.gz: f7d39c9fa75f48bcf996e04050b188f1f3c5e47c1ee5f8edd85deb4fdd6193da986d88230b781f8095559db691e1f8ba7d73034107565a3eecc35bf259b509bf
7
- data.tar.gz: 3a0a64c7295ccc4fe744a568db56ad24dea62e52f325e9edf750cb729b08ec33f1fcd47bd4dce6d6ee1f53cd0a9639bbd7952dc175908b5b1dc76ee504c317ef
6
+ metadata.gz: 808bcea8000e9b72d236bed60f17fa66f44ed41b2d6eca83c64ced249f1f96346651312ca44dc0248cf90bc23ed7e8d092232846c6948f22c1f6847ff16079ac
7
+ data.tar.gz: 4826df4e710ce5022aa7231b77fc12e482832db3bcbef9e111c0bdcee7a5c172038be6087e867c77a249c5fad7c0f4918752f01144e3b4495c28b4039092b759
@@ -5,9 +5,12 @@ sudo: false
5
5
  rvm:
6
6
  - 2.1
7
7
  - 2.0
8
- - 1.9.3
8
+ - 1.9
9
9
 
10
10
  env:
11
+ - RAILS=master DB=sqlite3
12
+ - RAILS=master DB=mysql
13
+ - RAILS=master DB=postgres
11
14
  - RAILS=4-1-stable DB=sqlite3
12
15
  - RAILS=4-1-stable DB=mysql
13
16
  - RAILS=4-1-stable DB=postgres
@@ -0,0 +1,93 @@
1
+ # Change Log
2
+ This change log was started in August 2014. All notable changes to this project
3
+ henceforth should be documented here.
4
+
5
+ ## Version 1.4.0 - 2014-09-23
6
+ ### Added
7
+
8
+ * Add support for Rails 4.2.0! Let us know if you encounter any issues.
9
+
10
+ *Xiang Li*
11
+
12
+ * Add `not_true` and `not_false` predicates and update the "Basic Searching"
13
+ wiki. Fixes #123, #353.
14
+
15
+ *Pedro Chambino*
16
+
17
+ * Add `ro.yml` Romanian translation file.
18
+
19
+ *Andreas Philippi*
20
+
21
+ * Add new documentation in the README explaining how to group queries by `OR`
22
+ instead of the default `AND` using the `m: 'or'` combinator.
23
+
24
+ * Add new documentation in the README and in the source code comments
25
+ explaining in detail how to handle whitelisting/authorization of
26
+ attributes, associations, sorts and scopes.
27
+
28
+ * Add new documentation in the README explaining in more detail how to use
29
+ scopes for searching with Ransack.
30
+
31
+ * Begin a CHANGELOG.
32
+
33
+ *Jon Atack*
34
+
35
+ ### Fixed
36
+
37
+ * Fix singular/plural Active Record attribute translations.
38
+
39
+ *Andreas Philippi*
40
+
41
+ * Fix the params hash being modified by `Search.new` and the Ransack scope.
42
+
43
+ *Daniel Rikowski*
44
+
45
+ * Apply default scope conditions for association joins (fix for Rails 3).
46
+
47
+ Avoid selecting records from joins that would normally be filtered out
48
+ if they were selected from the base table. Only applies to Rails 3, as
49
+ this issue was fixed in Rails 4.
50
+
51
+ *Andrew Vit*
52
+
53
+ * Fix incoherent code examples in the README Associations section that
54
+ sometimes used `@q` and other times `@search`.
55
+
56
+ *Jon Atack*
57
+
58
+ ### Changed
59
+
60
+ * Refactor Ransack::Translate.
61
+
62
+ * Rewrite much of the Ransack README documentation, including the
63
+ Associations section code examples and the Authorizations section detailing
64
+ how to whitelist attributes, associations, sorts and scopes.
65
+
66
+ *Jon Atack*
67
+
68
+ ## Version 1.3.0 - 2014-08-23
69
+ ### Added
70
+
71
+ * Add search scopes by popular demand. Using `ransackable_scopes`, users can
72
+ define whitelists for allowed model scopes on a parent table. Not yet
73
+ implemented for associated models' scopes; scopes must be defined on the
74
+ parent table.
75
+
76
+ *Gleb Mazovetskiy*, *Andrew Vit*, *Sven Schwyn*
77
+
78
+ * Add `JOINS` merging.
79
+
80
+ * Add `OR` grouping on base search.
81
+
82
+ * Allow authorizing/whitelisting attributes, associations, sorts and scopes.
83
+
84
+ * Improve boolean predicates’ handling of `false` values.
85
+
86
+ * Allow configuring Ransack to raise on instead of ignore unknown search
87
+ conditions.
88
+
89
+ * Allow passing blank values to search without crashing.
90
+
91
+ * Add wildcard escaping compatibility for SQL Server databases.
92
+
93
+ * Add various I18n translations.
data/Gemfile CHANGED
@@ -3,7 +3,7 @@ gemspec
3
3
 
4
4
  gem 'rake'
5
5
 
6
- rails = ENV['RAILS'] || '4-1-stable'
6
+ rails = ENV['RAILS'] || 'master'
7
7
 
8
8
  gem 'polyamorous', '~> 1.1'
9
9
 
data/README.md CHANGED
@@ -9,7 +9,8 @@ Ransack is a rewrite of [MetaSearch]
9
9
  (https://github.com/activerecord-hackery/meta_search)
10
10
  created by [Ernie Miller](http://twitter.com/erniemiller)
11
11
  and maintained by [Ryan Bigg](http://twitter.com/ryanbigg),
12
- [Jon Atack](http://twitter.com/jonatack) and a great group of [contributors](https://github.com/activerecord-hackery/ransack/graphs/contributors).
12
+ [Jon Atack](http://twitter.com/jonatack) and a great group of [contributors]
13
+ (https://github.com/activerecord-hackery/ransack/graphs/contributors).
13
14
  While it supports many of the same features as MetaSearch, its underlying
14
15
  implementation differs greatly from MetaSearch,
15
16
  and backwards compatibility is not a design goal.
@@ -25,44 +26,25 @@ instead.
25
26
 
26
27
  ## Getting started
27
28
 
28
- Because ActiveRecord has been evolving quite a bit, your friendly Ransack is
29
- available in several flavors! Take your pick:
30
-
31
- In your Gemfile, for the last officially released gem compatible with Rails
32
- 3.x, 4.0 and 4.1 (for Rails 4.2, use the dedicated `rails-4.2` branch described
33
- below for now):
29
+ In your Gemfile, for the last officially released gem for Rails 3 and 4:
34
30
 
35
31
  ```ruby
36
32
  gem 'ransack'
37
33
  ```
38
34
 
39
- Or if you want to use the latest updates on the Ransack master branch:
35
+ Or if you want to use the latest updates (including Rails 4.2 compatibility):
40
36
 
41
37
  ```ruby
42
38
  gem 'ransack', github: 'activerecord-hackery/ransack'
43
39
  ```
44
40
 
45
- If you are using Rails 4.1, you may prefer the dedicated [Rails 4.1 branch](https://github.com/activerecord-hackery/ransack/tree/rails-4.1) which
46
- contains the latest updates, supports only 4.1, and is lighter and somewhat
47
- faster:
48
-
49
- ```ruby
50
- gem 'ransack', github: 'activerecord-hackery/ransack', branch: 'rails-4.1'
51
- ```
52
-
53
- Similarly, if you are using Rails 4.0, you may prefer the dedicated [Rails 4 branch](https://github.com/activerecord-hackery/ransack/tree/rails-4) for the
54
- same reasons:
55
-
56
- ```ruby
57
- gem 'ransack', github: 'activerecord-hackery/ransack', branch: 'rails-4'
58
- ```
59
-
60
- Last but definitely not least, an experimental [Rails 4.2 branch](https://github.com/activerecord-hackery/ransack/tree/rails-4.2) is
61
- available for those on the edge:
62
-
63
- ```ruby
64
- gem 'ransack', github: 'activerecord-hackery/ransack', branch: 'rails-4.2'
65
- ```
41
+ The other branches (`rails-4`, `rails-4.1`, and `rails-4.2`) were each used for
42
+ running Ransack with the latest upcoming version of Rails at the time. They are
43
+ lighter and somewhat faster-running because they do not have to support previous
44
+ versions of Rails and Active Record. However, once support for that version of
45
+ Rails is merged into Ransack master, the branches are no longer actively
46
+ maintained with the latest fixes and additions to Ransack -- unless the
47
+ community submits pull requests to maintain them, and you are welcome to do so!
66
48
 
67
49
  ## Usage
68
50
 
@@ -87,16 +69,18 @@ If you're coming from MetaSearch, things to note:
87
69
  3. Common ActiveRecord::Relation methods are no longer delegated by the
88
70
  search object. Instead, you will get your search results (an
89
71
  ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
90
- `Search#result`. If passed `distinct: true`, `result` will generate a `SELECT
91
- DISTINCT` to avoid returning duplicate rows, even if conditions on a join
92
- would otherwise result in some.
72
+ `Search#result`.
73
+
74
+ 4. If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
75
+ avoid returning duplicate rows, even if conditions on a join would otherwise
76
+ result in some.
93
77
 
94
78
  Please note that for many databases, a sort on an associated table's columns
95
- will result in invalid SQL with `distinct: true` -- in those cases, you're on
79
+ may result in invalid SQL with `distinct: true` -- in those cases, you're on
96
80
  your own, and will need to modify the result as needed to allow these queries
97
- to work. Thankfully, 9 times out of 10, sort against the search's base is
98
- sufficient, though, as that's generally what's being displayed on your
99
- results page.
81
+ to work. If `distinct: true` is causing you problems, another way to remove
82
+ duplicates is to call `#to_a.uniq` on your collection instead (see the next
83
+ section below).
100
84
 
101
85
  ####In your controller
102
86
 
@@ -113,6 +97,8 @@ this example, with preloading each Person's Articles and pagination):
113
97
  def index
114
98
  @q = Person.search(params[:q])
115
99
  @people = @q.result.includes(:articles).page(params[:page])
100
+ # or use `to_a.uniq` to remove duplicates (can also be done in the view):
101
+ @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
116
102
  end
117
103
  ```
118
104
 
@@ -126,10 +112,19 @@ which are defined in
126
112
 
127
113
  ```erb
128
114
  <%= search_form_for @q do |f| %>
115
+
116
+ # Search if the name field contains...
129
117
  <%= f.label :name_cont %>
130
118
  <%= f.search_field :name_cont %>
119
+
120
+ # Search if an associated articles.title starts with...
131
121
  <%= f.label :articles_title_start %>
132
122
  <%= f.search_field :articles_title_start %>
123
+
124
+ # Attributes may be chained. Search multiple attributes for one value...
125
+ <%= f.label :name_or_description_or_email_or_articles_title_cont %>
126
+ <%= f.search_field :name_or_description_or_email_or_articles_title_cont %>
127
+
133
128
  <%= f.submit %>
134
129
  <% end %>
135
130
  ```
@@ -210,9 +205,10 @@ Article.search(params[:q])
210
205
  Article.ransack(params[:q])
211
206
  ```
212
207
 
213
- ### has_many and belongs_to associations
208
+ ### Associations
214
209
 
215
- You can easily use Ransack to search in associated objects.
210
+ You can easily use Ransack to search for objects in `has_many` and `belongs_to`
211
+ associations.
216
212
 
217
213
  Given you have these associations ...
218
214
 
@@ -220,7 +216,7 @@ Given you have these associations ...
220
216
  class Employee < ActiveRecord::Base
221
217
  belongs_to :supervisor
222
218
 
223
- # has attribute last_name:string
219
+ # has attributes first_name:string and last_name:string
224
220
  end
225
221
 
226
222
  class Department < ActiveRecord::Base
@@ -242,8 +238,8 @@ end
242
238
  ```ruby
243
239
  class SupervisorsController < ApplicationController
244
240
  def index
245
- @search = Supervisor.search(params[:q])
246
- @supervisors = @search.result.includes(:department, :employees)
241
+ @q = Supervisor.search(params[:q])
242
+ @supervisors = @q.result.includes(:department, :employees)
247
243
  end
248
244
  end
249
245
  ```
@@ -251,15 +247,15 @@ end
251
247
  ... you might set up your form like this ...
252
248
 
253
249
  ```erb
254
- <%= search_form_for @search do |f| %>
250
+ <%= search_form_for @q do |f| %>
255
251
  <%= f.label :last_name_cont %>
256
252
  <%= f.search_field :last_name_cont %>
257
253
 
258
254
  <%= f.label :department_title_cont %>
259
255
  <%= f.search_field :department_title_cont %>
260
256
 
261
- <%= f.label :employees_last_name_cont %>
262
- <%= f.search_field :employees_last_name_cont %>
257
+ <%= f.label :employees_first_name_or_employees_last_name_cont %>
258
+ <%= f.search_field :employees_first_name_or_employees_last_name_cont %>
263
259
 
264
260
  <%= f.submit "search" %>
265
261
  <% end %>
@@ -280,50 +276,220 @@ information about `ransacker` methods can be found [here in the wiki]
280
276
  (https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers).
281
277
  Feel free to contribute working `ransacker` code examples to the wiki!
282
278
 
283
- ### Using SimpleForm
279
+ ### Authorization (whitelisting/blacklisting)
284
280
 
285
- If you want to combine form builders of ransack and SimpleForm, just set the
286
- RANSACK_FORM_BUILDER environment variable before Rails started, e.g. in
287
- ``config/application.rb`` before ``require 'rails/all'`` and of course use
288
- ``gem 'simple_form'`` in your ``Gemfile``:
281
+ By default, searching and sorting are authorized on any column of your model
282
+ and no class methods/scopes are whitelisted.
283
+
284
+ Ransack adds four methods to `ActiveRecord::Base` that you can redefine as
285
+ class methods in your models to apply selective authorization:
286
+ `ransackable_attributes`, `ransackable_associations`, `ransackable_scopes` and
287
+ `ransortable_attributes`.
288
+
289
+ Here is how these four methods are implemented in Ransack:
289
290
 
290
291
  ```ruby
291
- require File.expand_path('../boot', __FILE__)
292
+ def ransackable_attributes(auth_object = nil)
293
+ # By default returns all column names and any defined ransackers as an array
294
+ # of strings. For overriding with a whitelist array of strings.
295
+ column_names + _ransackers.keys
296
+ end
292
297
 
293
- ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
298
+ def ransackable_associations(auth_object = nil)
299
+ # By default returns the names of all associations as an array of strings.
300
+ # For overriding with a whitelist array of strings.
301
+ reflect_on_all_associations.map { |a| a.name.to_s }
302
+ end
294
303
 
295
- require 'rails/all'
304
+ def ransortable_attributes(auth_object = nil)
305
+ # By default returns the names of all attributes for sorting as an array of
306
+ # strings. For overriding with a whitelist array of strings.
307
+ ransackable_attributes(auth_object)
308
+ end
309
+
310
+ def ransackable_scopes(auth_object = nil)
311
+ # By default returns an empty array, i.e. no class methods/scopes
312
+ # are authorized. For overriding with a whitelist array of *symbols*.
313
+ []
314
+ end
296
315
  ```
297
316
 
298
- ### Authorization
317
+ Any values not returned from these methods will be ignored by Ransack, i.e.
318
+ they are not authorized.
299
319
 
300
- By default, Ransack exposes search on any model column, so make sure you
301
- sanitize your params and only pass the allowed keys. Alternately, you can
302
- define these class methods on your models to apply selective authorization
303
- based on a given auth object:
320
+ All four methods can receive a single optional parameter, `auth_object`. When
321
+ you call the search or ransack method on your model, you can provide a value
322
+ for an `auth_object` key in the options hash which can be used by your own
323
+ overridden methods.
304
324
 
305
- * `def self.ransackable_attributes(auth_object = nil)`
306
- * `def self.ransackable_associations(auth_object = nil)`
307
- * `def self.ransackable_scopes(auth_object = nil)`
308
- * `def self.ransortable_attributes(auth_object = nil)` (for sorting)
325
+ Here is an example that puts all this together, adapted from
326
+ [this blog post by Ernie Miller]
327
+ (http://erniemiller.org/2012/05/11/why-your-ruby-class-macros-might-suck-mine-did/).
328
+ In an `Article` model, add the following `ransackable_attributes` class method
329
+ (preferably private):
330
+ ```ruby
331
+ # article.rb
332
+ class Article < ActiveRecord::Base
333
+
334
+ private
335
+
336
+ def self.ransackable_attributes(auth_object = nil)
337
+ if auth_object == :admin
338
+ # whitelist all attributes for admin
339
+ super
340
+ else
341
+ # whitelist only the title and body attributes for other users
342
+ super & %w(title body)
343
+ end
344
+ end
345
+ end
346
+ ```
347
+ Here is example code for the `articles_controller`:
348
+ ```ruby
349
+ # articles_controller.rb
350
+ class ArticlesController < ApplicationController
309
351
 
310
- Any values not included in the arrays returned from these methods will be
311
- ignored. The auth object should be optional when building the search, and is
312
- ignored by default:
352
+ def index
353
+ @q = Article.search(params[:q], auth_object: set_ransack_auth_object)
354
+ @articles = @q.result
355
+ end
356
+
357
+ private
313
358
 
359
+ def set_ransack_auth_object
360
+ current_user.admin? ? :admin : nil
361
+ end
362
+ end
314
363
  ```
315
- Employee.search({ salary_gt: 100000 }, { auth_object: current_user })
316
- ```
364
+ Trying it out in `rails console`:
365
+ ```ruby
366
+ > Article
367
+ => Article(id: integer, person_id: integer, title: string, body: text)
368
+
369
+ > Article.ransackable_attributes
370
+ => ["title", "body"]
371
+
372
+ > Article.ransackable_attributes(:admin)
373
+ => ["id", "person_id", "title", "body"]
317
374
 
318
- ### Scopes
375
+ > Article.search(id_eq: 1).result.to_sql
376
+ => SELECT "articles".* FROM "articles" # Note that search param was ignored!
319
377
 
320
- Searching by scope requires defining a whitelist of `ransackable_scopes` on the
321
- model class. By default all class methods (e.g. scopes) are ignored. Scopes
322
- will be applied for matching `true` values, or for given values if the scope
323
- accepts a value:
378
+ > Article.search({ id_eq: 1 }, { auth_object: nil }).result.to_sql
379
+ => SELECT "articles".* FROM "articles" # Search param still ignored!
324
380
 
381
+ > Article.search({ id_eq: 1 }, { auth_object: :admin }).result.to_sql
382
+ => SELECT "articles".* FROM "articles" WHERE "articles"."id" = 1
325
383
  ```
384
+ That's it! Now you know how to whitelist/blacklist various elements in Ransack.
385
+
386
+ ### Using Scopes/Class Methods
387
+
388
+ Continuing on from the preceding section, searching by scopes requires defining
389
+ a whitelist of `ransackable_scopes` on the model class. The whitelist should be
390
+ an array of *symbols*. By default, all class methods (e.g. scopes) are ignored.
391
+ Scopes will be applied for matching `true` values, or for given values if the
392
+ scope accepts a value:
393
+
394
+ ```ruby
395
+ class Employee < ActiveRecord::Base
396
+ scope :active, ->(boolean = true) { (where active: boolean) }
397
+ scope :salary_gt, ->(amount) { where('salary > ?', amount) }
398
+
399
+ # Scopes are just syntactical sugar for class methods, which may also be used:
400
+
401
+ def self.hired_since(date)
402
+ where('start_date >= ?', date)
403
+ end
404
+
405
+ private
406
+
407
+ def self.ransackable_scopes(auth_object = nil)
408
+ if auth_object.try(:admin?)
409
+ # allow admin users access to all three methods
410
+ %i(active hired_since salary_gt)
411
+ else
412
+ # allow other users to search on active and hired_since only
413
+ %i(active hired_since)
414
+ end
415
+ end
416
+ end
417
+
326
418
  Employee.search({ active: true, hired_since: '2013-01-01' })
419
+
420
+ Employee.search({ salary_gt: 100_000 }, { auth_object: current_user })
421
+ ```
422
+
423
+ ### Grouping queries by OR instead of AND
424
+
425
+ The default `AND` grouping can be changed to `OR` by adding `m: 'or'` to the
426
+ query hash.
427
+
428
+ You can easily try it in your controller code by changing `params[:q]` in the
429
+ `index` action to `params[:q].try(:merge, m: 'or')` as follows:
430
+
431
+ ```ruby
432
+ def index
433
+ @q = Artist.search(params[:q].try(:merge, m: 'or'))
434
+ @artists = @q.result
435
+ end
436
+ ```
437
+ Normally, if you wanted users to be able to toggle between `AND` and `OR`
438
+ query grouping, you would probably set up your search form so that `m` was in
439
+ the URL params hash, but here we assigned `m` manually just to try it out
440
+ quickly.
441
+
442
+ Alternatively, trying it in the Rails console:
443
+
444
+ ```ruby
445
+ artists = Artist.search(name_cont: 'foo', style_cont: 'bar', m: 'or')
446
+ => Ransack::Search<class: Artist, base: Grouping <conditions: [
447
+ Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
448
+ Condition <attributes: ["style"], predicate: cont, values: ["bar"]>
449
+ ], combinator: or>>
450
+
451
+ artists.result.to_sql
452
+ => "SELECT \"artists\".* FROM \"artists\"
453
+ WHERE ((\"artists\".\"name\" ILIKE '%foo%'
454
+ OR \"artists\".\"style\" ILIKE '%bar%'))"
455
+ ```
456
+
457
+ The combinator becomes `or` instead of the default `and`, and the SQL query
458
+ becomes `WHERE...OR` instead of `WHERE...AND`.
459
+
460
+ This works with associations as well. Imagine an Artist model that has many
461
+ Memberships, and many Musicians through Memberships:
462
+
463
+ ```ruby
464
+ artists = Artist.search(name_cont: 'foo', musicians_email_cont: 'bar', m: 'or')
465
+ => Ransack::Search<class: Artist, base: Grouping <conditions: [
466
+ Condition <attributes: ["name"], predicate: cont, values: ["foo"]>,
467
+ Condition <attributes: ["musicians_email"], predicate: cont, values: ["bar"]>
468
+ ], combinator: or>>
469
+
470
+ artists.result.to_sql
471
+ => "SELECT \"artists\".* FROM \"artists\"
472
+ LEFT OUTER JOIN \"memberships\"
473
+ ON \"memberships\".\"artist_id\" = \"artists\".\"id\"
474
+ LEFT OUTER JOIN \"musicians\"
475
+ ON \"musicians\".\"id\" = \"memberships\".\"musician_id\"
476
+ WHERE ((\"artists\".\"name\" ILIKE '%foo%'
477
+ OR \"musicians\".\"email\" ILIKE '%bar%'))"
478
+ ```
479
+
480
+ ### Using SimpleForm
481
+
482
+ If you want to combine form builders of ransack and SimpleForm, just set the
483
+ RANSACK_FORM_BUILDER environment variable before Rails started, e.g. in
484
+ ``config/application.rb`` before ``require 'rails/all'`` and of course use
485
+ ``gem 'simple_form'`` in your ``Gemfile``:
486
+
487
+ ```ruby
488
+ require File.expand_path('../boot', __FILE__)
489
+
490
+ ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
491
+
492
+ require 'rails/all'
327
493
  ```
328
494
 
329
495
  ### I18n