ransack 2.4.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/cronjob.yml +6 -9
  3. data/.github/workflows/rubocop.yml +1 -1
  4. data/.github/workflows/test.yml +25 -34
  5. data/.nojekyll +0 -0
  6. data/CHANGELOG.md +90 -11
  7. data/CONTRIBUTING.md +4 -3
  8. data/README.md +45 -973
  9. data/docs/.gitignore +20 -0
  10. data/docs/.nojekyll +0 -0
  11. data/docs/babel.config.js +3 -0
  12. data/docs/blog/2022-03-27-ransack-3.0.0.md +20 -0
  13. data/docs/docs/getting-started/_category_.json +4 -0
  14. data/docs/docs/getting-started/advanced-mode.md +46 -0
  15. data/docs/docs/getting-started/configuration.md +47 -0
  16. data/docs/docs/getting-started/search-matches.md +67 -0
  17. data/docs/docs/getting-started/simple-mode.md +284 -0
  18. data/docs/docs/getting-started/sorting.md +79 -0
  19. data/docs/docs/getting-started/using-predicates.md +282 -0
  20. data/docs/docs/going-further/_category_.json +4 -0
  21. data/docs/docs/going-further/associations.md +70 -0
  22. data/docs/docs/going-further/custom-predicates.md +52 -0
  23. data/docs/docs/going-further/documentation.md +31 -0
  24. data/docs/docs/going-further/exporting-to-csv.md +49 -0
  25. data/docs/docs/going-further/external-guides.md +57 -0
  26. data/docs/docs/going-further/form-customisation.md +63 -0
  27. data/docs/docs/going-further/i18n.md +53 -0
  28. data/docs/{img → docs/going-further/img}/create_release.png +0 -0
  29. data/docs/docs/going-further/merging-searches.md +41 -0
  30. data/docs/docs/going-further/other-notes.md +428 -0
  31. data/docs/docs/going-further/ransackers.md +331 -0
  32. data/docs/docs/going-further/release_process.md +36 -0
  33. data/docs/docs/going-further/saving-queries.md +82 -0
  34. data/docs/docs/going-further/searching-postgres.md +57 -0
  35. data/docs/docs/intro.md +99 -0
  36. data/docs/docusaurus.config.js +108 -0
  37. data/docs/package-lock.json +9207 -0
  38. data/docs/package.json +37 -0
  39. data/docs/sidebars.js +31 -0
  40. data/docs/src/components/HomepageFeatures/index.js +64 -0
  41. data/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  42. data/docs/src/css/custom.css +39 -0
  43. data/docs/src/pages/index.module.css +23 -0
  44. data/docs/src/pages/markdown-page.md +7 -0
  45. data/docs/static/.nojekyll +0 -0
  46. data/docs/static/img/docusaurus.png +0 -0
  47. data/docs/static/img/favicon.ico +0 -0
  48. data/docs/static/img/logo.svg +1 -0
  49. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  50. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  51. data/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  52. data/docs/static/img/undraw_docusaurus_react.svg +170 -0
  53. data/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  54. data/{logo → docs/static/logo}/ransack-h.png +0 -0
  55. data/{logo → docs/static/logo}/ransack-h.svg +0 -0
  56. data/{logo → docs/static/logo}/ransack-v.png +0 -0
  57. data/{logo → docs/static/logo}/ransack-v.svg +0 -0
  58. data/{logo → docs/static/logo}/ransack.png +0 -0
  59. data/{logo → docs/static/logo}/ransack.svg +0 -0
  60. data/docs/yarn.lock +7671 -0
  61. data/lib/polyamorous/activerecord_6.0_ruby_2/join_association.rb +20 -1
  62. data/lib/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -1
  63. data/lib/polyamorous/activerecord_6.0_ruby_2/reflection.rb +11 -1
  64. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -4
  65. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -1
  66. data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_association.rb +0 -0
  67. data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/join_dependency.rb +0 -0
  68. data/lib/polyamorous/{activerecord_6.2_ruby_2 → activerecord_7.0_ruby_2}/reflection.rb +0 -0
  69. data/lib/polyamorous.rb +1 -0
  70. data/lib/ransack/adapters/active_record/base.rb +1 -3
  71. data/lib/ransack/adapters/active_record/context.rb +7 -2
  72. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +10 -9
  73. data/lib/ransack/configuration.rb +16 -2
  74. data/lib/ransack/helpers/form_helper.rb +11 -3
  75. data/lib/ransack/locale/sv.yml +70 -0
  76. data/lib/ransack/nodes/sort.rb +2 -2
  77. data/lib/ransack/search.rb +4 -3
  78. data/lib/ransack/translate.rb +1 -1
  79. data/lib/ransack/version.rb +1 -1
  80. data/ransack.gemspec +4 -4
  81. data/spec/polyamorous/activerecord_compatibility_spec.rb +15 -0
  82. data/spec/polyamorous/join_association_spec.rb +1 -6
  83. data/spec/polyamorous/join_dependency_spec.rb +0 -16
  84. data/spec/ransack/adapters/active_record/base_spec.rb +28 -11
  85. data/spec/ransack/configuration_spec.rb +14 -0
  86. data/spec/ransack/helpers/form_helper_spec.rb +33 -2
  87. data/spec/ransack/nodes/condition_spec.rb +13 -0
  88. data/spec/ransack/search_spec.rb +140 -27
  89. data/spec/support/schema.rb +49 -0
  90. metadata +72 -25
  91. data/docs/release_process.md +0 -20
  92. data/lib/polyamorous/activerecord_5.2_ruby_2/join_association.rb +0 -24
  93. data/lib/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -79
  94. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -11
