ransack 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +40 -22
  3. data/CHANGELOG.md +176 -27
  4. data/CONTRIBUTING.md +30 -19
  5. data/Gemfile +8 -3
  6. data/README.md +131 -58
  7. data/Rakefile +5 -2
  8. data/lib/ransack.rb +10 -5
  9. data/lib/ransack/adapters.rb +43 -23
  10. data/lib/ransack/adapters/active_record.rb +2 -2
  11. data/lib/ransack/adapters/active_record/3.0/compat.rb +5 -5
  12. data/lib/ransack/adapters/active_record/3.0/context.rb +5 -3
  13. data/lib/ransack/adapters/active_record/3.1/context.rb +1 -4
  14. data/lib/ransack/adapters/active_record/base.rb +12 -1
  15. data/lib/ransack/adapters/active_record/context.rb +148 -55
  16. data/lib/ransack/adapters/active_record/ransack/constants.rb +53 -53
  17. data/lib/ransack/adapters/active_record/ransack/context.rb +3 -1
  18. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +20 -28
  19. data/lib/ransack/adapters/mongoid/base.rb +21 -6
  20. data/lib/ransack/adapters/mongoid/context.rb +9 -5
  21. data/lib/ransack/configuration.rb +24 -3
  22. data/lib/ransack/constants.rb +11 -22
  23. data/lib/ransack/context.rb +20 -13
  24. data/lib/ransack/helpers/form_builder.rb +5 -6
  25. data/lib/ransack/helpers/form_helper.rb +50 -69
  26. data/lib/ransack/locale/da.yml +70 -0
  27. data/lib/ransack/locale/id.yml +70 -0
  28. data/lib/ransack/locale/ja.yml +70 -0
  29. data/lib/ransack/locale/pt-BR.yml +70 -0
  30. data/lib/ransack/locale/{zh.yml → zh-CN.yml} +1 -1
  31. data/lib/ransack/locale/zh-TW.yml +70 -0
  32. data/lib/ransack/nodes.rb +1 -1
  33. data/lib/ransack/nodes/attribute.rb +4 -1
  34. data/lib/ransack/nodes/bindable.rb +18 -6
  35. data/lib/ransack/nodes/condition.rb +58 -28
  36. data/lib/ransack/nodes/grouping.rb +15 -4
  37. data/lib/ransack/nodes/sort.rb +9 -5
  38. data/lib/ransack/predicate.rb +6 -2
  39. data/lib/ransack/search.rb +6 -5
  40. data/lib/ransack/translate.rb +2 -2
  41. data/lib/ransack/version.rb +1 -1
  42. data/ransack.gemspec +4 -4
  43. data/spec/mongoid/adapters/mongoid/base_spec.rb +20 -1
  44. data/spec/mongoid/nodes/condition_spec.rb +15 -0
  45. data/spec/mongoid/support/mongoid.yml +5 -0
  46. data/spec/mongoid/support/schema.rb +4 -0
  47. data/spec/mongoid_spec_helper.rb +13 -9
  48. data/spec/ransack/adapters/active_record/base_spec.rb +249 -71
  49. data/spec/ransack/adapters/active_record/context_spec.rb +16 -18
  50. data/spec/ransack/helpers/form_builder_spec.rb +5 -2
  51. data/spec/ransack/helpers/form_helper_spec.rb +84 -14
  52. data/spec/ransack/nodes/condition_spec.rb +24 -0
  53. data/spec/ransack/nodes/grouping_spec.rb +56 -0
  54. data/spec/ransack/predicate_spec.rb +5 -5
  55. data/spec/ransack/search_spec.rb +79 -70
  56. data/spec/support/schema.rb +43 -29
  57. metadata +17 -12
data/README.md CHANGED
@@ -29,18 +29,18 @@ instead.
29
29
  If you're viewing this at
