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
@@ -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,9 +49,8 @@ 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
- I18n.translate(defaults.shift, options.merge(interpolations))
53
+ I18n.translate(defaults.shift, **options.merge(interpolations))
54
54
  end
55
55
 
56
56
  def association(key, options = {})
@@ -66,8 +66,8 @@ module Ransack
66
66
  [:"ransack.associations.#{i18n_key(context.klass)}.#{key}"]
67
67
  end
68
68
  defaults << context.traverse(key).model_name.human
69
- options = { :count => 1, :default => defaults }
70
- I18n.translate(defaults.shift, options)
69
+ options = { count: 1, default: defaults }
70
+ I18n.translate(defaults.shift, **options)
71
71
  end
72
72
 
73
73
  private
@@ -83,7 +83,7 @@ module Ransack
83
83
  options = { count: 1, default: defaults }
84
84
  interpolations = build_interpolations(associated_class)
85
85
 
86
- I18n.translate(defaults.shift, options.merge(interpolations))
86
+ I18n.translate(defaults.shift, **options.merge(interpolations))
87
87
  end
88
88
 
89
89
  def default_attribute_name
@@ -149,7 +149,7 @@ module Ransack
149
149
  end
150
150
 
151
151
  def i18n_key(klass)
152
- raise "not implemented"
152
+ klass.model_name.i18n_key
153
153
  end
154
154
  end
155
155
  end
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '2.3.2'
2
+ VERSION = '4.1.1'
3
3
  end
@@ -26,7 +26,14 @@ module Ransack
26
26
  end
27
27
 
28
28
  def visit_and(object)
29
- raise "not implemented"
29
+ nodes = object.values.map { |o| accept(o) }.compact
30
+ return nil unless nodes.size > 0
31
+
32
+ if nodes.size > 1
33
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes))
34
+ else
35
+ nodes.first
36
+ end
30
37
  end
31
38
 
32
39
  def visit_or(object)
@@ -35,17 +42,46 @@ module Ransack
35
42
  end
36
43
 
37
44
  def quoted?(object)
38
- raise "not implemented"
45
+ case object
46
+ when Arel::Nodes::SqlLiteral, Bignum, Fixnum
47
+ false
48
+ else
49
+ true
50
+ end
39
51
  end
40
52
 
41
53
  def visit(object)
42
54
  send(DISPATCH[object.class], object)
43
55
  end
44
56
 
57
+ def visit_Ransack_Nodes_Sort(object)
58
+ if object.valid?
59
+ if object.attr.is_a?(Arel::Attributes::Attribute)
60
+ object.attr.send(object.dir)
61
+ else
62
+ ordered(object)
63
+ end
64
+ else
65
+ scope_name = :"sort_by_#{object.name}_#{object.dir}"
66
+ scope_name if object.context.object.respond_to?(scope_name)
67
+ end
68
+ end
69
+
45
70
  DISPATCH = Hash.new do |hash, klass|
46
71
  hash[klass] = "visit_#{
47
72
  klass.name.gsub(Constants::TWO_COLONS, Constants::UNDERSCORE)
48
73
  }"
49
74
  end
75
+
76
+ private
77
+
78
+ def ordered(object)
79
+ case object.dir
80
+ when 'asc'.freeze
81
+ Arel::Nodes::Ascending.new(object.attr)
82
+ when 'desc'.freeze
83
+ Arel::Nodes::Descending.new(object.attr)
84
+ end
85
+ end
50
86
  end
51
87
  end
data/lib/ransack.rb CHANGED
@@ -1,18 +1,15 @@
1
1
  require 'active_support/core_ext'
2
2
  require 'ransack/configuration'
3
- require 'ransack/adapters'
4
- require 'polyamorous'
5
-
6
- Ransack::Adapters.object_mapper.require_constants
3
+ require 'polyamorous/polyamorous'
7
4
 
8
5
  module Ransack
9
6
  extend Configuration
10
- class UntraversableAssociationError < StandardError; end;
7
+ class UntraversableAssociationError < StandardError; end
11
8
  end
12
9
 
13
10
  Ransack.configure do |config|
