ransack 2.3.2 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/codeql.yml +72 -0
  5. data/.github/workflows/cronjob.yml +99 -0
  6. data/.github/workflows/deploy.yml +35 -0
  7. data/.github/workflows/rubocop.yml +20 -0
  8. data/.github/workflows/test-deploy.yml +29 -0
  9. data/.github/workflows/test.yml +131 -0
  10. data/.nojekyll +0 -0
  11. data/.rubocop.yml +50 -0
  12. data/CHANGELOG.md +251 -1
  13. data/CONTRIBUTING.md +51 -29
  14. data/Gemfile +12 -10
  15. data/README.md +45 -907
  16. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  17. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +75 -0
  18. data/docs/.gitignore +19 -0
  19. data/docs/.nojekyll +0 -0
  20. data/docs/babel.config.js +3 -0
  21. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  22. data/docs/docs/getting-started/_category_.json +4 -0
  23. data/docs/docs/getting-started/advanced-mode.md +46 -0
  24. data/docs/docs/getting-started/configuration.md +47 -0
  25. data/docs/docs/getting-started/search-matches.md +67 -0
  26. data/docs/docs/getting-started/simple-mode.md +288 -0
  27. data/docs/docs/getting-started/sorting.md +71 -0
  28. data/docs/docs/getting-started/using-predicates.md +282 -0
  29. data/docs/docs/going-further/_category_.json +4 -0
  30. data/docs/docs/going-further/acts-as-taggable-on.md +114 -0
  31. data/docs/docs/going-further/associations.md +70 -0
  32. data/docs/docs/going-further/custom-predicates.md +52 -0
  33. data/docs/docs/going-further/documentation.md +43 -0
  34. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  35. data/docs/docs/going-further/external-guides.md +57 -0
  36. data/docs/docs/going-further/form-customisation.md +63 -0
  37. data/docs/docs/going-further/i18n.md +53 -0
  38. data/docs/docs/going-further/img/create_release.png +0 -0
  39. data/docs/docs/going-further/merging-searches.md +41 -0
  40. data/docs/docs/going-further/other-notes.md +428 -0
  41. data/docs/docs/going-further/polymorphic-search.md +46 -0
  42. data/docs/docs/going-further/ransackers.md +331 -0
  43. data/docs/docs/going-further/release_process.md +36 -0
  44. data/docs/docs/going-further/saving-queries.md +82 -0
  45. data/docs/docs/going-further/searching-postgres.md +57 -0
  46. data/docs/docs/going-further/wiki-contributors.md +82 -0
  47. data/docs/docs/intro.md +99 -0
  48. data/docs/docusaurus.config.js +120 -0
  49. data/docs/package.json +42 -0
  50. data/docs/sidebars.js +31 -0
  51. data/docs/src/components/HomepageFeatures/index.js +64 -0
  52. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  53. data/docs/src/css/custom.css +39 -0
  54. data/docs/src/pages/index.module.css +23 -0
  55. data/docs/src/pages/markdown-page.md +7 -0
  56. data/docs/static/.nojekyll +0 -0
  57. data/docs/static/img/docusaurus.png +0 -0
  58. data/docs/static/img/favicon.ico +0 -0
  59. data/docs/static/img/logo.svg +1 -0
  60. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  61. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  62. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  63. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  64. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  65. data/docs/yarn.lock +8879 -0
  66. data/lib/polyamorous/activerecord/join_association.rb +70 -0
  67. data/{polyamorous/lib/polyamorous/activerecord_6.0_ruby_2 → lib/polyamorous/activerecord}/join_dependency.rb +33 -12
  68. data/lib/polyamorous/activerecord/reflection.rb +11 -0
  69. data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +3 -4
  70. data/lib/ransack/adapters/active_record/base.rb +83 -10
  71. data/lib/ransack/adapters/active_record/context.rb +56 -44
  72. data/lib/ransack/configuration.rb +53 -10
  73. data/lib/ransack/constants.rb +126 -4
  74. data/lib/ransack/context.rb +34 -5
  75. data/lib/ransack/helpers/form_builder.rb +6 -6
  76. data/lib/ransack/helpers/form_helper.rb +14 -5
  77. data/lib/ransack/helpers.rb +1 -1
  78. data/lib/ransack/locale/sv.yml +70 -0
  79. data/lib/ransack/nodes/attribute.rb +3 -3
  80. data/lib/ransack/nodes/condition.rb +80 -9
  81. data/lib/ransack/nodes/grouping.rb +4 -4
  82. data/lib/ransack/nodes/node.rb +1 -1
  83. data/lib/ransack/nodes/sort.rb +3 -3
  84. data/lib/ransack/nodes/value.rb +3 -3
  85. data/lib/ransack/predicate.rb +1 -1
  86. data/lib/ransack/ransacker.rb +1 -1
  87. data/lib/ransack/search.rb +15 -7
  88. data/lib/ransack/translate.rb +6 -6
  89. data/lib/ransack/version.rb +1 -1
  90. data/lib/ransack/visitor.rb +38 -2
  91. data/lib/ransack.rb +5 -8
  92. data/ransack.gemspec +9 -15
  93. data/spec/blueprints/articles.rb +1 -1
  94. data/spec/blueprints/comments.rb +1 -1
  95. data/spec/blueprints/notes.rb +1 -1
  96. data/spec/blueprints/tags.rb +1 -1
  97. data/spec/console.rb +5 -5
  98. data/spec/helpers/polyamorous_helper.rb +2 -8
  99. data/spec/helpers/ransack_helper.rb +1 -1
  100. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  101. data/spec/{ransack → polyamorous}/join_association_spec.rb +3 -1
  102. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -16
  103. data/spec/ransack/adapters/active_record/base_spec.rb +125 -16
  104. data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
  105. data/spec/ransack/configuration_spec.rb +33 -9
  106. data/spec/ransack/helpers/form_builder_spec.rb +8 -8
  107. data/spec/ransack/helpers/form_helper_spec.rb +109 -20
  108. data/spec/ransack/nodes/condition_spec.rb +37 -0
  109. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  110. data/spec/ransack/nodes/value_spec.rb +115 -0
  111. data/spec/ransack/predicate_spec.rb +37 -2
  112. data/spec/ransack/search_spec.rb +238 -30
  113. data/spec/ransack/translate_spec.rb +1 -1
  114. data/spec/spec_helper.rb +7 -5
  115. data/spec/support/schema.rb +108 -11
  116. metadata +98 -62
  117. data/.travis.yml +0 -47
  118. data/lib/ransack/adapters/active_record/ransack/constants.rb +0 -128
  119. data/lib/ransack/adapters/active_record/ransack/context.rb +0 -55
  120. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +0 -61
  121. data/lib/ransack/adapters/active_record/ransack/translate.rb +0 -8
  122. data/lib/ransack/adapters/active_record/ransack/visitor.rb +0 -47
  123. data/lib/ransack/adapters.rb +0 -64
  124. data/lib/ransack/nodes.rb +0 -8
  125. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -20
  126. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
  127. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -12
  128. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -2
  129. data/polyamorous/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -2
  130. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
  131. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
  132. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -2
  133. data/polyamorous/lib/polyamorous/version.rb +0 -3
  134. data/polyamorous/polyamorous.gemspec +0 -27
  135. /data/{logo → docs/static/logo}/ransack-h.png +0 -0
  136. /data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  137. /data/{logo → docs/static/logo}/ransack-v.png +0 -0
  138. /data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  139. /data/{logo → docs/static/logo}/ransack.png +0 -0
  140. /data/{logo → docs/static/logo}/ransack.svg +0 -0
  141. /data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
  142. /data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
  143. /data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
  144. /data/lib/ransack/{adapters/active_record.rb → active_record.rb} +0 -0
  145. /data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