@@ -1 +1,20 @@
1
- require 'polyamorous/activerecord_5.2_ruby_2/join_association'
1
+ module Polyamorous
2
+ module JoinAssociationExtensions
3
+ include SwappingReflectionClass
4
+ def self.prepended(base)
5
+ base.class_eval { attr_reader :join_type }
6
+ end
7
+
8
+ def initialize(reflection, children, polymorphic_class = nil, join_type = Arel::Nodes::InnerJoin)
9
+ @join_type = join_type
10
+ if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
11
+ swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
12
+ super(reflection, children)
13
+ self.reflection.options[:polymorphic] = true
14
+ end
15
+ else
16
+ super(reflection, children)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,4 +1,3 @@
1
- # active_record_6.0_ruby_2/join_dependency.rb
2
1
  module Polyamorous
3
2
  module JoinDependencyExtensions
4
3
  # Replaces ActiveRecord::Associations::JoinDependency#build
@@ -1 +1,11 @@
1
- require 'polyamorous/activerecord_5.2_ruby_2/reflection'
1
+ module Polyamorous
2
+ module ReflectionExtensions
3
+ def join_scope(table, foreign_table, foreign_klass)
4
+ if respond_to?(:polymorphic?) && polymorphic?
5
+ super.where!(foreign_table[foreign_type].eq(klass.name))
6
+ else
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -66,9 +66,5 @@ module Polyamorous
66
66
 
67
67
  joins
68
68
  end
69
-
70
- def ==(other)
71
- base_klass == other.base_klass
72
- end
73
69
  end
74
70
  end
@@ -1,4 +1,3 @@
1
- # active_record_6.1_ruby_2/join_dependency.rb
2
1
  module Polyamorous
3
2
  module JoinDependencyExtensions
4
3
  # Replaces ActiveRecord::Associations::JoinDependency#build
@@ -0,0 +1 @@
1
+ require 'polyamorous/polyamorous'
@@ -4,7 +4,6 @@ module Ransack
4
4
  module Base
5
5
 
6
6
  def self.extended(base)
7
- alias :search :ransack unless base.respond_to? :search
8
7
  base.class_eval do
9
8
  class_attribute :_ransackers
10
9
  class_attribute :_ransack_aliases
@@ -14,7 +13,6 @@ module Ransack
14
13
  end
15
14
 
16
15
  def ransack(params = {}, options = {})
17
- ActiveSupport::Deprecation.warn("#search is deprecated and will be removed in 2.3, please use #ransack instead") if __callee__ == :search
18
16
  Search.new(self, params, options)
19
17
  end
20
18
 
@@ -70,7 +68,7 @@ module Ransack
70
68
  end
71
69
 
72
70
  # ransack_scope_skip_sanitize_args, by default, returns an empty array.
73
- # i.e. use the sanitize_scope_args setting to determin if args should be converted.
71
+ # i.e. use the sanitize_scope_args setting to determine if args should be converted.
74
72
  # For overriding with a list of scopes which should be passed the args as-is.
75
73
  #
76
74
  def ransackable_scopes_skip_sanitize_args
@@ -44,9 +44,13 @@ module Ransack
44
44
  else
45
45
  case Ransack.options[:postgres_fields_sort_option]