14
11
  Ransack::Constants::AREL_PREDICATES.each do |name|
15
- config.add_predicate name, :arel_predicate => name
12
+ config.add_predicate name, arel_predicate: name
16
13
  end
17
14
  Ransack::Constants::DERIVED_PREDICATES.each do |args|
18
15
  config.add_predicate(*args)
@@ -22,8 +19,8 @@ end
22
19
  require 'ransack/search'
23
20
  require 'ransack/ransacker'
24
21
  require 'ransack/translate'
25
-
26
- Ransack::Adapters.object_mapper.require_adapter
22
+ require 'ransack/active_record'
23
+ require 'ransack/context'
27
24
 
28
25
  ActiveSupport.on_load(:action_controller) do
29
26
  require 'ransack/helpers'
data/ransack.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
+
2
3
  $:.push File.expand_path("../lib", __FILE__)
3
4
  require "ransack/version"
4
5
 
@@ -6,27 +7,20 @@ Gem::Specification.new do |s|
6
7
  s.name = "ransack"
7
8
  s.version = Ransack::VERSION
8
9
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Ernie Miller", "Ryan Bigg", "Jon Atack","Sean Carroll"]
10
- s.email = ["ernie@erniemiller.org", "radarlistener@gmail.com", "jonnyatack@gmail.com","sfcarroll@gmail.com"]
10
+ s.authors = ["Ernie Miller", "Ryan Bigg", "Jon Atack", "Sean Carroll", "David Rodríguez"]
11
+ s.email = ["ernie@erniemiller.org", "radarlistener@gmail.com", "jonnyatack@gmail.com", "sfcarroll@gmail.com"]
11
12
  s.homepage = "https://github.com/activerecord-hackery/ransack"
12
- s.summary = %q{Object-based searching for Active Record and Mongoid (currently).}
13
+ s.summary = %q{Object-based searching for Active Record.}
13
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.}
14
- s.required_ruby_version = '>= 2.3'
15
+ s.required_ruby_version = '>= 3.0'
15
16
  s.license = 'MIT'
16
17
 
17
- s.add_dependency 'activerecord', '>= 5.2.1'
18
- s.add_dependency 'activesupport', '>= 5.2.1'
18
+ s.add_dependency 'activerecord', '>= 6.1.5'
19
+ s.add_dependency 'activesupport', '>= 6.1.5'
19
20
  s.add_dependency 'i18n'
20
- s.add_dependency 'polyamorous', Ransack::VERSION.to_s
21
21
 
22
22
  s.files = `git ls-files`.split("\n")
23
-
24
- s.test_files = `git ls-files -- {test,spec,features}/*`
25
- .split("\n")
26
-
27
- s.executables = `git ls-files -- bin/*`
28
- .split("\n")
29
- .map { |f| File.basename(f) }
30
-
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
31
25
  s.require_paths = ["lib"]
32
26
  end
@@ -2,4 +2,4 @@ Article.blueprint do
2
2
  person
3
3
  title
4
4
  body
5
- end
5
+ end
@@ -2,4 +2,4 @@ Comment.blueprint do
2
2
  article
3
3
  person
4
4
  body
5
- end
5
+ end
@@ -2,4 +2,4 @@ Note.blueprint do
2
2
  note
3
3
  notable_type { "Article" }
4
4
  notable_id
5
- end
5
+ end
@@ -1,3 +1,3 @@
1
1
  Tag.blueprint do
2
2
  name { Sham.tag_name }
3
- end
3
+ end
data/spec/console.rb CHANGED
@@ -14,11 +14,11 @@ Sham.define do
14
14
  title { Faker::Lorem.sentence }
15
15
  body { Faker::Lorem.paragraph }
16
16
  salary { |index| 30000 + (index * 1000) }
17
- tag_name { Faker::Lorem.words(3).join(' ') }
18
- note { Faker::Lorem.words(7).join(' ') }
19
- only_admin { Faker::Lorem.words(3).join(' ') }
20
- only_search { Faker::Lorem.words(3).join(' ') }
21
- only_sort { Faker::Lorem.words(3).join(' ') }
17
+ tag_name { Faker::Lorem.words(number: 3).join(' ') }
18
+ note { Faker::Lorem.words(number: 7).join(' ') }
19
+ only_admin { Faker::Lorem.words(number: 3).join(' ') }
20
+ only_search { Faker::Lorem.words(number: 3).join(' ') }
21
+ only_sort { Faker::Lorem.words(number: 3).join(' ') }
22
22
  notable_id { |id| id }