@@ -36,7 +36,7 @@ module Ransack
36
36
  'lt'.freeze, 'lteq'.freeze,
37
37
  'gt'.freeze, 'gteq'.freeze,
38
38
  'in'.freeze, 'not_in'.freeze
39
- ].freeze
39
+ ].freeze
40
40
  A_S_I = ['a'.freeze, 's'.freeze, 'i'.freeze].freeze
41
41
 
42
42
  EQ = 'eq'.freeze
@@ -45,10 +45,132 @@ module Ransack
45
45
  NOT_EQ_ALL = 'not_eq_all'.freeze
46
46
  CONT = 'cont'.freeze
47
47
 
48
- RAILS_6_0 = '6.0.0'.freeze
49
-
50
48
  RANSACK_SLASH_SEARCHES = 'ransack/searches'.freeze
51
49
  RANSACK_SLASH_SEARCHES_SLASH_SEARCH = 'ransack/searches/search'.freeze
50
+
51
+ DISTINCT = 'DISTINCT '.freeze
52
+
53
+ DERIVED_PREDICATES = [
54
+ [CONT, {
55
+ arel_predicate: 'matches'.freeze,
56
+ formatter: proc { |v| "%#{escape_wildcards(v)}%" }
57
+ }
58
+ ],
59
+ ['not_cont'.freeze, {
60
+ arel_predicate: 'does_not_match'.freeze,
61
+ formatter: proc { |v| "%#{escape_wildcards(v)}%" }
62
+ }
63
+ ],
64
+ ['i_cont'.freeze, {
65
+ arel_predicate: 'matches'.freeze,
66
+ formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
67
+ case_insensitive: true
68
+ }
69
+ ],
70
+ ['not_i_cont'.freeze, {
71
+ arel_predicate: 'does_not_match'.freeze,
72
+ formatter: proc { |v| "%#{escape_wildcards(v.downcase)}%" },
73
+ case_insensitive: true
74
+ }
75
+ ],
76
+ ['start'.freeze, {
77
+ arel_predicate: 'matches'.freeze,
78
+ formatter: proc { |v| "#{escape_wildcards(v)}%" }
79
+ }
80
+ ],
81
+ ['not_start'.freeze, {
82
+ arel_predicate: 'does_not_match'.freeze,
83
+ formatter: proc { |v| "#{escape_wildcards(v)}%" }
84
+ }
85
+ ],
86
+ ['end'.freeze, {
87
+ arel_predicate: 'matches'.freeze,
88
+ formatter: proc { |v| "%#{escape_wildcards(v)}" }
89
+ }
90
+ ],
91
+ ['not_end'.freeze, {
92
+ arel_predicate: 'does_not_match'.freeze,
93
+ formatter: proc { |v| "%#{escape_wildcards(v)}" }
94
+ }
95
+ ],
96
+ ['true'.freeze, {
97
+ arel_predicate: proc { |v| v ? EQ : NOT_EQ },
98
+ compounds: false,
99
+ type: :boolean,
100
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
101
+ formatter: proc { |v| true }
102
+ }
103
+ ],
104
+ ['not_true'.freeze, {
105
+ arel_predicate: proc { |v| v ? NOT_EQ : EQ },
106
+ compounds: false,
107
+ type: :boolean,
108
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
109
+ formatter: proc { |v| true }
110
+ }
111
+ ],
112
+ ['false'.freeze, {
113
+ arel_predicate: proc { |v| v ? EQ : NOT_EQ },
114
+ compounds: false,
115
+ type: :boolean,
116
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
117
+ formatter: proc { |v| false }
118
+ }
119
+ ],
120
+ ['not_false'.freeze, {
121
+ arel_predicate: proc { |v| v ? NOT_EQ : EQ },
122
+ compounds: false,
123
+ type: :boolean,
124
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
125
+ formatter: proc { |v| false }
126
+ }
127
+ ],
128
+ ['present'.freeze, {
129
+ arel_predicate: proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
130
+ compounds: false,
131
+ type: :boolean,
132
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
133
+ formatter: proc { |v| [nil, ''.freeze].freeze }
134
+ }
135
+ ],
136
+ ['blank'.freeze, {
137
+ arel_predicate: proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
138
+ compounds: false,
139
+ type: :boolean,
140
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
141
+ formatter: proc { |v| [nil, ''.freeze].freeze }
142
+ }
143
+ ],
144
+ ['null'.freeze, {
145
+ arel_predicate: proc { |v| v ? EQ : NOT_EQ },
146
+ compounds: false,
147
+ type: :boolean,
148
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
149
+ formatter: proc { |v| nil }
150
+ }
151
+ ],
152
+ ['not_null'.freeze, {
153
+ arel_predicate: proc { |v| v ? NOT_EQ : EQ },
154
+ compounds: false,
155
+ type: :boolean,
156
+ validator: proc { |v| BOOLEAN_VALUES.include?(v) },
157
+ formatter: proc { |v| nil } }
158
+ ]
159
+ ].freeze
160
+
161
+ module_function
162
+ # replace % \ to \% \\
163
+ def escape_wildcards(unescaped)
164
+ case ActiveRecord::Base.connection.adapter_name
165
+ when "Mysql2".freeze
166
+ # Necessary for MySQL
167
+ unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
168
+ when "PostgreSQL".freeze
169
+ # Necessary for PostgreSQL
170
+ unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
171
+ else
172
+ unescaped
173
+ end
174
+ end
52
175
  end
