ransack 4.2.1 → 4.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/lib/polyamorous/polyamorous.rb +2 -2
  4. data/lib/ransack/adapters/active_record/context.rb +30 -3
  5. data/lib/ransack/constants.rb +1 -1
  6. data/lib/ransack/context.rb +3 -0
  7. data/lib/ransack/helpers/form_builder.rb +6 -7
  8. data/lib/ransack/helpers/form_helper.rb +86 -20
  9. data/lib/ransack/invalid_search_error.rb +3 -0
  10. data/lib/ransack/locale/ja.yml +51 -51
  11. data/lib/ransack/locale/ko.yml +70 -0
  12. data/lib/ransack/locale/uk.yml +72 -0
  13. data/lib/ransack/nodes/condition.rb +39 -7
  14. data/lib/ransack/nodes/grouping.rb +1 -1
  15. data/lib/ransack/nodes/sort.rb +1 -1
  16. data/lib/ransack/nodes/value.rb +9 -1
  17. data/lib/ransack/search.rb +4 -3
  18. data/lib/ransack/version.rb +1 -1
  19. data/lib/ransack.rb +8 -0
  20. data/spec/polyamorous/join_association_spec.rb +0 -1
  21. data/spec/polyamorous/join_dependency_spec.rb +0 -1
  22. data/spec/ransack/adapters/active_record/base_spec.rb +106 -3
  23. data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
  24. data/spec/ransack/helpers/form_builder_spec.rb +0 -2
  25. data/spec/ransack/helpers/form_helper_spec.rb +219 -5
  26. data/spec/ransack/nodes/condition_spec.rb +230 -0
  27. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  28. data/spec/ransack/nodes/value_spec.rb +12 -1
  29. data/spec/ransack/predicate_spec.rb +16 -9
  30. data/spec/ransack/search_spec.rb +121 -1
  31. data/spec/ransack/translate_spec.rb +0 -1
  32. data/spec/spec_helper.rb +2 -3
  33. data/spec/support/schema.rb +42 -0
  34. metadata +43 -87
  35. data/.github/FUNDING.yml +0 -3
  36. data/.github/SECURITY.md +0 -12
  37. data/.github/workflows/codeql.yml +0 -72
  38. data/.github/workflows/cronjob.yml +0 -99
  39. data/.github/workflows/deploy.yml +0 -35
  40. data/.github/workflows/rubocop.yml +0 -20
  41. data/.github/workflows/test-deploy.yml +0 -29
  42. data/.github/workflows/test.yml +0 -131
  43. data/.gitignore +0 -7
  44. data/.nojekyll +0 -0
  45. data/.rubocop.yml +0 -50
  46. data/CHANGELOG.md +0 -1186
  47. data/CONTRIBUTING.md +0 -171
  48. data/Gemfile +0 -53
  49. data/Rakefile +0 -24
  50. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
  51. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
  52. data/docs/.gitignore +0 -19
  53. data/docs/.nojekyll +0 -0
  54. data/docs/babel.config.js +0 -3
  55. data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
  56. data/docs/docs/getting-started/_category_.json +0 -4
  57. data/docs/docs/getting-started/advanced-mode.md +0 -46
  58. data/docs/docs/getting-started/configuration.md +0 -47
  59. data/docs/docs/getting-started/search-matches.md +0 -67
  60. data/docs/docs/getting-started/simple-mode.md +0 -288
  61. data/docs/docs/getting-started/sorting.md +0 -71
  62. data/docs/docs/getting-started/using-predicates.md +0 -282
  63. data/docs/docs/going-further/_category_.json +0 -4
  64. data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
  65. data/docs/docs/going-further/associations.md +0 -70
  66. data/docs/docs/going-further/custom-predicates.md +0 -52
  67. data/docs/docs/going-further/documentation.md +0 -43
  68. data/docs/docs/going-further/exporting-to-csv.md +0 -49
  69. data/docs/docs/going-further/external-guides.md +0 -57
  70. data/docs/docs/going-further/form-customisation.md +0 -63
  71. data/docs/docs/going-further/i18n.md +0 -53
  72. data/docs/docs/going-further/img/create_release.png +0 -0
  73. data/docs/docs/going-further/merging-searches.md +0 -41
  74. data/docs/docs/going-further/other-notes.md +0 -428
  75. data/docs/docs/going-further/polymorphic-search.md +0 -46
  76. data/docs/docs/going-further/ransackers.md +0 -331
  77. data/docs/docs/going-further/release_process.md +0 -36
  78. data/docs/docs/going-further/saving-queries.md +0 -82
  79. data/docs/docs/going-further/searching-postgres.md +0 -57
  80. data/docs/docs/going-further/wiki-contributors.md +0 -82
  81. data/docs/docs/intro.md +0 -99
  82. data/docs/docusaurus.config.js +0 -120
  83. data/docs/package.json +0 -42
  84. data/docs/sidebars.js +0 -31
  85. data/docs/src/components/HomepageFeatures/index.js +0 -64
  86. data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  87. data/docs/src/css/custom.css +0 -39
  88. data/docs/src/pages/index.module.css +0 -23
  89. data/docs/src/pages/markdown-page.md +0 -7
  90. data/docs/static/.nojekyll +0 -0
  91. data/docs/static/img/docusaurus.png +0 -0
  92. data/docs/static/img/favicon.ico +0 -0
  93. data/docs/static/img/logo.svg +0 -1
  94. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  95. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  96. data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  97. data/docs/static/img/undraw_docusaurus_react.svg +0 -170
  98. data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  99. data/docs/static/logo/ransack-h.png +0 -0
  100. data/docs/static/logo/ransack-h.svg +0 -34
  101. data/docs/static/logo/ransack-v.png +0 -0
  102. data/docs/static/logo/ransack-v.svg +0 -34
  103. data/docs/static/logo/ransack.png +0 -0
  104. data/docs/static/logo/ransack.svg +0 -21
  105. data/docs/yarn.lock +0 -8884
  106. data/ransack.gemspec +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13c8c8decdc91a6c495f419ad8cbd6d489fd43247c52fb5dd68faba65a5dba0b