23
23
  end
24
24
 
@@ -3,14 +3,8 @@ module PolyamorousHelper
3
3
  Polyamorous::JoinAssociation.new reflection, children, klass
4
4
  end
5
5
 
6
- if ActiveRecord.version >= ::Gem::Version.new("6.0.0.rc1")
7
- def new_join_dependency(klass, associations = {})
8
- Polyamorous::JoinDependency.new klass, klass.arel_table, associations, Polyamorous::InnerJoin
9
- end
10
- else
11
- def new_join_dependency(klass, associations = {})
12
- Polyamorous::JoinDependency.new klass, klass.arel_table, associations
13
- end
6
+ def new_join_dependency(klass, associations = {})
7
+ Polyamorous::JoinDependency.new klass, klass.arel_table, associations, Polyamorous::InnerJoin
14
8
  end
15
9
 
16
10
  def new_join(name, type = Polyamorous::InnerJoin, klass = nil)
@@ -6,4 +6,4 @@ module RansackHelper
6
6
  def quote_column_name(column)
7
7
  ActiveRecord::Base.connection.quote_column_name(column)
8
8
  end
9
- end
9
+ end
@@ -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
@@ -10,7 +10,9 @@ module Polyamorous
10
10
  new_join_association(reflection, parent.children, Article)
11
11
  }
12
12
 
13
- it 'leaves the orginal reflection intact for thread safety' do
13
+ subject { new_join_association(reflection, parent.children, Person) }
14
+
15
+ it 'leaves the original reflection intact for thread safety' do
14
16
  reflection.instance_variable_set(:@klass, Article)
15
17
  join_association
16
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
 
@@ -122,12 +131,32 @@ module Ransack
122
131
  expect { Person.ransack('') }.to_not raise_error
123
132
  end
124
133
 
134
+ it 'raises exception if ransack! called with unknown condition' do
135
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
136
+ end
137
+
125
138
  it 'does not modify the parameters' do
126
139
  params = { name_eq: '' }
127
140
  expect { Person.ransack(params) }.not_to change { params }
128
141
  end
129
142
  end
130
143
 
144
+ context 'has_one through associations' do
145
+ let(:address) { Address.create!(city: 'Boston') }
146
+ let(:org) { Organization.create!(name: 'Testorg', address: address) }
147
+ let!(:employee) { Employee.create!(name: 'Ernie', organization: org) }
148
+
149
+ it 'works when has_one through association is first' do
150
+ s = Employee.ransack(address_city_eq: 'Boston', organization_name_eq: 'Testorg')
151
+ expect(s.result.to_a).to include(employee)
152
+ end
153
+
154
+ it 'works when has_one through association is last' do
155
+ s = Employee.ransack(organization_name_eq: 'Testorg', address_city_eq: 'Boston')
156
+ expect(s.result.to_a).to include(employee)
157
+ end
158
+ end
159
+
131
160
  context 'negative conditions on HABTM associations' do
132
161
  let(:medieval) { Tag.create!(name: 'Medieval') }
133
162
  let(:fantasy) { Tag.create!(name: 'Fantasy') }
@@ -143,14 +172,12 @@ module Ransack
143
172
  it 'removes redundant joins from top query' do
144
173
  s = Article.ransack(tags_name_not_eq: "Fantasy")
145
174
  sql = s.result.to_sql
146
-
147
175
  expect(sql).to_not include('LEFT OUTER JOIN')
148
176
  end
149
177
 
150
178
  it 'handles != for single values' do
151
179
  s = Article.ransack(tags_name_not_eq: "Fantasy")
152
180
  articles = s.result.to_a
153
-
154
181
  expect(articles).to include marco
155
182
  expect(articles).to_not include arthur
156
183
  end
@@ -267,10 +294,12 @@ module Ransack
267
294
  # end