46
46
  when :nulls_first
47
- scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS FIRST" : "#{scope_or_sort.to_sql} NULLS LAST"
47
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
48
48
  when :nulls_last
49
- scope_or_sort = scope_or_sort.direction == :asc ? "#{scope_or_sort.to_sql} NULLS LAST" : "#{scope_or_sort.to_sql} NULLS FIRST"
49
+ scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
50
+ when :nulls_always_first
51
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
52
+ when :nulls_always_last
53
+ scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
50
54
  end
51
55
 
52
56
  relation = relation.order(scope_or_sort)
@@ -139,6 +143,7 @@ module Ransack
139
143
  stashed.eql?(association)
140
144
  }
141
145
  @object.joins_values.delete_if { |jd|
146
+ jd.instance_variables.include?(:@join_root) &&
142
147
  jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
143
148
  }
144
149
  end
@@ -47,18 +47,19 @@ module Ransack
47
47
  end
48
48
 
49
49
  def casted_array?(predicate)
50
- (predicate.respond_to?(:value) && predicate.value.is_a?(Array)) || # Rails 6.1
51
- (predicate.respond_to?(:val) && predicate.val.is_a?(Array)) # Rails 5.2, 6.0
50
+ value_from(predicate).is_a?(Array) && predicate.is_a?(Arel::Nodes::Casted)
52
51
  end
53
52
 
54
- def format_values_for(predicate)
55
- value = if predicate.respond_to?(:value)
56
- predicate.value # Rails 6.1
57
- else
58
- predicate.val # Rails 5.2, 6.0
59
- end
53
+ def value_from(predicate)
54
+ if predicate.respond_to?(:value)
55
+ predicate.value # Rails 6.1
56
+ elsif predicate.respond_to?(:val)
57
+ predicate.val # Rails 6.0
58
+ end
59
+ end
60
60
 
61
- value.map do |val|
61
+ def format_values_for(predicate)
62
+ value_from(predicate).map do |val|
62
63
  val.is_a?(String) ? Arel::Nodes.build_quoted(val) : val
63
64
  end
64
65
  end
@@ -34,7 +34,8 @@ module Ransack
34
34
  :down_arrow => '▲'.freeze,
35
35
  :default_arrow => nil,
36
36
  :sanitize_scope_args => true,
37
- :postgres_fields_sort_option => nil
37
+ :postgres_fields_sort_option => nil,
38
+ :strip_whitespace => true
38
39
  }
39
40
 
40
41
  def configure
@@ -148,7 +149,7 @@ module Ransack
148
149
  # User may want to configure it like this:
149
150
  #
150
151
  # Ransack.configure do |c|
151
- # c.postgres_fields_sort_option = :nulls_first # or :nulls_last
152
+ # c.postgres_fields_sort_option = :nulls_first # or e.g. :nulls_always_last
152
153
  # end
153
154
  #
154
155
  # See this feature: https://www.postgresql.org/docs/13/queries-order.html
@@ -170,6 +171,19 @@ module Ransack
170
171
  self.options[:hide_sort_order_indicators] = boolean
171
172
  end
172
173
 
174
+ # By default, Ransack displays strips all whitespace when searching for a string.
175
+ # The default may be globally changed in an initializer file like
176
+ # `config/initializers/ransack.rb` as follows:
177
+ #
178
+ # Ransack.configure do |config|
179
+ # # Enable whitespace stripping for string searches
180
+ # config.strip_whitespace = true
181
+ # end
182
+ #
183
+ def strip_whitespace=(boolean)
184
+ self.options[:strip_whitespace] = boolean
185
+ end
186
+
173
187
  def arel_predicate_with_suffix(arel_predicate, suffix)
174
188
  if arel_predicate === Proc
175
189
  proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
@@ -130,12 +130,20 @@ module Ransack
130
130
 
131
131
  def url_options