53
176
  end
54
-
@@ -1,19 +1,24 @@
1
1
  require 'ransack/visitor'
2
- Ransack::Adapters.object_mapper.require_context
3
2
 
4
3
  module Ransack
5
4
  class Context
6
5
  attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
7
6
  attr_accessor :auth_object, :search_key
7
+ attr_reader :arel_visitor
8
8
 
9
9
  class << self
10
10
 
11
11
  def for_class(klass, options = {})
12
- raise "not implemented"
12
+ if klass < ActiveRecord::Base
13
+ Adapters::ActiveRecord::Context.new(klass, options)
14
+ end
13
15
  end
14
16
 
15
17
  def for_object(object, options = {})
16
- raise "not implemented"
18
+ case object
19
+ when ActiveRecord::Relation
20
+ Adapters::ActiveRecord::Context.new(object.klass, options)
21
+ end
17
22
  end
18
23
 
19
24
  def for(object, options = {})
@@ -30,11 +35,35 @@ module Ransack
30
35
  end # << self
31
36
 
32
37
  def initialize(object, options = {})
33
- raise "not implemented"
38
+ @object = relation_for(object)
39
+ @klass = @object.klass
40
+ @join_dependency = join_dependency(@object)
41
+ @join_type = options[:join_type] || Polyamorous::OuterJoin
42
+ @search_key = options[:search_key] || Ransack.options[:search_key]
43
+ @associations_pot = {}
44
+ @tables_pot = {}
45
+ @lock_associations = []
46
+
47
+ @base = @join_dependency.instance_variable_get(:@join_root)
48
+ end
49
+
50
+ def bind_pair_for(key)
51
+ @bind_pairs ||= {}
52
+
53
+ @bind_pairs[key] ||= begin
54
+ parent, attr_name = get_parent_and_attribute_name(key.to_s)
55
+ [parent, attr_name] if parent && attr_name
56
+ end
34
57
  end