4
- data.tar.gz: 6badc6529685a1421d6f71cf3b4e8f6e40b933ab61b0f7135d35e5246ff95de4
3
+ metadata.gz: 4fe4a128cacdb920f2efe5fc5063cd21b8fa1a4a527fa07e84123f2ead7a6694
4
+ data.tar.gz: 82f66ffeaa4d8bed52614374fe71617bb1c2d85ac1745dfca7e4520e07782545
5
5
  SHA512:
6
- metadata.gz: b1215ab1627a929bf0dcbc01be0bc67c65115c2544fd8eb731ccdaf7648e28db067c12920468eebbc9df46b8f7623b3c1373071b43d7d38d38fb28935235701c
7
- data.tar.gz: '04693edcd727c4bf0aead8d37662835920efb4b17c52c9a56d679b272d6932ea7f4d51832a06b1c76dae30dfa9620836a1760891d876a60b18c25fbc93a8c6f9'
6
+ metadata.gz: a80ce4b0da981c6e3b2d18de8c742d3598a2a7479b44d04adaa1ce3c60a81fa4377f706c78048c1d72f719d488afd379c303b55811053d8613eb82cf0f0dfcad
7
+ data.tar.gz: cd3f836f63aa19596a3510961bbe64a1f9abcc858473bf9516623e5fe76908bcbb346d36cf1cd7cb83094f0a3c0f4bcdb942b0e137a9020cd8772e28d01d9f51
data/README.md CHANGED
@@ -13,7 +13,7 @@ There are advanced searching solutions around, like ElasticSearch or Algolia. **
13
13
 
14
14
  Ready to move beyond the basics? Use **advanced features** like i18n and extensive configuration options.
15
15
 
16
- Ransack is supported for Rails 7.2, 7.1, 7.0, 6.1 on Ruby 3.1 and later.
16
+ Ransack is supported for Rails 8.0, 7.2, 7.1 on Ruby 3.1 and later.
17
17
 
18
18
  ## Installation
19
19
 
@@ -33,9 +33,11 @@ gem 'ransack', :github => 'activerecord-hackery/ransack', :branch => 'main'
33
33
 
34
34
  ### Documentation
35
35
 