132
132
  @params.merge(
133
- @options.merge(
133
+ @options.except(:class).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
@@ -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"
@@ -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)
@@ -15,10 +15,11 @@ module Ransack
15
15
  :translate, :to => :base
16
16
 
17
17
  def initialize(object, params = {}, options = {})
18
+ strip_whitespace = options.fetch(:strip_whitespace, Ransack.options[:strip_whitespace])
18
19
  params = params.to_unsafe_h if params.respond_to?(:to_unsafe_h)
19
20
  if params.is_a? Hash
20
21
  params = params.dup
21
- params = params.transform_values { |v| v.is_a?(String) ? v.strip : v }
22
+ params = params.transform_values { |v| v.is_a?(String) && strip_whitespace ? v.strip : v }
22
23
  params.delete_if { |k, v| [*v].all?{ |i| i.blank? && i != false } }
23
24
  else
24
25
  params = {}
@@ -42,10 +43,10 @@ module Ransack
42
43
  collapse_multiparameter_attributes!(params).each do |key, value|
43
44
  if ['s'.freeze, 'sorts'.freeze].freeze.include?(key)
44
45
  send("#{key}=", value)
45
- elsif base.attribute_method?(key)
46
- base.send("#{key}=", value)
47
46
  elsif @context.ransackable_scope?(key, @context.object)
48
47
  add_scope(key, value)
48
+ elsif base.attribute_method?(key)
49
+ base.send("#{key}=", value)
49
50
  elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions
50
51
  raise ArgumentError, "Invalid search term #{key}"
51
52
  end
@@ -32,6 +32,7 @@ module Ransack
32
32
  defaults = base_ancestors.map do |klass|
33
33
  "ransack.attributes.#{i18n_key(klass)}.#{original_name}".to_sym
34
34
  end
35
+ defaults << options.delete(:default) if options[:default]
35
36
 
36
37
  translated_names = attribute_names.map do |name|
37
38
  attribute_name(context, name, options[:include_associations])
@@ -48,7 +49,6 @@ module Ransack
48
49
  defaults << "%{attributes}".freeze
49
50
  end
50
51
 
51
- defaults << options.delete(:default) if options[:default]
52
52
  options.reverse_merge! count: 1, default: defaults
53
53
  I18n.translate(defaults.shift, **options.merge(interpolations))
54
54
  end
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '2.4.2'
2
+ VERSION = '3.0.0'
3
3
  end
data/ransack.gemspec CHANGED
@@ -7,16 +7,16 @@ Gem::Specification.new do |s|
7
7
  s.name = "ransack"
8
8
  s.version = Ransack::VERSION
9
9
  s.platform = Gem::Platform::RUBY
10
- s.authors = ["Ernie Miller", "Ryan Bigg", "Jon Atack", "Sean Carroll"]
10
+ s.authors = ["Ernie Miller", "Ryan Bigg", "Jon Atack", "Sean Carroll", "David Rodríguez"]
11
11
  s.email = ["ernie@erniemiller.org", "radarlistener@gmail.com", "jonnyatack@gmail.com", "sfcarroll@gmail.com"]
12
12
  s.homepage = "https://github.com/activerecord-hackery/ransack"
13
- s.summary = %q{Object-based searching for Active Record and Mongoid (currently).}
13
+ s.summary = %q{Object-based searching for Active Record.}
14
14
  s.description = %q{Ransack is the successor to the MetaSearch gem. It improves and expands upon MetaSearch's functionality, but does not have a 100%-compatible API.}
15
15
  s.required_ruby_version = '>= 2.6'
16
16
  s.license = 'MIT'
17
17
 
18
- s.add_dependency 'activerecord', '>= 5.2.4'
19
- s.add_dependency 'activesupport', '>= 5.2.4'
18
+ s.add_dependency 'activerecord', '>= 6.0.4'
19
+ s.add_dependency 'activesupport', '>= 6.0.4'
20
20
  s.add_dependency 'i18n'
21
21
 
22
22
  s.files = `git ls-files`.split("\n")
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ module Polyamorous
4
+ describe "ActiveRecord Compatibility" do
5
+ it 'works with self joins and includes' do
6
+ trade_account = Account.create!
7
+ Account.create!(trade_account: trade_account)
8
+
9
+ accounts = Account.joins(:trade_account).includes(:trade_account, :agent_account)
10
+ account = accounts.first
11
+
12
+ expect(account.agent_account).to be_nil
13
+ end
14
+ end
15
+ end
@@ -12,12 +12,7 @@ module Polyamorous
12
12
 
13
13
  subject { new_join_association(reflection, parent.children, Person) }
14
14
 
15
- it 'respects polymorphism on equality test' do
16
- expect(subject).to eq new_join_association(reflection, parent.children, Person)
17
- expect(subject).not_to eq new_join_association(reflection, parent.children, Article)
18
- end
19
-
20
- it 'leaves the orginal reflection intact for thread safety' do
15
+ it 'leaves the original reflection intact for thread safety' do
21
16
  reflection.instance_variable_set(:@klass, Article)
22
17
  join_association
23
18
  .swapping_reflection_klass(reflection, Person) do |new_reflection|
@@ -77,21 +77,5 @@ module Polyamorous
77
77
  specify { expect(subject.send(:join_root).drop(1)[1].table_name)
78
78
  .to eq 'comments' }
79
79
  end
80
-
81
- context '#left_outer_join in Rails 5 overrides join type specified',
82
- if: ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MAJOR < 6 && ActiveRecord::VERSION::MINOR < 2 do
83
-
84
- let(:join_type_class) do
85
- new_join_dependency(
86
- Person,
87
- new_join(:articles)
88
- ).join_constraints(
89
- [],
90
- Arel::Nodes::OuterJoin
91
- ).first.joins.map(&:class)
92
- end
93
-
94
- specify { expect(join_type_class).to eq [Arel::Nodes::OuterJoin] }
95
- end
96
80
  end
97
81
  end
@@ -8,7 +8,6 @@ module Ransack
8
8
  subject { ::ActiveRecord::Base }
9
9
 
10
10
  it { should respond_to :ransack }
11
- it { should respond_to :search }
12
11
 
13
12
  describe '#search' do
14
13
  subject { Person.ransack }
@@ -44,12 +43,12 @@ module Ransack
44
43
 
45
44
  it 'applies stringy boolean scopes with true value in an array' do
46
45
  s = Person.ransack('of_age' => ['true'])
47
- expect(s.result.to_sql).to (include 'age >= 18')
46
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{(age >= '18')} : 'age >= 18')
48
47
  end
49
48
 
50
49
  it 'applies stringy boolean scopes with false value in an array' do
51
50
  s = Person.ransack('of_age' => ['false'])
52
- expect(s.result.to_sql).to (include 'age < 18')
51
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age < '18'} : 'age < 18')
53
52
  end
