ransack 1.3.0 → 1.4.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.
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