36
- There is [extensive documentation on Ransack](https://activerecord-hackery.github.io/ransack/), which is a [Docusaurus](https://docusaurus.io/) project and run as a GitHub Pages site.
36
+ There is [extensive documentation on Ransack](https://activerecord-hackery.github.io/ransack/), which is a [Docusaurus](https://docusaurus.io/) project and run as a GitHub Pages site. Alternatively there is [AI Generated documentation](https://deepwiki.com/activerecord-hackery/ransack/1-overview) produced by [devin.ai](https://devin.ai/).
37
37
 
38
- ## Issues tracker
38
+ This [gist](https://gist.github.com/raghubetina/d5fc3df67ddbadcac271) has a quick-start cheatsheet, created by [@raghubetina](https://gist.github.com/raghubetina)
39
+
40
+ ## Issue tracker
39
41
 
40
42
  * Before filing an issue, please read the [Contributing Guide](CONTRIBUTING.md).
41
43
  * 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_.
@@ -1,4 +1,4 @@
1
- if defined?(::ActiveRecord)
1
+ ActiveSupport.on_load(:active_record) do
2
2
  module Polyamorous
3
3
  InnerJoin = Arel::Nodes::InnerJoin
4
4
  OuterJoin = Arel::Nodes::OuterJoin
@@ -15,7 +15,7 @@ if defined?(::ActiveRecord)
15
15
  require 'polyamorous/activerecord/join_dependency'
16
16
  require 'polyamorous/activerecord/reflection'
17
17
 
18
- if ::ActiveRecord.version >= ::Gem::Version.new("7.2")
18
+ if ::ActiveRecord.version >= ::Gem::Version.new("7.2") && ::ActiveRecord.version < ::Gem::Version.new("7.2.2.1")
19
19
  require "polyamorous/activerecord/join_association_7_2"
20
20
  end
21
21
 
@@ -19,7 +19,8 @@ module Ransack
19
19
  unless schema_cache.send(:data_source_exists?, table)
20
20
  raise "No table named #{table} exists."
21
21
  end
22
- attr.klass.columns.find { |column| column.name == name }.type
22
+ column = attr.klass.columns.find { |column| column.name == name }
23
+ column&.type
23
24
  end
24
25
 
25
26
  def evaluate(search, opts = {})
@@ -171,7 +172,25 @@ module Ransack
171
172
  join_constraints.each do |j|
172
173
  subquery.join_sources << Arel::Nodes::InnerJoin.new(j.left, j.right)
173
174
  end
174
- subquery.where(correlated_key.eq(primary_key))
175
+
176
+ # Handle polymorphic associations where correlated_key is an array
177
+ if correlated_key.is_a?(Array)
178
+ # For polymorphic associations, we need to add conditions for both the foreign key and type
179
+ correlated_key.each_with_index do |key, index|
180
+ if index == 0
181
+ # This is the foreign key
182
+ subquery = subquery.where(key.eq(primary_key))
183
+ else
184
+ # This is the type key, which should be equal to the model name
185
+ subquery = subquery.where(key.eq(@klass.name))
186
+ end
187
+ end
188
+ else
189
+ # Original behavior for non-polymorphic associations
190
+ subquery = subquery.where(correlated_key.eq(primary_key))
191
+ end
192
+
193
+ subquery
175
194
  end
176
195
 
177
196
  def primary_key
@@ -201,7 +220,15 @@ module Ransack
201
220
  nil
202
221
  end
203
222
  when Arel::Nodes::And
204
- extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
223
+ # And may have multiple children, so we need to check all, not via left/right
224
+ if join_root.children.any?
225
+ join_root.children.each do |child|
226
+ key = extract_correlated_key(child)
227
+ return key if key
228
+ end
229
+ else
230
+ extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
231
+ end
205
232
  else
206
233
  # eg parent was Arel::Nodes::And and the evaluated side was one of
207
234
  # Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
@@ -165,7 +165,7 @@ module Ransack
165
165
  when "Mysql2".freeze
166
166
  # Necessary for MySQL
167
167
  unescaped.to_s.gsub(/([\\%_])/, '\\\\\\1')
168
- when "PostgreSQL".freeze
168
+ when "PostGIS".freeze, "PostgreSQL".freeze
169
169
  # Necessary for PostgreSQL
170
170
  unescaped.to_s.gsub(/([\\%_.])/, '\\\\\\1')
171
171
  else
@@ -77,6 +77,9 @@ module Ransack
77
77
  return unless @klass.method(scope) && args != false
78
78
  @object = if scope_arity(scope) < 1 && args == true
79
79
  @object.public_send(scope)
80
+ elsif scope_arity(scope) == 1 && args.is_a?(Array)
81
+ # For scopes with arity 1, pass the array as a single argument instead of splatting
82
+ @object.public_send(scope, args)
80
83
  else
81
84
  @object.public_send(scope, *args)
82
85
  end
@@ -6,13 +6,12 @@ module ActionView::Helpers::Tags
6
6
  # https://github.com/rails/rails/commit/c1a118a
7
7
  class Base
8
8
  private
9
- if defined? ::ActiveRecord
10
- def value
11
- if @allow_method_names_outside_object
12
- object.send @method_name if object && object.respond_to?(@method_name, true)
13
- else
14
- object.send @method_name if object
15
- end
9
+
10
+ def value
11
+ if @allow_method_names_outside_object
12
+ object.send @method_name if object && object.respond_to?(@method_name, true)
13
+ else
14
+ object.send @method_name if object
16
15
  end
17
16
  end
18
17
  end
@@ -7,30 +7,48 @@ module Ransack
7
7
  # <%= search_form_for(@q) do |f| %>
8
8
  #
9
9
  def search_form_for(record, options = {}, &proc)
10
- if record.is_a? Ransack::Search
11
- search = record
12
- options[:url] ||= polymorphic_path(
13
- search.klass, format: options.delete(:format)
14
- )
15
- elsif record.is_a?(Array) &&
16
- (search = record.detect { |o| o.is_a?(Ransack::Search) })
17
- options[:url] ||= polymorphic_path(
18
- options_for(record), format: options.delete(:format)
19
- )
10
+ search = extract_search_and_set_url(record, options, 'search_form_for')
11
+ options[:html] ||= {}
12
+ html_options = build_html_options(search, options, :get)
13
+ finalize_form_options(options, html_options)
14
+ form_for(record, options, &proc)
15
+ end
16
+
17
+ # +search_form_with+
18
+ #
19
+ # <%= search_form_with(model: @q) do |f| %>
20
+ #
21
+ def search_form_with(record_or_options = {}, options = {}, &proc)
22
+ if record_or_options.is_a?(Hash) && record_or_options.key?(:model)
23
+ # Called with keyword arguments: search_form_with(model: @q)
24
+ options = record_or_options
25
+ record = options.delete(:model)
20
26
  else
21
- raise ArgumentError,
22
- 'No Ransack::Search object was provided to search_form_for!'
27
+ # Called with positional arguments: search_form_with(@q)
28
+ record = record_or_options
23
29
  end
30
+ search = extract_search_and_set_url(record, options, 'search_form_with')
24
31
  options[:html] ||= {}
25
- html_options = {
26
- class: html_option_for(options[:class], search),
27
- id: html_option_for(options[:id], search),
28
- method: :get
29
- }
30
- options[:as] ||= Ransack.options[:search_key]
31
- options[:html].reverse_merge!(html_options)
32
- options[:builder] ||= FormBuilder
32
+ html_options = build_html_options(search, options, :get)
33
+ finalize_form_with_options(options, html_options)
34
+ form_with(model: search, **options, &proc)
35
+ end
33
36
 
37
+ # +turbo_search_form_for+
38
+ #
39
+ # <%= turbo_search_form_for(@q) do |f| %>
40
+ #
41
+ # This is a turbo-enabled version of search_form_for that submits via turbo streams
42
+ # instead of traditional HTML GET requests. Useful for seamless integration with
43
+ # paginated results and other turbo-enabled components.
44
+ #
45
+ def turbo_search_form_for(record, options = {}, &proc)
46
+ search = extract_search_and_set_url(record, options, 'turbo_search_form_for')
47
+ options[:html] ||= {}
48
+ turbo_options = build_turbo_options(options)
49
+ method = options.delete(:method) || :post
50
+ html_options = build_html_options(search, options, method).merge(turbo_options)
51
+ finalize_form_options(options, html_options)
34
52
  form_for(record, options, &proc)
35
53
  end
36
54
 
@@ -68,6 +86,54 @@ module Ransack
68
86
 
69
87
  private
70
88
 
89
+ def extract_search_and_set_url(record, options, method_name)
90
+ if record.is_a? Ransack::Search
91
+ search = record
92
+ options[:url] ||= polymorphic_path(
93
+ search.klass, format: options.delete(:format)
94
+ )
95
+ search
96
+ elsif record.is_a?(Array) &&
97
+ (search = record.detect { |o| o.is_a?(Ransack::Search) })
98
+ options[:url] ||= polymorphic_path(
99
+ options_for(record), format: options.delete(:format)
100
+ )
101
+ search
102
+ else
103
+ raise ArgumentError,
104
+ "No Ransack::Search object was provided to #{method_name}!"
105
+ end
106
+ end
107
+
108
+ def build_turbo_options(options)
109
+ data_options = {}
110
+ if options[:turbo_frame]
111
+ data_options[:turbo_frame] = options.delete(:turbo_frame)
112
+ end
113
+ data_options[:turbo_action] = options.delete(:turbo_action) || 'advance'
114
+ { data: data_options }
115
+ end
116
+
117
+ def build_html_options(search, options, method)
118
+ {
119
+ class: html_option_for(options[:class], search),
120
+ id: html_option_for(options[:id], search),
121
+ method: method
122
+ }
123
+ end
124
+
125
+ def finalize_form_options(options, html_options)
126
+ options[:as] ||= Ransack.options[:search_key]
127
+ options[:html].reverse_merge!(html_options)
128
+ options[:builder] ||= FormBuilder
129
+ end
130
+
131
+ def finalize_form_with_options(options, html_options)
132
+ options[:scope] ||= Ransack.options[:search_key]
133
+ options[:html].reverse_merge!(html_options)
134
+ options[:builder] ||= FormBuilder
135
+ end
136
+
71
137
  def options_for(record)
72
138
  record.map { |r| parse_record(r) }
73
139
  end
@@ -0,0 +1,3 @@
1
+ module Ransack
2
+ class InvalidSearchError < ArgumentError; end
3
+ end
@@ -14,57 +14,57 @@ ja:
14
14
  asc: "昇順"
15
15
  desc: "降順"
16
16
  predicates:
17
- eq: "は以下と等しい"
18
- eq_any: "は以下のいずれかに等しい"
19
- eq_all: "は以下の全てに等しい"
20
- not_eq: "は以下と等しくない"
21
- not_eq_any: "は以下のいずれかに等しくない"
22
- not_eq_all: "は以下の全てと等しくない"
23
- matches: "は以下と合致している"
24
- matches_any: "は以下のいずれかと合致している"
25
- matches_all: "は以下の全てと合致している"
26
- does_not_match: "は以下と合致していない"
27
- does_not_match_any: "は以下のいずれかに合致していない"
28
- does_not_match_all: "は以下の全てに合致していない"
29
- lt: "は以下よりも小さい"
30
- lt_any: "は以下のいずれかより小さい"
31
- lt_all: "は以下の全てよりも小さい"
32
- lteq: "は以下より小さいか等しい"
33
- lteq_any: "は以下のいずれかより小さいか等しい"
34
- lteq_all: "は以下の全てより小さいか等しい"
35
- gt: "は以下より大きい"
36
- gt_any: "は以下のいずれかより大きい"
37
- gt_all: "は以下の全てより大きい"
38
- gteq: "は以下より大きいか等しい"
39
- gteq_any: "は以下のいずれかより大きいか等しい"
40
- gteq_all: "は以下の全てより大きいか等しい"
41
- in: "は以下の範囲内である"
42
- in_any: "は以下のいずれかの範囲内である"
43
- in_all: "は以下の全ての範囲内である"
44
- not_in: "は以下の範囲内でない"
45
- not_in_any: "は以下のいずれかの範囲内でない"
46
- not_in_all: "は以下の全ての範囲内"
47
- cont: "は以下を含む"
48
- cont_any: "はいずれかを含む"
49
- cont_all: "は以下の全てを含む"
50
- not_cont: "は含まない"
51
- not_cont_any: "は以下のいずれかを含まない"
52
- not_cont_all: "は以下の全てを含まない"
53
- start: "は以下で始まる"
54
- start_any: "は以下のどれかで始まる"
55
- start_all: "は以下の全てで始まる"
56
- not_start: "は以下で始まらない"
57
- not_start_any: "は以下のいずれかで始まらない"
58
- not_start_all: "は以下の全てで始まらない"
59
- end: "は以下で終わる"
60
- end_any: "は以下のいずれかで終わる"
61
- end_all: "は以下の全てで終わる"
62
- not_end: "は以下のどれでも終わらない"
63
- not_end_any: "は以下のいずれかで終わらない"
64
- not_end_all: "は以下の全てで終わらない"
17
+ eq: "等しい"
18
+ eq_any: "いずれかに等しい"
19
+ eq_all: "全てに等しい"
20
+ not_eq: "等しくない"
21
+ not_eq_any: "いずれかに等しくない"
22
+ not_eq_all: "全てと等しくない"
23
+ matches: "合致している"
24
+ matches_any: "いずれかと合致している"
25
+ matches_all: "全てと合致している"
26
+ does_not_match: "合致していない"
27
+ does_not_match_any: "いずれかに合致していない"
28
+ does_not_match_all: "全てに合致していない"
29
+ lt: "小さい"
30
+ lt_any: "いずれかより小さい"
31
+ lt_all: "全てよりも小さい"
32
+ lteq: "小さいか等しい"
33
+ lteq_any: "いずれかより小さいか等しい"
34
+ lteq_all: "全てより小さいか等しい"
35
+ gt: "大きい"
36
+ gt_any: "いずれかより大きい"
37
+ gt_all: "全てより大きい"
38
+ gteq: "大きいか等しい"
39
+ gteq_any: "いずれかより大きいか等しい"
40
+ gteq_all: "全てより大きいか等しい"
41
+ in: "範囲内である"
42
+ in_any: "いずれかの範囲内である"
43
+ in_all: "全ての範囲内である"
44
+ not_in: "範囲内でない"
45
+ not_in_any: "いずれかの範囲内でない"
46
+ not_in_all: "全ての範囲内"
47
+ cont: "含む"
48
+ cont_any: "いずれかを含む"
49
+ cont_all: "全てを含む"
50
+ not_cont: "含まない"
51
+ not_cont_any: "いずれかを含まない"
52
+ not_cont_all: "全てを含まない"
53
+ start: "始まる"
54
+ start_any: "どれかで始まる"
55
+ start_all: "全てで始まる"
56
+ not_start: "始まらない"
57
+ not_start_any: "いずれかで始まらない"
58
+ not_start_all: "全てで始まらない"
59
+ end: "終わる"
60
+ end_any: "いずれかで終わる"
61
+ end_all: "全てで終わる"
62
+ not_end: "どれでも終わらない"
63
+ not_end_any: "いずれかで終わらない"
64
+ not_end_all: "全てで終わらない"
65
65
  'true': "真"
66
66
  'false': "偽"
67
- present: "は存在する"
68
- blank: "は空である"
67
+ present: "存在する"
68
+ blank: "空である"
69
69
  'null': "無効"
70
- not_null: "は無効ではない"
70
+ not_null: "無効ではない"
@@ -0,0 +1,70 @@
1
+ ko:
2
+ ransack:
3
+ search: "검색"
4
+ predicate: "조건"
5
+ and: "그리고"
6
+ or: "또는"
7
+ any: "어떤 것이든"
8
+ all: "모두"
9
+ combinator: "조합기"
10
+ attribute: "속성"
11
+ value: "값"
12
+ condition: "조건"
13
+ sort: "정렬"
14
+ asc: "오름차순"
15
+ desc: "내림차순"
16
+ predicates:
17
+ eq: "같음"
18
+ eq_any: "어떤 것이든 같음"
19
+ eq_all: "모두 같음"
20
+ not_eq: "같지 않음"
21
+ not_eq_any: "어떤 것이든 같지 않음"
22
+ not_eq_all: "모두 같지 않음"
23
+ matches: "일치함"
24
+ matches_any: "어떤 것이든 일치함"
25
+ matches_all: "모두 일치함"
26
+ does_not_match: "일치하지 않음"
27
+ does_not_match_any: "어떤 것이든 일치하지 않음"
28
+ does_not_match_all: "모두 일치하지 않음"
29
+ lt: "보다 작음"
30
+ lt_any: "어떤 것이든 보다 작음"
31
+ lt_all: "모두 보다 작음"
32
+ lteq: "보다 작거나 같음"
33
+ lteq_any: "어떤 것이든 보다 작거나 같음"
34
+ lteq_all: "모두 보다 작거나 같음"
35
+ gt: "보다 큼"
36
+ gt_any: "어떤 것이든 보다 큼"
37
+ gt_all: "모두 보다 큼"
38
+ gteq: "보다 크거나 같음"
39
+ gteq_any: "어떤 것이든 보다 크거나 같음"
40
+ gteq_all: "모두 보다 크거나 같음"
41
+ in: "포함됨"
42
+ in_any: "어떤 것이든 포함됨"
43
+ in_all: "모두 포함됨"
44
+ not_in: "포함되지 않음"
45
+ not_in_any: "어떤 것이든 포함되지 않음"
46
+ not_in_all: "모두 포함되지 않음"
47
+ cont: "포함함"
48
+ cont_any: "어떤 것이든 포함함"
49
+ cont_all: "모두 포함함"
50
+ not_cont: "포함하지 않음"
51
+ not_cont_any: "어떤 것이든 포함하지 않음"
52
+ not_cont_all: "모두 포함하지 않음"
53
+ start: "시작함"
54
+ start_any: "어떤 것이든 시작함"
55
+ start_all: "모두 시작함"
56
+ not_start: "시작하지 않음"
57
+ not_start_any: "어떤 것이든 시작하지 않음"
58
+ not_start_all: "모두 시작하지 않음"
59
+ end: "끝남"
60
+ end_any: "어떤 것이든 끝남"
61
+ end_all: "모두 끝남"
62
+ not_end: "끝나지 않음"
63
+ not_end_any: "어떤 것이든 끝나지 않음"
64
+ not_end_all: "모두 끝나지 않음"
65
+ 'true': "참"
66
+ 'false': "거짓"
67
+ present: "존재함"
68
+ blank: "비어있음"
69
+ 'null': "널"
70
+ not_null: "널이 아님"
@@ -0,0 +1,72 @@
1
+ # Інші переклади на https://github.com/activerecord-hackery/ransack/blob/main/lib/ransack/locale
2
+ #
3
+ uk:
4
+ ransack:
5
+ search: пошук
6
+ predicate: предикат
7
+ and: і
8
+ or: або
9
+ any: будь-який
10
+ all: усі
11
+ combinator: комбінатор
12
+ attribute: атрибут
13
+ value: значення
14
+ condition: умова
15
+ sort: сортування
16
+ asc: за зростанням
17
+ desc: за спаданням
18
+ predicates:
19
+ eq: рівний
20
+ eq_any: рівний будь-якому
21
+ eq_all: рівний усім
22
+ not_eq: не рівний
23
+ not_eq_any: не рівний будь-якому
24
+ not_eq_all: не рівний усім
25
+ matches: збігається
26
+ matches_any: збігається з будь-яким
27
+ matches_all: збігається з усіма
28
+ does_not_match: не збігається
29
+ does_not_match_any: не збігається з будь-яким
30
+ does_not_match_all: не збігається з усіма
31
+ lt: менше ніж
32
+ lt_any: менше за будь-який
33
+ lt_all: менше за всі
34
+ lteq: менше або рівне
35
+ lteq_any: менше або рівне будь-якому
36
+ lteq_all: менше або рівне всім
37
+ gt: більше ніж
38
+ gt_any: більше ніж будь-який
39
+ gt_all: більше ніж усі
40
+ gteq: більше або рівне
41
+ gteq_any: більше або рівне будь-якому
42
+ gteq_all: більше або рівне всім
43
+ in: міститься у
44
+ in_any: міститься в будь-якому
45
+ in_all: міститься в усіх
46
+ not_in: не міститься у
47
+ not_in_any: не міститься в будь-якому
48
+ not_in_all: не міститься в усіх
49
+ cont: містить
50
+ cont_any: містить будь-який
51
+ cont_all: містить усі
52
+ not_cont: не містить
53
+ not_cont_any: не містить жодного
54
+ not_cont_all: не містить усіх
55
+ start: починається з
56
+ start_any: починається з будь-якого
57
+ start_all: починається з усіх
58
+ not_start: не починається з
59
+ not_start_any: не починається з будь-якого
60
+ not_start_all: не починається з усіх
61
+ end: закінчується на
62
+ end_any: закінчується на будь-який
63
+ end_all: закінчується на всі
64
+ not_end: не закінчується на
65
+ not_end_any: не закінчується на будь-який
66
+ not_end_all: не закінчується на всі
67
+ 'true': так
68
+ 'false': ні
69
+ present: присутній
70
+ blank: порожній
71
+ 'null': нульовий
72
+ not_null: не нульовий
@@ -1,3 +1,5 @@
1
+ require 'ransack/invalid_search_error'
2
+
1
3
  module Ransack
2
4
  module Nodes
3
5
  class Condition < Node
@@ -38,7 +40,7 @@ module Ransack
38
40
  predicate = Predicate.named(name)
39
41
 
40
42
  unless predicate || Ransack.options[:ignore_unknown_conditions]
41
- raise ArgumentError, "No valid predicate for #{key}"
43
+ raise InvalidSearchError, "No valid predicate for #{key}"
42
44
  end
43
45
 
44
46
  if context.present?
@@ -224,7 +226,7 @@ module Ransack
224
226
  end
225
227
 
226
228
  def casted_values_for_attribute(attr)
227
- validated_values.map { |v| v.cast(predicate.type || attr.type) }
229
+ validated_values.map(&:cast_array)
228
230
  end
229
231
 
230
232
  def formatted_values_for_attribute(attr)
@@ -233,6 +235,9 @@ module Ransack
233
235
  val = attr.ransacker.formatter.call(val)
234
236
  end
235
237
  val = predicate.format(val)
238
+ if val.is_a?(String) && val.include?('%')
239
+ val = Arel::Nodes::Quoted.new(val)
240
+ end
236
241
  val
237
242
  end
238
243
  if predicate.wants_array
@@ -286,12 +291,24 @@ module Ransack
286
291
  def arel_predicate
287
292
  predicate = attributes.map { |attribute|
288
293
  association = attribute.parent
289
- if negative? && attribute.associated_collection?
294
+ parent_table = association.table
295
+
296
+ if negative? && attribute.associated_collection? && not_nested_condition(attribute, parent_table)
290
297
  query = context.build_correlated_subquery(association)
291
298
  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))
299
+
300
+ case self.predicate_name
301
+ when 'not_null'
302
+ if self.value
303
+ query.where(format_predicate(attribute))
304
+ Arel::Nodes::In.new(context.primary_key, Arel.sql(query.to_sql))
305
+ else
306
+ query.where(format_predicate(attribute).not)
307
+ Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
308
+ end
309
+ when 'not_cont'
310
+ query.where(attribute.attr.matches(formatted_values_for_attribute(attribute)))
311
+ Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
295
312
  else
296
313
  query.where(format_predicate(attribute).not)
297
314
  Arel::Nodes::NotIn.new(context.primary_key, Arel.sql(query.to_sql))
@@ -313,6 +330,10 @@ module Ransack
313
330
  predicate
314
331
  end
315
332
 
333
+ def not_nested_condition(attribute, parent_table)
334
+ parent_table.class != Arel::Nodes::TableAlias && attribute.name.starts_with?(parent_table.name)
335
+ end
336
+
316
337
  private
317
338
 
318
339
  def combinator_method
@@ -322,6 +343,13 @@ module Ransack
322
343
  def format_predicate(attribute)
323
344
  arel_pred = arel_predicate_for_attribute(attribute)
324
345
  arel_values = formatted_values_for_attribute(attribute)
346
+
347
+ # For LIKE predicates, wrap the value in Arel::Nodes.build_quoted to prevent
348
+ # ActiveRecord normalization from affecting wildcard patterns
349
+ if like_predicate?(arel_pred)
350
+ arel_values = Arel::Nodes.build_quoted(arel_values)
351
+ end
352
+
325
353
  predicate = attr_value_for_attribute(attribute).public_send(arel_pred, arel_values)
326
354
 
327
355
  if in_predicate?(predicate)
@@ -338,8 +366,12 @@ module Ransack
338
366
  predicate.class == Arel::Nodes::In || predicate.class == Arel::Nodes::NotIn
339
367
  end
340
368
 
369
+ def like_predicate?(arel_predicate)
370
+ arel_predicate == 'matches' || arel_predicate == 'does_not_match'
371
+ end
372
+
341
373
  def casted_array?(predicate)
342
- predicate.value.is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
374
+ predicate.is_a?(Arel::Nodes::Casted) && predicate.value.is_a?(Array)
343
375
  end
344
376
 
345
377
  def format_values_for(predicate)
@@ -53,7 +53,7 @@ module Ransack
53
53
  end
54
54
 
55
55
  def []=(key, value)
56
- conditions.reject! { |c| c.key == key.to_s }
56
+ conditions.reject! { |c| c.key == key.to_s && c&.value == value&.value }
57
57
  self.conditions << value
58
58
  end
59
59
 
@@ -8,7 +8,7 @@ module Ransack
8
8
 
9
9
  class << self
10
10
  def extract(context, str)
11
- return unless str
11
+ return if str.blank?
12
12
  attr, direction = str.split(/\s+/, 2)
13
13
  self.new(context).build(name: attr, dir: direction)
14
14
  end