30
30
  [github.com/activerecord-hackery/ransack](https://github.com/activerecord-hackery/ransack),
31
31
  you're reading the documentation for the master branch with the latest features.
32
- [View documentation for the last release (1.7.0).](https://github.com/activerecord-hackery/ransack/tree/v1.7.0)
32
+ [View documentation for the last release (1.8.0).](https://github.com/activerecord-hackery/ransack/tree/v1.8.0)
33
33
 
34
34
  ## Getting started
35
35
 
36
- Ransack is compatible with Rails 3 and 4 on Ruby 1.9 and later (Ruby 2.2
37
- recommended). JRuby 9 ought to work as well (see
36
+ Ransack is compatible with Rails 3, 4 and 5 on Ruby 1.9 and later.
37
+ JRuby 9 ought to work as well (see
38
38
  [this](https://github.com/activerecord-hackery/polyamorous/issues/17)).
39
39
  If you are using Ruby 1.8 or an earlier JRuby and run into compatibility
40
40
  issues, you can use an earlier version of Ransack, say, up to 1.3.0.
41
41
 
42
42
  Ransack works out-of-the-box with Active Record and also features limited
43
- support for Mongoid 4.0 (without associations, further details
43
+ support for Mongoid 4 and 5 (without associations, further details
44
44
  [below](https://github.com/activerecord-hackery/ransack#mongoid)).
45
45
 
46
46
  In your Gemfile, for the last officially released gem:
@@ -49,17 +49,25 @@ In your Gemfile, for the last officially released gem:
49
49
  gem 'ransack'
50
50
  ```
51
51
 
52
- Or, if you would like to use the latest updates, use the `master` branch:
52
+ If you would like to use the latest updates (recommended), use the `master`
53
+ branch:
53
54
 
54
55
  ```ruby
55
56
  gem 'ransack', github: 'activerecord-hackery/ransack'
56
57
  ```
57
58
 
59
+ September 2015 update: If you are using Rails 5 (master) and need pagination
60
+ that works with Ransack, there is an
61
+ [updated version of the `will_paginate` gem here](https://github.com/jonatack/will_paginate).
62
+ It is also optimized for Ruby 2.2+. To use it, in your Gemfile:
63
+ `gem 'will_paginate', github: 'jonatack/will_paginate'`.
64
+
58
65
  ## Issues tracker
59
66
 
60
67
  * Before filing an issue, please read the [Contributing Guide](CONTRIBUTING.md).
61
68
  * File an issue if a bug is caused by Ransack, is new (has not already been reported), and _can be reproduced from the information you provide_.
62
69
  * Contributions are welcome, but please do not add "+1" comments to issues or pull requests :smiley:
70
+ * Please do not use the issue tracker for personal support requests. Stack Overflow is a better place for that where a wider community can help you!
63
71
 
64
72
  ## Usage
65
73
 
@@ -86,22 +94,6 @@ If you're coming from MetaSearch, things to note:
86
94
  ActiveRecord::Relation in the case of the ActiveRecord adapter) via a call to
87
95
  `Ransack#result`.
88
96
 
89
- 4. If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
90
- avoid returning duplicate rows, even if conditions on a join would otherwise
91
- result in some. It generates the same SQL as calling `uniq` on the relation.
92
-
93
- Please note that for many databases, a sort on an associated table's columns
94
- may result in invalid SQL with `distinct: true` -- in those cases, you're on
95
- your own, and will need to modify the result as needed to allow these queries
96
- to work.
97
-
98
- If `distinct: true` or `uniq` is causing invalid SQL, another way to remove
99
- duplicates is to call `to_a.uniq` on the collection at the end (see the next
100
- section below) -- with the caveat that the de-duping is taking place in Ruby
101
- instead of in SQL, which is potentially slower and uses more memory, and that
102
- it may display awkwardly with pagination if the number of results is greater
103
- than the page size.
104
-
105
97
  ####In your controller
106
98
 
107
99
  ```ruby
@@ -110,7 +102,7 @@ def index
110
102
  @people = @q.result(distinct: true)
111
103
  end
112
104
  ```
113
- or without `distinct:true`, for sorting on an associated table's columns (in
105
+ or without `distinct: true`, for sorting on an associated table's columns (in
114
106
  this example, with preloading each Person's Articles and pagination):
115
107
 
116
108
  ```ruby
@@ -177,6 +169,14 @@ column title or a default sort order:
177
169
  <%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>
178
170
  ```
179
171
 
172
+ You can use a block if the link markup is hard to fit into the label parameter:
173
+
174
+ ```erb
175
+ <%= sort_link(@q, :name) do %>
176
+ <strong>Player Name</strong>
177
+ <% end %>
178
+ ```
179
+
180
180
  With a polymorphic association, you may need to specify the name of the link
181
181
  explicitly to avoid an `uninitialized constant Model::Xxxable` error (see issue
182
182
  [#421](https://github.com/activerecord-hackery/ransack/issues/421)):
@@ -213,6 +213,15 @@ The sort link may be displayed without the order indicator arrow by passing
213
213
  <%= sort_link(@q, :name, hide_indicator: true) %>
214
214
  ```
215
215
 
216
+ Alternatively, all sort links may be displayed without the order indicator arrow
217
+ by adding this to an initializer file like `config/initializers/ransack.rb`:
218
+
219
+ ```ruby
220
+ Ransack.configure do |c|
221
+ c.hide_sort_order_indicators = true
222
+ end
223
+ ```
224
+
216
225
  ### Advanced Mode
217
226
 
218
227
  "Advanced" searches (ab)use Rails' nested attributes functionality in order to
@@ -264,7 +273,7 @@ Article.search(params[:q])
264
273
  ```
265
274
 
266
275
  Users have reported issues of `#search` name conflicts with other gems, so
267
- the `#search` method alias might be deprecated in the next major version of
276
+ the `#search` method alias will be deprecated in the next major version of
268
277
  Ransack (2.0). It's advisable to use the default `#ransack` instead.
269
278
 
270
279
  For now, if Ransack's `#search` method conflicts with the name of another
@@ -335,15 +344,40 @@ end
335
344
  ...
336
345
  <%= content_tag :table do %>
337
346
  <%= content_tag :th, sort_link(@q, :last_name) %>
338
- <%= content_tag :th, sort_link(@q, 'departments.title') %>
339
- <%= content_tag :th, sort_link(@q, 'employees.last_name') %>
347
+ <%= content_tag :th, sort_link(@q, :department_title) %>
348
+ <%= content_tag :th, sort_link(@q, :employees_last_name) %>
340
349
  <% end %>
341
350
  ```
342
351
 
343
- Please note that in a sort link, the association is expressed as an SQL string
344
- (`'employees.last_name'`) with a pluralized table name, instead of the symbol
345
- `:employee_last_name` syntax with a class#underscore table name used for
346
- Ransack objects elsewhere.
352
+ If you have trouble sorting on associations, try using an SQL string with the
353
+ pluralized table (`'departments.title'`,`'employees.last_name'`) instead of the
354
+ symbolized association (`:department_title)`, `:employees_last_name`).
355
+
356
+ ### Ransack Aliases
357
+
358
+ You can customize the attribute names for your Ransack searches by using a
359
+ `ransack_alias`. This is particularly useful for long attribute names that are
360
+ necessary when querying associations or multiple columns.
361
+
362
+ ```ruby
363
+ class Post < ActiveRecord::Base
364
+ belongs_to :author
365
+
366
+ # Abbreviate :author_first_name_or_author_last_name to :author
367
+ ransack_alias :author, :author_first_name_or_author_last_name
368
+ end
369
+ ```
370
+
371
+ Now, rather than using `:author_first_name_or_author_last_name_cont` in your
372
+ form, you can simply use `:author_cont`. This serves to produce more expressive
373
+ query parameters in your URLs.
374
+
375
+ ```erb
376
+ <%= search_form_for @q do |f| %>
377
+ <%= f.label :author_cont %>
378
+ <%= f.search_field :author_cont %>
379
+ <% end %>
380
+ ```
347
381
 
348
382
  ### Using Ransackers to add custom search functions via Arel
349
383
 
@@ -354,6 +388,58 @@ information about `ransacker` methods can be found [here in the wiki]
354
388
  (https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers).
355
389
  Feel free to contribute working `ransacker` code examples to the wiki!
356
390
 
391
+ ### Problem with DISTINCT selects
392
+
393
+ If passed `distinct: true`, `result` will generate a `SELECT DISTINCT` to
394
+ avoid returning duplicate rows, even if conditions on a join would otherwise
395
+ result in some. It generates the same SQL as calling `uniq` on the relation.
396
+
397
+ Please note that for many databases, a sort on an associated table's columns
398
+ may result in invalid SQL with `distinct: true` -- in those cases, you will
399
+ will need to modify the result as needed to allow these queries to work.
400
+
401
+ For example, you could call joins and includes on the result which has the
402
+ effect of adding those tables columns to the select statement, overcoming
403
+ the issue, like so:
404
+
405
+ ```ruby
406
+ def index
407
+ @q = Person.ransack(params[:q])
408
+ @people = @q.result(distinct: true)
409
+ .includes(:articles)
410
+ .joins(:articles)
411
+ .page(params[:page])
412
+ end
413
+ ```
414
+
415
+ If the above doesn't help, you can also use ActiveRecord's `select` query
416
+ to explicitly add the columns you need, which brute force's adding the
417
+ columns you need that your SQL engine is complaining about, you need to
418
+ make sure you give all of the columns you care about, for example:
419
+
420
+ ```ruby
421
+ def index
422
+ @q = Person.ransack(params[:q])
423
+ @people = @q.result(distinct: true)
424
+ .select('people.*, articles.name, articles.description')
425
+ .page(params[:page])
426
+ end
427
+ ```
428
+
429
+ A final way of last resort is to call `to_a.uniq` on the collection at the end
430
+ with the caveat that the de-duping is taking place in Ruby instead of in SQL,
431
+ which is potentially slower and uses more memory, and that it may display
432
+ awkwardly with pagination if the number of results is greater than the page size.
433
+
434
+ For example:
435
+
436
+ ```ruby
437
+ def index
438
+ @q = Person.ransack(params[:q])
439
+ @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
440
+ end
441
+ ```
442
+
357
443
  ### Authorization (whitelisting/blacklisting)
358
444
 
359
445
  By default, searching and sorting are authorized on any column of your model
@@ -416,9 +502,6 @@ In an `Article` model, add the following `ransackable_attributes` class method
416
502
 
417
503
  ```ruby
418
504
  class Article < ActiveRecord::Base
419
-
420
- private
421
-
422
505
  def self.ransackable_attributes(auth_object = nil)
423
506
  if auth_object == :admin
424
507
  # whitelist all attributes for admin
@@ -428,6 +511,8 @@ class Article < ActiveRecord::Base
428
511
  super & %w(title body)
429
512
  end
430
513
  end
514
+
515
+ private_class_method :ransackable_attributes
431
516
  end
432
517
  ```
433
518
 
@@ -435,7 +520,6 @@ Here is example code for the `articles_controller`:
435
520
 
436
521
  ```ruby
437
522
  class ArticlesController < ApplicationController
438
-
439
523
  def index
440
524
  @q = Article.ransack(params[:q], auth_object: set_ransack_auth_object)
441
525
  @articles = @q.result
@@ -483,7 +567,7 @@ scope accepts a value:
483
567
 
484
568
  ```ruby
485
569
  class Employee < ActiveRecord::Base
486
- scope :active, ->(boolean = true) { where(active: boolean) }
570
+ scope :activated, ->(boolean = true) { where(active: boolean) }
487
571
  scope :salary_gt, ->(amount) { where('salary > ?', amount) }
488
572
 
489
573
  # Scopes are just syntactical sugar for class methods, which may also be used:
@@ -492,29 +576,28 @@ class Employee < ActiveRecord::Base
492
576
  where('start_date >= ?', date)
493
577
  end
494
578
 
495
- private
496
-
497
579
  def self.ransackable_scopes(auth_object = nil)
498
580
  if auth_object.try(:admin?)
499
581
  # allow admin users access to all three methods
500
- %i(active hired_since salary_gt)
582
+ %i(activated hired_since salary_gt)
501
583
  else
502
- # allow other users to search on active and hired_since only
503
- %i(active hired_since)
584
+ # allow other users to search on `activated` and `hired_since` only
585
+ %i(activated hired_since)
504
586
  end
505
587
  end
588
+
589
+ private_class_method :ransackable_scopes
506
590
  end
507
591
 
508
- Employee.ransack({ active: true, hired_since: '2013-01-01' })
592
+ Employee.ransack({ activated: true, hired_since: '2013-01-01' })
509
593
 
510
594
  Employee.ransack({ salary_gt: 100_000 }, { auth_object: current_user })
511
595
  ```
512
596
 
513
- If the `true` value is being passed via url params or by some other mechanism
514
- that will convert it to a string (i.e. `active: 'true'` instead of
515
- `active: true`), the true value will *not* be passed to the scope. If you want
516
- to pass a `'true'` string to the scope, you should wrap it in an array (i.e.
517
- `active: ['true']`).
597
+ In Rails 3 and 4, if the `true` value is being passed via url params or some
598
+ other mechanism that will convert it to a string, the true value may not be
599
+ passed to the ransackable scope unless you wrap it in an array
600
+ (i.e. `activated: ['true']`). This is currently resolved in Rails 5 :smiley:
518
601
 
519
602
  Scopes are a recent addition to Ransack and currently have a few caveats:
520
603
  First, a scope involving child associations needs to be defined in the parent
@@ -662,16 +745,10 @@ called on a `ransack` search returns a `Mongoid::Criteria` object:
662
745
  @people = @q.result.active.order_by(updated_at: -1).limit(10)
663
746
  ```
664
747
 
665
- _NOTE: Ransack currently works with either Active Record or Mongoid, but not
748
+ NOTE: Ransack currently works with either Active Record or Mongoid, but not
666
749
  both in the same application. If both are present, Ransack will default to
667
- Active Record only. Here is the code containing the logic:_
668
-
669
- ```ruby
670
- @current_adapters ||= {
671
- :active_record => defined?(::ActiveRecord::Base),
672
- :mongoid => defined?(::Mongoid) && !defined?(::ActiveRecord::Base)
673
- }
674
- ```
750
+ Active Record only. The logic is contained in
751
+ `Ransack::Adapters#instantiate_object_mapper` should you need to override it.
675
752
 
676
753
  ## Semantic Versioning
677
754
 
@@ -699,7 +776,3 @@ directly related to bug reports, pull requests, or documentation improvements.
699
776
  * Spread the word on Twitter, Facebook, and elsewhere if Ransack's been useful
700
777
  to you. The more people who are using the project, the quicker we can find and
701
778
  fix bugs!
702
-
703
- ## Copyright
704
-
705
- Copyright &copy; 2011-2015 [Ernie Miller](http://twitter.com/erniemiller)
data/Rakefile CHANGED
@@ -5,7 +5,10 @@ Bundler::GemHelper.install_tasks
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec) do |rspec|
7
7
  ENV['SPEC'] = 'spec/ransack/**/*_spec.rb'
8
- rspec.rspec_opts = ['--backtrace']
8
+ # With Rails 3, using `--backtrace` raises 'invalid option' when testing.
9
+ # With Rails 4 and 5 it can be uncommented to see the backtrace:
10
+ #
11
+ # rspec.rspec_opts = ['--backtrace']
9
12
  end
10
13
 
11
14
  RSpec::Core::RakeTask.new(:mongoid) do |rspec|
@@ -14,7 +17,7 @@ RSpec::Core::RakeTask.new(:mongoid) do |rspec|
14
17
  end
15
18
 
16
19
  task :default do
17
- if ENV['DB'] =~ /mongodb/
20
+ if ENV['DB'] =~ /mongoid/
18
21
  Rake::Task["mongoid"].invoke
19
22
  else
20
23
  Rake::Task["spec"].invoke
@@ -1,13 +1,19 @@
1
1
  require 'active_support/core_ext'
2
-
3
2
  require 'ransack/configuration'
4
-
5
3
  require 'ransack/adapters'
6
- Ransack::Adapters.require_constants
4
+
5
+ Ransack::Adapters.object_mapper.require_constants
7
6
 
8
7
  module Ransack
9
8
  extend Configuration
10
9
  class UntraversableAssociationError < StandardError; end;
10
+
11
+ SUPPORTS_ATTRIBUTE_ALIAS =
12
+ begin
13
+ ActiveRecord::Base.respond_to?(:attribute_aliases)
14
+ rescue NameError
15
+ false
16
+ end
11
17
  end
12
18
 
13
19
  Ransack.configure do |config|
@@ -23,9 +29,8 @@ require 'ransack/search'
23
29
  require 'ransack/ransacker'
24
30
  require 'ransack/helpers'
25
31
  require 'action_controller'
26
-
27
32
  require 'ransack/translate'
28
33
 
29
- Ransack::Adapters.require_adapter
34
+ Ransack::Adapters.object_mapper.require_adapter
30
35
 
31
36
  ActionController::Base.helper Ransack::Helpers::FormHelper
@@ -1,42 +1,62 @@
1
1
  module Ransack
2
2
  module Adapters
3
3
 
4
- def self.current_adapters
5
- @current_adapters ||= {
6
- :active_record => defined?(::ActiveRecord::Base),
7
- :mongoid => defined?(::Mongoid) && !defined?(::ActiveRecord::Base)
8
- }
4
+ def self.object_mapper
5
+ @object_mapper ||= instantiate_object_mapper
9
6
  end
10
- def self.require_constants
11
- require 'ransack/adapters/mongoid/ransack/constants' if current_adapters[:mongoid]
12
- require 'ransack/adapters/active_record/ransack/constants' if current_adapters[:active_record]
7
+
8
+ def self.instantiate_object_mapper
9
+ if defined?(::ActiveRecord::Base)
10
+ ActiveRecordAdapter.new
11
+ elsif defined?(::Mongoid)
12
+ MongoidAdapter.new
13
+ end
13
14
  end
14
15
 
15
- def self.require_adapter
16
- if current_adapters[:active_record]
16
+ class ActiveRecordAdapter
17
+ def require_constants
18
+ require 'ransack/adapters/active_record/ransack/constants'
19
+ end
20
+
21
+ def require_adapter
17
22
  require 'ransack/adapters/active_record/ransack/translate'
18
23
  require 'ransack/adapters/active_record'
19
24
  end
20
25
 
21
- if current_adapters[:mongoid]
26
+ def require_context
27
+ require 'ransack/adapters/active_record/ransack/visitor'
28
+ end
29
+
30
+ def require_nodes
31
+ require 'ransack/adapters/active_record/ransack/nodes/condition'
32
+ end
33
+
34
+ def require_search
35
+ require 'ransack/adapters/active_record/ransack/context'
36
+ end
37
+ end
38
+
39
+ class MongoidAdapter
40
+ def require_constants
41
+ require 'ransack/adapters/mongoid/ransack/constants'
42
+ end
43
+
44
+ def require_adapter
22
45
  require 'ransack/adapters/mongoid/ransack/translate'
23
46
  require 'ransack/adapters/mongoid'
24
47
  end
25
- end
26
48
 
27
- def self.require_context
28
- require 'ransack/adapters/active_record/ransack/visitor' if current_adapters[:active_record]
29
- require 'ransack/adapters/mongoid/ransack/visitor' if current_adapters[:mongoid]
30
- end
49
+ def require_context
50
+ require 'ransack/adapters/mongoid/ransack/visitor'
51
+ end
31
52
 
32
- def self.require_nodes
33
- require 'ransack/adapters/active_record/ransack/nodes/condition' if current_adapters[:active_record]
34
- require 'ransack/adapters/mongoid/ransack/nodes/condition' if current_adapters[:mongoid]
35
- end
53
+ def require_nodes
54
+ require 'ransack/adapters/mongoid/ransack/nodes/condition'
55
+ end
36
56
 
37
- def self.require_search
38
- require 'ransack/adapters/active_record/ransack/context' if current_adapters[:active_record]
39
- require 'ransack/adapters/mongoid/ransack/context' if current_adapters[:mongoid]
57
+ def require_search
58
+ require 'ransack/adapters/mongoid/ransack/context'
59
+ end
40
60
  end
41
61
  end
42
62
  end