35
58
 
36
59
  def klassify(obj)
37
- raise "not implemented"
60
+ if Class === obj && ::ActiveRecord::Base > obj
61
+ obj
62
+ elsif obj.respond_to? :klass
63
+ obj.klass
64
+ else
65
+ raise ArgumentError, "Don't know how to klassify #{obj.inspect}"
66
+ end
38
67
  end
39
68
 
40
69
  # Convert a string representing a chain of associations and an attribute
@@ -33,7 +33,7 @@ module Ransack
33
33
  text = args.first
34
34
  i18n = options[:i18n] || {}
35
35
  text ||= object.translate(
36
- method, i18n.reverse_merge(:include_associations => true)
36
+ method, i18n.reverse_merge(include_associations: true)
37
37
  ) if object.respond_to? :translate
38
38
  super(method, text, options, &block)
39
39
  end
@@ -45,9 +45,9 @@ module Ransack
45
45
  end
46
46
 
47
47
  def attribute_select(options = nil, html_options = nil, action = nil)
48
- options = options || {}
49
- html_options = html_options || {}
50
- action = action || Constants::SEARCH
48
+ options ||= {}
49
+ html_options ||= {}
50
+ action ||= Constants::SEARCH
51
51
  default = options.delete(:default)
52
52
  raise ArgumentError, formbuilder_error_message(
53
53
  "#{action}_select") unless object.respond_to?(:context)
@@ -240,7 +240,7 @@ module Ransack
240
240
  def get_attribute_element(action, base)
241
241
  begin
242
242
  [
243
- Translate.association(base, :context => object.context),
243
+ Translate.association(base, context: object.context),
244
244
  collection_for_base(action, base)
245
245
  ]