268
295
 
269
296
  it 'creates ransack attributes' do
297
+ person = Person.create!(name: 'Aric Smith')
298
+
270
299
  s = Person.ransack(reversed_name_eq: 'htimS cirA')
271
300
  expect(s.result.size).to eq(1)
272
301
 
273
- expect(s.result.first).to eq Person.where(name: 'Aric Smith').first
302
+ expect(s.result.first).to eq person
274
303
  end
275
304
 
276
305
  it 'can be accessed through associations' do
@@ -310,7 +339,11 @@ module Ransack
310
339
  end
311
340
 
312
341
  it 'should function correctly with a multi-parameter attribute' do
313
- ::ActiveRecord::Base.default_timezone = :utc
342
+ if ::ActiveRecord::VERSION::MAJOR >= 7
343
+ ::ActiveRecord.default_timezone = :utc
344
+ else
345
+ ::ActiveRecord::Base.default_timezone = :utc
346
+ end
314
347
  Time.zone = 'UTC'
315
348
 
316
349
  date = Date.current
@@ -460,9 +493,9 @@ module Ransack
460
493
  Comment.create(article: Article.create(title: 'Avenger'), person: Person.create(salary: 100_000)),
461
494
  Comment.create(article: Article.create(title: 'Avenge'), person: Person.create(salary: 50_000)),
462
495
  ]
463
- expect(Comment.ransack(article_title_cont: 'aven',s: 'person_salary desc').result).to eq(comments)
496
+ expect(Comment.ransack(article_title_cont: 'aven', s: 'person_salary desc').result).to eq(comments)
464
497
  expect(Comment.joins(:person).ransack(s: 'persons_salarydesc', article_title_cont: 'aven').result).to eq(comments)
465
- expect(Comment.joins(:person).ransack(article_title_cont: 'aven',s: 'persons_salary desc').result).to eq(comments)
498
+ expect(Comment.joins(:person).ransack(article_title_cont: 'aven', s: 'persons_salary desc').result).to eq(comments)
466
499
  end
467
500
 
468
501
  it 'allows sort by `only_sort` field' do
@@ -541,7 +574,6 @@ module Ransack
541
574
  )
542
575
  end
543
576
 
544
-
545
577
  it 'should allow passing ransacker arguments to a ransacker' do