54
53
 
55
54
  it 'ignores unlisted scopes' do
@@ -69,15 +68,25 @@ module Ransack
69
68
 
70
69
  it 'passes values to scopes' do
71
70
  s = Person.ransack('over_age' => 18)
72
- expect(s.result.to_sql).to (include 'age > 18')
71
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
73
72
  end
74
73
 
75
74
  it 'chains scopes' do
76
75
  s = Person.ransack('over_age' => 18, 'active' => true)
77
- expect(s.result.to_sql).to (include 'age > 18')
76
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '18'} : 'age > 18')
78
77
  expect(s.result.to_sql).to (include 'active = 1')
79
78
  end
80
79
 
80
+ it 'applies scopes that define string SQL joins' do
81
+ allow(Article)
82
+ .to receive(:ransackable_scopes)
83
+ .and_return([:latest_comment_cont])
84
+
85
+ # Including a negative condition to test removing the scope
86
+ s = Search.new(Article, notes_note_not_eq: 'Test', latest_comment_cont: 'Test')
87
+ expect(s.result.to_sql).to include 'latest_comment'
88
+ end
89
+
81
90
  context "with sanitize_custom_scope_booleans set to false" do
82
91
  before(:all) do
83
92
  Ransack.configure { |c| c.sanitize_custom_scope_booleans = false }
@@ -89,12 +98,12 @@ module Ransack
89
98
 
90
99
  it 'passes true values to scopes' do
91
100
  s = Person.ransack('over_age' => 1)
92
- expect(s.result.to_sql).to (include 'age > 1')
101
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
93
102
  end
94
103
 
95
104
  it 'passes false values to scopes' do
96
105
  s = Person.ransack('over_age' => 0)
97
- expect(s.result.to_sql).to (include 'age > 0')
106
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
98
107
  end
99
108
  end
100
109
 
@@ -107,12 +116,12 @@ module Ransack
107
116
 
108
117
  it 'passes true values to scopes' do
109
118
  s = Person.ransack('over_age' => 1)
110
- expect(s.result.to_sql).to (include 'age > 1')
119
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '1'} : 'age > 1')
111
120
  end
112
121
 
113
122
  it 'passes false values to scopes' do
114
123
  s = Person.ransack('over_age' => 0)
115
- expect(s.result.to_sql).to (include 'age > 0')
124
+ expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
116
125
  end
117
126
  end
118
127
 
@@ -123,7 +132,7 @@ module Ransack
123
132
  end
124
133
 
125
134
  it 'raises exception if ransack! called with unknown condition' do