246
246
  rescue UntraversableAssociationError
@@ -253,7 +253,7 @@ module Ransack
253
253
  [
254
254
  attr_from_base_and_column(base, c),
255
255
  Translate.attribute(
256
- attr_from_base_and_column(base, c), :context => object.context
256
+ attr_from_base_and_column(base, c), context: object.context
257
257
  )
258
258
  ]
259
259
  end
@@ -129,13 +129,21 @@ module Ransack
129
129
  end
130
130
 
131
131
  def url_options
132
- @params.merge(
133
- @options.merge(
132
+ @params.except(:host).merge(
133
+ @options.except(:class, :data, :host).merge(
134
134
  @search.context.search_key => search_and_sort_params))
135
135
  end
136
136
 
137
137
  def html_options(args)
138
- html_options = extract_options_and_mutate_args!(args)
138
+ if args.empty?
139
+ html_options = @options
140
+ else
141
+ deprecation_message = "Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one."
142
+ caller_location = caller_locations(2, 2).first
143
+ warn "#{deprecation_message} (called at #{caller_location.path}:#{caller_location.lineno})"
144
+ html_options = extract_options_and_mutate_args!(args)
145
+ end
146
+
139
147
  html_options.merge(
140
148
  class: [['sort_link'.freeze, @current_dir], html_options[:class]]
141
149
  .compact.join(' '.freeze)
@@ -145,7 +153,7 @@ module Ransack
145
153
  private
146
154
 
147
155
  def parameters_hash(params)
148
- if ::ActiveRecord::VERSION::MAJOR >= 5 && params.respond_to?(:to_unsafe_h)
156
+ if params.respond_to?(:to_unsafe_h)
149
157
  params.to_unsafe_h
150
158
  else
151
159
  params
@@ -172,7 +180,8 @@ module Ransack
172
180
  end
173
181
 
174
182
  def search_params
175
- @params[@search.context.search_key].presence || {}
183
+ query_params = @params[@search.context.search_key]
184
+ query_params.is_a?(Hash) ? query_params : {}
176
185
  end
177
186
 
178
187
  def sort_params
@@ -1,2 +1,2 @@
1
1
  require 'ransack/helpers/form_builder'
2
- require 'ransack/helpers/form_helper'
2
+ require 'ransack/helpers/form_helper'
@@ -0,0 +1,70 @@
1
+ sv:
2
+ ransack:
3
+ search: "sök"
4
+ predicate: "predikat"
5
+ and: "och"
6
+ or: "eller"
7
+ any: "vilken som"
8
+ all: "alla"
9
+ combinator: "kombinator"
10
+ attribute: "attribut"
11
+ value: "värde"
12
+ condition: "villkor"
13
+ sort: "sortera"
14
+ asc: "stigande"
15
+ desc: "fallande"
16
+ predicates:
17
+ eq: "lika med"
18
+ eq_any: "lika med vilket som"
19
+ eq_all: "lika med alla"
20
+ not_eq: "inte lika med"
21
+ not_eq_any: "inte lika med någon"
22
+ not_eq_all: "inte lika med alla"
23
+ matches: "matchar"
24
+ matches_any: "matchar någon"
25
+ matches_all: "matchar alla"
26
+ does_not_match: "matchar inte"
27
+ does_not_match_any: "matchar inte någon"
28
+ does_not_match_all: "matchar inte alla"
29
+ lt: "mindre än"
30
+ lt_any: "mindre än någon"
31
+ lt_all: "mindre än alla"
32
+ lteq: "mindre än eller lika med"
33
+ lteq_any: "mindre än eller lika med någon"
34
+ lteq_all: "mindre än eller lika med alla"
35
+ gt: "större än"
36
+ gt_any: "större än någon"
37
+ gt_all: "större än alla"
38
+ gteq: "större än eller lika med"
39
+ gteq_any: "större än eller lika med någon"
40
+ gteq_all: "större än eller lika med alla"
41
+ in: "i"
42
+ in_any: "i någon"
43
+ in_all: "i alla"
44
+ not_in: "inte i"
45
+ not_in_any: "inte i någon"
46
+ not_in_all: "inte i alla"
47
+ cont: "innehåller"
48
+ cont_any: "innehåller någon"
49
+ cont_all: "innehåller alla"
50
+ not_cont: "innehåller inte"
51
+ not_cont_any: "innehåller inte någon"
52
+ not_cont_all: "innehåller inte alla"
53
+ start: "börjar med"
54
+ start_any: "börjar med någon"
55
+ start_all: "börjar med alla"
56
+ not_start: "börjar inte med"
57
+ not_start_any: "börjar inte med någon"
58
+ not_start_all: "börjar inte med alla"
59
+ end: "slutar med"
60
+ end_any: "slutar med någon"
61
+ end_all: "slutar med alla"
62
+ not_end: "slutar inte med"
63
+ not_end_any: "slutar inte med någon"
64
+ not_end_all: "slutar inte med alla"
65
+ 'true': "är sant"
66
+ 'false': "är falskt"
67
+ present: "existerar"
68
+ blank: "är tom"
69
+ 'null': "är null"
70
+ not_null: "är inte null"
@@ -5,8 +5,8 @@ module Ransack
5
5
 
6
6
  attr_reader :name, :ransacker_args
7
7
 
8
- delegate :blank?, :present?, :to => :name
9
- delegate :engine, :to => :context
8
+ delegate :blank?, :present?, to: :name
9
+ delegate :engine, to: :context
10
10
 
11
11
  def initialize(context, name = nil, ransacker_args = [])
12
12
  super(context)
@@ -30,7 +30,7 @@ module Ransack
30
30
 
31
31
  def type
32
32
  if ransacker
33
- return ransacker.type
33
+ ransacker.type
34
34
  else
35
35
  context.type_for(self)
36
36
  end
@@ -2,8 +2,8 @@ module Ransack
2
2
  module Nodes
3
3
  class Condition < Node
4
4
  i18n_word :attribute, :predicate, :combinator, :value
5
- i18n_alias :a => :attribute, :p => :predicate,
6
- :m => :combinator, :v => :value
5
+ i18n_alias a: :attribute, p: :predicate,
6
+ m: :combinator, v: :value
7
7
 
8
8
  attr_accessor :predicate
9
9
 
@@ -15,10 +15,10 @@ module Ransack
15
15
  if attributes.size > 0 && predicate
16
16
  condition = self.new(context)
17
17
  condition.build(
18
- :a => attributes,
19
- :p => predicate.name,
20
- :m => combinator,
21
- :v => predicate.wants_array ? Array(values) : [values]
18
+ a: attributes,
19
+ p: predicate.name,
20
+ m: combinator,
21
+ v: predicate.wants_array ? Array(values) : [values]
22
22
  )
23
23
  # TODO: Figure out what to do with multiple types of attributes,
24
24
  # if anything. Tempted to go with "garbage in, garbage out" here.
@@ -127,7 +127,6 @@ module Ransack
127
127
  alias :m= :combinator=
128
128
  alias :m :combinator
129
129
 
130
-
131
130
  # == build_attribute
132
131
  #
133
132
  # This method was originally called from Nodes::Grouping#new_condition
@@ -263,7 +262,6 @@ module Ransack
263
262
  attr.attr
264
263
  end
265
264
 
266
-
267
265
  def default_type
268
266
  predicate.type || (attributes.first && attributes.first.type)
269
267
  end
@@ -285,12 +283,85 @@ module Ransack
285
283
  predicate.negative?
286
284
  end
287
285
 
286
+ def arel_predicate
287
+ predicate = attributes.map { |attribute|
288
+ association = attribute.parent
289
+ if negative? && attribute.associated_collection?
290
+ query = context.build_correlated_subquery(association)
291
+ context.remove_association(association)
292
+ if self.predicate_name == 'not_null' && self.value
293
+ query.where(format_predicate(attribute))
294
+ Arel::Nodes::In.new(context.primary_key, Arel.sql(query.to_sql))
295
+ else
296
+ query.where(format_predicate(attribute).not)
297
+ Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
298
+ end
299
+ else
300
+ format_predicate(attribute)
301
+ end
302
+ }.reduce(combinator_method)
303
+
304
+ if replace_right_node?(predicate)
305
+ # Replace right node object to plain integer value in order to avoid
306
+ # ActiveModel::RangeError from Arel::Node::Casted.
307
+ # The error can be ignored here because RDBMSs accept large numbers
308
+ # in condition clauses.
309
+ plain_value = predicate.right.value
310
+ predicate.right = plain_value
311
+ end
312
+
313
+ predicate
314
+ end
315
+
288
316
  private
289
317
 
318
+ def combinator_method
319
+ combinator === Constants::OR ? :or : :and
320
+ end
321
+
322
+ def format_predicate(attribute)
323
+ arel_pred = arel_predicate_for_attribute(attribute)
324
+ arel_values = formatted_values_for_attribute(attribute)
325
+ predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
326
+
327
+ if in_predicate?(predicate)
328
+ predicate.right = predicate.right.map do |pr|
329
+ casted_array?(pr) ? format_values_for(pr) : pr
330
+ end
331
+ end
332
+
333
+ predicate
334
+ end
335
+
336
+ def in_predicate?(predicate)
337
+ return unless defined?(Arel::Nodes::Casted)
338
+ predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
339
+ end
340
+
341
+ def casted_array?(predicate)
342
+ predicate.value.is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
343
+ end
344
+
345
+ def format_values_for(predicate)
346
+ predicate.value.map do |val|
347
+ val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
348
+ end
349
+ end
350
+
351
+ def replace_right_node?(predicate)
352
+ return false unless predicate.is_a?(Arel::Nodes::Binary)
353
+
354
+ arel_node = predicate.right
355
+ return false unless arel_node.is_a?(Arel::Nodes::Casted)
356
+
357
+ relation, name = arel_node.attribute.values
358
+ attribute_type = relation.type_for_attribute(name).type
359
+ attribute_type == :integer && arel_node.value.is_a?(Integer)
360
+ end
361
+
290
362
  def valid_combinator?
291
363
  attributes.size < 2 || Constants::AND_OR.include?(combinator)
292
364
  end
293
-
294
365
  end
295
366
  end
296
367
  end
@@ -7,9 +7,9 @@ module Ransack
7
7
  alias :m= :combinator=
8
8
 
9
9
  i18n_word :condition, :and, :or
10
- i18n_alias :c => :condition, :n => :and, :o => :or
10
+ i18n_alias c: :condition, n: :and, o: :or
11
11
 
12
- delegate :each, :to => :values
12
+ delegate :each, to: :values
13
13
 
14
14
  def initialize(context, combinator = nil)
15
15
  super(context)
@@ -22,7 +22,7 @@ module Ransack
22
22
 
23
23
  def translate(key, options = {})
24
24
  super or Translate.attribute(
25
- key.to_s, options.merge(:context => context)
25
+ key.to_s, options.merge(context: context)
26
26
  )
27
27
  end
28
28
 
@@ -108,7 +108,7 @@ module Ransack
108
108
  alias :g= :groupings=
109
109
 
110
110
  def method_missing(method_id, *args)
111
- method_name = method_id.to_s
111
+ method_name = method_id.to_s.dup
112
112
  writer = method_name.sub!(/\=$/, ''.freeze)
113
113
  if attribute_method?(method_name)
114
114
  if writer
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Nodes
3
3
  class Node
4
4
  attr_reader :context
5
- delegate :contextualize, :to => :context
5
+ delegate :contextualize, to: :context
6
6
  class_attribute :i18n_words
7
7
  class_attribute :i18n_aliases
8
8
  self.i18n_words = []
@@ -9,7 +9,7 @@ module Ransack
9
9
  class << self
10
10
  def extract(context, str)
11
11
  return unless str
12
- attr, direction = str.split(/\s+/,2)
12
+ attr, direction = str.split(/\s+/, 2)
13
13
  self.new(context).build(name: attr, dir: direction)
14
14
  end
15
15
  end
@@ -31,8 +31,8 @@ module Ransack
31
31
  end
32
32
 
33
33
  def name=(name)
34
- @name = name
35
- context.bind(self, name)
34
+ @name = context.ransackable_alias(name) || name
35
+ context.bind(self, @name)
36
36
  end
37
37
 
38
38
  def dir=(dir)
@@ -2,7 +2,7 @@ module Ransack
2
2
  module Nodes
3
3
  class Value < Node
4
4
  attr_accessor :value
5
- delegate :present?, :blank?, :to => :value
5
+ delegate :present?, :blank?, to: :value
6
6
 
7
7
  def initialize(context, value = nil)
8
8
  super(context)
@@ -26,7 +26,7 @@ module Ransack
26
26
  case type
27
27
  when :date
28
28
  cast_to_date(value)
29
- when :datetime, :timestamp, :time
29
+ when :datetime, :timestamp, :time, :timestamptz
30
30
  cast_to_time(value)
31
31
  when :boolean
32
32
  cast_to_boolean(value)
@@ -50,7 +50,7 @@ module Ransack
50
50
  y, m, d = *[val].flatten
51
51
  m ||= 1
52
52
  d ||= 1
53
- Date.new(y,m,d) rescue nil
53
+ Date.new(y, m, d) rescue nil
54
54
  end
55
55
  end
56
56
 
@@ -10,7 +10,7 @@ module Ransack
10
10
  end
11
11
 
12
12
  def named(name)
13
- Ransack.predicates[name.to_s]
13
+ Ransack.predicates[(name || Ransack.options[:default_predicate]).to_s]
14
14
  end
15
15
 
16
16
  def detect_and_strip_from_string!(str)
@@ -3,7 +3,7 @@ module Ransack
3
3
 
4
4
  attr_reader :name, :type, :formatter, :args
5
5
 
6
- delegate :call, :to => :@callable
6
+ delegate :call, to: :@callable
7
7
 
8
8
  def initialize(klass, name, opts = {}, &block)
9
9
  @klass, @name = klass, name
@@ -1,6 +1,11 @@
1
- require 'ransack/nodes'
1
+ require 'ransack/nodes/bindable'
2
+ require 'ransack/nodes/node'
3
+ require 'ransack/nodes/attribute'
4
+ require 'ransack/nodes/value'
5
+ require 'ransack/nodes/condition'
6
+ require 'ransack/nodes/sort'
7
+ require 'ransack/nodes/grouping'
2
8
  require 'ransack/context'
3
- Ransack::Adapters.object_mapper.require_search
4
9
  require 'ransack/naming'
5
10
 
6
11
  module Ransack
@@ -9,15 +14,17 @@ module Ransack
9
14
 
10
15
  attr_reader :base, :context
11
16
 
12
- delegate :object, :klass, :to => :context
17
+ delegate :object, :klass, to: :context
13
18
  delegate :new_grouping, :new_condition,
14
19
  :build_grouping, :build_condition,
15
- :translate, :to => :base
20
+ :translate, to: :base
16
21
 
17
22
  def initialize(object, params = {}, options = {})
23
+ strip_whitespace = options.fetch(:strip_whitespace, Ransack.options[:strip_whitespace])
18
24
  params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
19
25
  if params.is_a? Hash
20
26
  params = params.dup
27
+ params = params.transform_values { |v| v.is_a?(String) && strip_whitespace ? v.strip : v }
21
28
  params.delete_if { |k, v| [*v].all?{ |i| i.blank? && i != false } }
22
29
  else
23
30
  params = {}
@@ -29,6 +36,7 @@ module Ransack
29
36
  )
30
37
  @scope_args = {}
31
38
  @sorts ||= []
39
+ @ignore_unknown_conditions = options[:ignore_unknown_conditions] == false ? false : true
32
40
  build(params.with_indifferent_access)
33
41
  end
34
42
 
@@ -40,11 +48,11 @@ module Ransack
40
48
  collapse_multiparameter_attributes!(params).each do |key, value|
41
49
  if ['s'.freeze, 'sorts'.freeze].freeze.include?(key)
42
50
  send("#{key}=", value)
43
- elsif base.attribute_method?(key)
44
- base.send("#{key}=", value)
45
51
  elsif @context.ransackable_scope?(key, @context.object)
46
52
  add_scope(key, value)
47
- elsif !Ransack.options[:ignore_unknown_conditions]
53
+ elsif base.attribute_method?(key)
54
+ base.send("#{key}=", value)
55
+ elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions
48
56
  raise ArgumentError, "Invalid search term #{key}"
49
57
  end
50
58
  end