546
578
  s = Person.ransack(
547
579
  c: [{
@@ -641,6 +673,37 @@ module Ransack
641
673
  it { should_not include 'only_sort' }
642
674
  it { should include 'only_admin' }
643
675
  end
676
+
677
+ context 'when not defined in model, nor in ApplicationRecord' do
678
+ subject { Article.ransackable_attributes }
679
+
680
+ it "raises a helpful error" do
681
+ without_application_record_method(:ransackable_attributes) do
682
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article attributes explicitly allowlisted/)
683
+ end
684
+ end
685
+ end
686
+
687
+ context 'when defined only in model by delegating to super' do
688
+ subject { Article.ransackable_attributes }
689
+
690
+ around do |example|
691
+ Article.singleton_class.define_method(:ransackable_attributes) do
692
+ super(nil) - super(nil)
693
+ end
694
+
695
+ example.run
696
+ ensure
697
+ Article.singleton_class.remove_method(:ransackable_attributes)
698
+ end
699
+
700
+ it "returns the allowlist in the model, and warns" do
701
+ without_application_record_method(:ransackable_attributes) do
702
+ expect { subject }.to output(/Ransack's builtin `ransackable_attributes` method is deprecated/).to_stderr
703
+ expect(subject).to be_empty
704
+ end
705
+ end
706
+ end
644
707
  end
645
708
 
646
709
  describe '#ransortable_attributes' do
@@ -673,6 +736,37 @@ module Ransack
673
736
  it { should include 'parent' }
674
737
  it { should include 'children' }
675
738
  it { should include 'articles' }
739
+
740
+ context 'when not defined in model, nor in ApplicationRecord' do
741
+ subject { Article.ransackable_associations }
742
+
743
+ it "raises a helpful error" do
744
+ without_application_record_method(:ransackable_associations) do
745
+ expect { subject }.to raise_error(RuntimeError, /Ransack needs Article associations explicitly allowlisted/)
746
+ end
747
+ end
748
+ end
749
+
750
+ context 'when defined only in model by delegating to super' do
751
+ subject { Article.ransackable_associations }
752
+
753
+ around do |example|
754
+ Article.singleton_class.define_method(:ransackable_associations) do
755
+ super(nil) - super(nil)
756
+ end
757
+
758
+ example.run
759
+ ensure
760
+ Article.singleton_class.remove_method(:ransackable_associations)
761
+ end
762
+
763
+ it "returns the allowlist in the model, and warns" do
764
+ without_application_record_method(:ransackable_associations) do
765
+ expect { subject }.to output(/Ransack's builtin `ransackable_associations` method is deprecated/).to_stderr
766
+ expect(subject).to be_empty
767
+ end
768
+ end
769
+ end
676
770
  end
677
771
 
678
772
  describe '#ransackable_scopes' do
@@ -687,6 +781,21 @@ module Ransack
687
781
  it { should eq [] }
688
782
  end
689
783
 
784
+ private
785
+
786
+ def without_application_record_method(method)
787
+ ApplicationRecord.singleton_class.alias_method :"original_#{method}", :"#{method}"
788
+ ApplicationRecord.singleton_class.remove_method :"#{method}"
789
+
790
+ yield
791
+ ensure
792
+ ApplicationRecord.singleton_class.alias_method :"#{method}", :"original_#{method}"
793
+ ApplicationRecord.singleton_class.remove_method :"original_#{method}"
794
+ end
795
+
796
+ def rails7_and_mysql
797
+ ::ActiveRecord::VERSION::MAJOR >= 7 && ENV['DB'] == 'mysql'
798
+ end
690
799
  end
691
800
  end
692
801
  end
@@ -9,7 +9,6 @@ module Ransack
9
9
  describe Context do
10
10
  subject { Context.new(Person) }
11
11
 
12
-
13
12
  it 'has an Active Record alias tracker method' do
14
13
  expect(subject.alias_tracker)
15
14
  .to be_an ::ActiveRecord::Associations::AliasTracker
@@ -79,6 +78,25 @@ module Ransack
79
78
  expect(constraint.right.relation.name).to eql 'people'
80
79
  expect(constraint.right.name).to eql 'id'
81
80
  end
81
+
82
+ it 'build correlated subquery for multiple conditions (default scope)' do
83
+ search = Search.new(Person, { comments_body_not_eq: 'some_title' })
84
+
85
+ # Was
86
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
87
+ # SELECT "comments"."disabled" FROM "comments"
88
+ # WHERE "comments"."disabled" = "people"."id"
89
+ # AND NOT ("comments"."body" != 'some_title')
90
+ # ) ORDER BY "people"."id" DESC
91
+ # Should Be
92
+ # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
93
+ # SELECT "comments"."person_id" FROM "comments"
94
+ # WHERE "comments"."person_id" = "people"."id"
95
+ # AND NOT ("comments"."body" != 'some_title')
96
+ # ) ORDER BY "people"."id" DESC
97
+
98
+ expect(search.result.to_sql).to match /.comments.\..person_id. = .people.\..id./
99
+ end
82
100
  end
83
101
 
84
102
  describe 'sharing context across searches' do
@@ -91,23 +109,6 @@ module Ransack
91
109
  context: shared_context)
92
110
  end
93
111
 
94
- describe '#join_associations', if: AR_version <= '4.0' do
95
- it 'returns dependent join associations for all searches run
96
- against the context' do
97
- parents, children = shared_context.join_associations
98
-
99
- expect(children.aliased_table_name).to eq "children_people"
100
- expect(parents.aliased_table_name).to eq "parents_people"
101
- end
102
-
103
- it 'can be rejoined to execute a valid query' do
104
- parents, children = shared_context.join_associations
105
-
106
- expect { Person.joins(parents).joins(children).to_a }
107
- .to_not raise_error
108
- end
109
- end
110
-
111
112
  describe '#join_sources' do
112
113
  it 'returns dependent arel join nodes for all searches run against
113
114
  the context' do