126
- expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error
135
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
127
136
  end
128
137
 
129
138
  it 'does not modify the parameters' do
@@ -314,7 +323,11 @@ module Ransack
314
323
  end
315
324
 
316
325
  it 'should function correctly with a multi-parameter attribute' do
317
- ::ActiveRecord::Base.default_timezone = :utc
326
+ if ::ActiveRecord::VERSION::MAJOR >= 7
327
+ ::ActiveRecord.default_timezone = :utc
328
+ else
329
+ ::ActiveRecord::Base.default_timezone = :utc
330
+ end
318
331
  Time.zone = 'UTC'
319
332
 
320
333
  date = Date.current
@@ -690,6 +703,10 @@ module Ransack
690
703
  it { should eq [] }
691
704
  end
692
705
 
706
+ private
707
+ def rails7_and_mysql
708
+ ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
709
+ end
693
710
  end
694
711
  end
695
712
  end
@@ -45,6 +45,20 @@ module Ransack
45
45
  Ransack.options = default
46
46
  end
47
47
 
48
+ it 'should have default value for strip_whitespace' do
49
+ expect(Ransack.options[:strip_whitespace]).to eq true
50
+ end
51
+
52
+ it 'changes default search key parameter' do
53
+ default = Ransack.options.clone
54
+
55
+ Ransack.configure { |c| c.strip_whitespace = false }
56
+
57
+ expect(Ransack.options[:strip_whitespace]).to eq false
58
+
59
+ Ransack.options = default
60
+ end
61
+
48
62
  it 'should have default values for arrows' do
49
63
  expect(Ransack.options[:up_arrow]).to eq '&#9660;'
50
64
  expect(Ransack.options[:down_arrow]).to eq '&#9650;'
@@ -469,8 +469,7 @@ module Ransack
469
469
  it { should match /exist\=existing/ }
470
470
  end
471
471
 
472
- context 'using a real ActionController::Parameter object',
473
- if: ::ActiveRecord::VERSION::MAJOR > 3 do
472
+ context 'using a real ActionController::Parameter object' do
474
473
 
475
474
  describe 'with symbol q:, #sort_link should include search params' do
476
475
  subject { @controller.view_context.sort_link(Person.ransack, :name) }
@@ -727,6 +726,38 @@ module Ransack
727
726
  it { should match /Block label&nbsp;&#9660;/ }
728
727
  end
729
728
 
729
+ describe '#sort_link with class option' do
730
+ subject { @controller.view_context
731
+ .sort_link(
732
+ [:main_app, Person.ransack(sorts: ['name desc'])],
733
+ :name,
734
+ class: 'people', controller: 'people'
735
+ )
736
+ }
737
+ it { should match /class="sort_link desc people"/ }
738
+ it { should_not match /people\?class=people/ }
739
+ end
740
+
741
+ describe '#sort_link with class option workaround' do
742
+ it "generates a correct link and prints a deprecation" do
743
+ expect do
744
+ link = @controller.view_context
745
+ .sort_link(
746
+ [:main_app, Person.ransack(sorts: ['name desc'])],
747
+ :name,
748
+ 'name',
749
+ { controller: 'people' },
750
+ class: 'people'
751
+ )
752
+
753
+ expect(link).to match(/class="sort_link desc people"/)
754
+ expect(link).not_to match(/people\?class=people/)
755
+ end.to output(
756
+ /Passing two trailing hashes to `sort_link` is deprecated, merge the trailing hashes into a single one\. \(called at #{Regexp.escape(__FILE__)}:/
757
+ ).to_stderr
758
+ end
759
+ end
760
+
730
761
  describe '#search_form_for with default format' do
731
762
  subject { @controller.view_context
732
763
  .search_form_for(Person.ransack) {} }
@@ -3,6 +3,19 @@ require 'spec_helper'
3
3
  module Ransack
4
4
  module Nodes
5
5
  describe Condition do
6
+ context 'bug report #1245' do
7
+ it 'preserves tuple behavior' do
8
+ ransack_hash = {
9
+ m: 'and',
10
+ g: [
11
+ { title_type_in: ['["title 1", ""]'] }
12
+ ]
13
+ }
14
+
15
+ sql = Article.ransack(ransack_hash).result.to_sql
16
+ expect(sql).to include("IN (('title 1', ''))")
17
+ end
18
+ end
6
19
 
7
20
  context 'with an alias' do
8
21
  subject {