ransack 4.1.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/lib/polyamorous/activerecord/join_association_7_2.rb +55 -0
  4. data/lib/polyamorous/polyamorous.rb +5 -1
  5. data/lib/ransack/adapters/active_record/context.rb +32 -5
  6. data/lib/ransack/constants.rb +1 -1
  7. data/lib/ransack/context.rb +7 -4
  8. data/lib/ransack/helpers/form_builder.rb +6 -7
  9. data/lib/ransack/helpers/form_helper.rb +86 -20
  10. data/lib/ransack/invalid_search_error.rb +3 -0
  11. data/lib/ransack/locale/ja.yml +51 -51
  12. data/lib/ransack/locale/ko.yml +70 -0
  13. data/lib/ransack/locale/uk.yml +72 -0
  14. data/lib/ransack/nodes/condition.rb +39 -7
  15. data/lib/ransack/nodes/grouping.rb +1 -1
  16. data/lib/ransack/nodes/sort.rb +1 -1
  17. data/lib/ransack/nodes/value.rb +9 -1
  18. data/lib/ransack/search.rb +4 -3
  19. data/lib/ransack/version.rb +1 -1
  20. data/lib/ransack.rb +9 -0
  21. data/spec/polyamorous/join_association_spec.rb +0 -1
  22. data/spec/polyamorous/join_dependency_spec.rb +0 -1
  23. data/spec/ransack/adapters/active_record/base_spec.rb +106 -3
  24. data/spec/ransack/adapters/active_record/context_spec.rb +72 -0
  25. data/spec/ransack/helpers/form_builder_spec.rb +0 -2
  26. data/spec/ransack/helpers/form_helper_spec.rb +219 -5
  27. data/spec/ransack/nodes/condition_spec.rb +230 -0
  28. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  29. data/spec/ransack/nodes/value_spec.rb +12 -1
  30. data/spec/ransack/predicate_spec.rb +16 -9
  31. data/spec/ransack/search_spec.rb +121 -1
  32. data/spec/ransack/translate_spec.rb +0 -1
  33. data/spec/spec_helper.rb +2 -3
  34. data/spec/support/schema.rb +42 -0
  35. metadata +17 -86
  36. data/.github/FUNDING.yml +0 -3
  37. data/.github/SECURITY.md +0 -12
  38. data/.github/workflows/codeql.yml +0 -72
  39. data/.github/workflows/cronjob.yml +0 -99
  40. data/.github/workflows/deploy.yml +0 -35
  41. data/.github/workflows/rubocop.yml +0 -20
  42. data/.github/workflows/test-deploy.yml +0 -29
  43. data/.github/workflows/test.yml +0 -131
  44. data/.gitignore +0 -7
  45. data/.nojekyll +0 -0
  46. data/.rubocop.yml +0 -50
  47. data/CHANGELOG.md +0 -1176
  48. data/CONTRIBUTING.md +0 -171
  49. data/Gemfile +0 -53
  50. data/Rakefile +0 -24
  51. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +0 -78
  52. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +0 -75
  53. data/docs/.gitignore +0 -19
  54. data/docs/.nojekyll +0 -0
  55. data/docs/babel.config.js +0 -3
  56. data/docs/blog/2022-03-27-ransack-3.0.0.md +0 -20
  57. data/docs/docs/getting-started/_category_.json +0 -4
  58. data/docs/docs/getting-started/advanced-mode.md +0 -46
  59. data/docs/docs/getting-started/configuration.md +0 -47
  60. data/docs/docs/getting-started/search-matches.md +0 -67
  61. data/docs/docs/getting-started/simple-mode.md +0 -288
  62. data/docs/docs/getting-started/sorting.md +0 -71
  63. data/docs/docs/getting-started/using-predicates.md +0 -282
  64. data/docs/docs/going-further/_category_.json +0 -4
  65. data/docs/docs/going-further/acts-as-taggable-on.md +0 -114
  66. data/docs/docs/going-further/associations.md +0 -70
  67. data/docs/docs/going-further/custom-predicates.md +0 -52
  68. data/docs/docs/going-further/documentation.md +0 -43
  69. data/docs/docs/going-further/exporting-to-csv.md +0 -49
  70. data/docs/docs/going-further/external-guides.md +0 -57
  71. data/docs/docs/going-further/form-customisation.md +0 -63
  72. data/docs/docs/going-further/i18n.md +0 -53
  73. data/docs/docs/going-further/img/create_release.png +0 -0
  74. data/docs/docs/going-further/merging-searches.md +0 -41
  75. data/docs/docs/going-further/other-notes.md +0 -428
  76. data/docs/docs/going-further/polymorphic-search.md +0 -46
  77. data/docs/docs/going-further/ransackers.md +0 -331
  78. data/docs/docs/going-further/release_process.md +0 -36
  79. data/docs/docs/going-further/saving-queries.md +0 -82
  80. data/docs/docs/going-further/searching-postgres.md +0 -57
  81. data/docs/docs/going-further/wiki-contributors.md +0 -82
  82. data/docs/docs/intro.md +0 -99
  83. data/docs/docusaurus.config.js +0 -120
  84. data/docs/package.json +0 -42
  85. data/docs/sidebars.js +0 -31
  86. data/docs/src/components/HomepageFeatures/index.js +0 -64
  87. data/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  88. data/docs/src/css/custom.css +0 -39
  89. data/docs/src/pages/index.module.css +0 -23
  90. data/docs/src/pages/markdown-page.md +0 -7
  91. data/docs/static/.nojekyll +0 -0
  92. data/docs/static/img/docusaurus.png +0 -0
  93. data/docs/static/img/favicon.ico +0 -0
  94. data/docs/static/img/logo.svg +0 -1
  95. data/docs/static/img/tutorial/docsVersionDropdown.png +0 -0
  96. data/docs/static/img/tutorial/localeDropdown.png +0 -0
  97. data/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  98. data/docs/static/img/undraw_docusaurus_react.svg +0 -170
  99. data/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  100. data/docs/static/logo/ransack-h.png +0 -0
  101. data/docs/static/logo/ransack-h.svg +0 -34
  102. data/docs/static/logo/ransack-v.png +0 -0
  103. data/docs/static/logo/ransack-v.svg +0 -34
  104. data/docs/static/logo/ransack.png +0 -0
  105. data/docs/static/logo/ransack.svg +0 -21
  106. data/docs/yarn.lock +0 -8879
  107. data/ransack.gemspec +0 -26
@@ -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
@@ -43,6 +43,14 @@ module Ransack
43
43
  end
44
44
  end
45
45
 
46
+ def cast_array
47
+ if value.is_a?(Array)
48
+ cast_to_date(value)
49
+ else
50
+ value
51
+ end
52
+ end
53
+
46
54
  def cast_to_date(val)
47
55
  if val.respond_to?(:to_date)
48
56
  val.to_date rescue nil
@@ -80,7 +88,7 @@ module Ransack
80
88
  end
81
89
 
82
90
  def cast_to_integer(val)
83
- val.blank? ? nil : val.to_i
91
+ val.respond_to?(:to_i) && !val.blank? ? val.to_i : nil
84
92
  end
85
93
 
86
94
  def cast_to_float(val)
@@ -7,6 +7,7 @@ require 'ransack/nodes/sort'
7
7
  require 'ransack/nodes/grouping'
8
8
  require 'ransack/context'
9
9
  require 'ransack/naming'
10
+ require 'ransack/invalid_search_error'
10
11
 
11
12
  module Ransack
12
13
  class Search
@@ -53,7 +54,7 @@ module Ransack
53
54
  elsif base.attribute_method?(key)
54
55
  base.send("#{key}=", value)
55
56
  elsif !Ransack.options[:ignore_unknown_conditions] || !@ignore_unknown_conditions
56
- raise ArgumentError, "Invalid search term #{key}"
57
+ raise InvalidSearchError, "Invalid search term #{key}"
57
58
  end
58
59
  end
59
60
  self
@@ -68,7 +69,7 @@ module Ransack
68
69
  else
69
70
  sort = Nodes::Sort.extract(@context, sort)
70
71
  end
71
- self.sorts << sort
72
+ self.sorts << sort if sort
72
73
  end
73
74
  when Hash
74
75
  args.each do |index, attrs|
@@ -78,7 +79,7 @@ module Ransack
78
79
  when String
79
80
  self.sorts = [args]
80
81
  else
81
- raise ArgumentError,
82
+ raise InvalidSearchError,
82
83
  "Invalid argument (#{args.class}) supplied to sorts="
83
84
  end
84
85
  end
@@ -1,3 +1,3 @@
1
1
  module Ransack
2
- VERSION = '4.1.1'
2
+ VERSION = '4.4.0'
3
3
  end
data/lib/ransack.rb CHANGED
@@ -1,3 +1,11 @@
1
+ require 'active_support/dependencies/autoload'
2
+ require 'active_support/deprecation'
3
+ require 'active_support/version'
4
+
5
+ if ::ActiveSupport.version >= ::Gem::Version.new("7.1")
6
+ require 'active_support/deprecator'
7
+ end
8
+
1
9
  require 'active_support/core_ext'
2
10
  require 'ransack/configuration'
3
11
  require 'polyamorous/polyamorous'
@@ -21,6 +29,7 @@ require 'ransack/ransacker'
21
29
  require 'ransack/translate'
22
30
  require 'ransack/active_record'
23
31
  require 'ransack/context'
32
+ require 'ransack/version'
24
33
 
25
34
  ActiveSupport.on_load(:action_controller) do
26
35
  require 'ransack/helpers'
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  module Polyamorous
4
4
  describe JoinAssociation do
5
-
6
5
  let(:join_dependency) { new_join_dependency Note, {} }
7
6
  let(:reflection) { Note.reflect_on_association(:notable) }
8
7
  let(:parent) { join_dependency.send(:join_root) }
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  module Polyamorous
4
4
  describe JoinDependency do
5
-
6
5
  context 'with symbol joins' do
7
6
  subject { new_join_dependency Person, articles: :comments }
8
7
 
@@ -4,7 +4,6 @@ module Ransack
4
4
  module Adapters
5
5
  module ActiveRecord
6
6
  describe Base do
7
-
8
7
  subject { ::ActiveRecord::Base }
9
8
 
10
9
  it { should respond_to :ransack }
@@ -124,17 +123,20 @@ module Ransack
124
123
  expect(s.result.to_sql).to (include rails7_and_mysql ? %q{age > '0'} : 'age > 0')
125
124
  end
126
125
  end
127
-
128
126
  end
129
127
 
130
128
  it 'does not raise exception for string :params argument' do
131
129
  expect { Person.ransack('') }.to_not raise_error
132
130
  end
133
131
 
134
- it 'raises exception if ransack! called with unknown condition' do
132
+ it 'raises ArgumentError exception if ransack! called with unknown condition' do
135
133
  expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(ArgumentError)
136
134
  end
137
135
 
136
+ it 'raises InvalidSearchError exception if ransack! called with unknown condition' do
137
+ expect { Person.ransack!(unknown_attr_eq: 'Ernie') }.to raise_error(InvalidSearchError)
138
+ end
139
+
138
140
  it 'does not modify the parameters' do
139
141
  params = { name_eq: '' }
140
142
  expect { Person.ransack(params) }.not_to change { params }
@@ -191,6 +193,41 @@ module Ransack
191
193
  end
192
194
  end
193
195
 
196
+ context 'negative conditions on related object with HABTM associations' do
197
+ let(:medieval) { Tag.create!(name: 'Medieval') }
198
+ let(:fantasy) { Tag.create!(name: 'Fantasy') }
199
+ let(:arthur) { Article.create!(title: 'King Arthur') }
200
+ let(:marco) { Article.create!(title: 'Marco Polo') }
201
+ let(:comment_arthur) { marco.comments.create!(body: 'King Arthur comment') }
202
+ let(:comment_marco) { arthur.comments.create!(body: 'Marco Polo comment') }
203
+
204
+ before do
205
+ comment_arthur.tags << medieval
206
+ comment_marco.tags << fantasy
207
+ end
208
+
209
+ it 'removes redundant joins from top query' do
210
+ s = Article.ransack(comments_tags_name_not_eq: "Fantasy")
211
+ sql = s.result.to_sql
212
+ expect(sql).to include('LEFT OUTER JOIN')
213
+ end
214
+
215
+ it 'handles != for single values' do
216
+ s = Article.ransack(comments_tags_name_not_eq: "Fantasy")
217
+ articles = s.result.to_a
218
+ expect(articles).to include marco
219
+ expect(articles).to_not include arthur
220
+ end
221
+
222
+ it 'handles NOT IN for multiple attributes' do
223
+ s = Article.ransack(comments_tags_name_not_in: ["Fantasy", "Scifi"])
224
+ articles = s.result.to_a
225
+
226
+ expect(articles).to include marco
227
+ expect(articles).to_not include arthur
228
+ end
229
+ end
230
+
194
231
  context 'negative conditions on self-referenced associations' do
195
232
  let(:pop) { Person.create!(name: 'Grandpa') }
196
233
  let(:dad) { Person.create!(name: 'Father') }
@@ -374,6 +411,63 @@ module Ransack
374
411
  expect(s.result.to_a).to eq [p]
375
412
  end
376
413
 
414
+ if ::ActiveRecord::VERSION::MAJOR >= 7 && ActiveRecord::Base.respond_to?(:normalizes)
415
+ context 'with ActiveRecord::normalizes' do
416
+ around(:each) do |example|
417
+ # Create a temporary model class with normalization for testing
418
+ test_class = Class.new(ActiveRecord::Base) do
419
+ self.table_name = 'people'
420
+ normalizes :name, with: ->(name) { name.gsub(/[^a-z0-9]/, '_') }
421
+
422
+ def self.ransackable_attributes(auth_object = nil)
423
+ Person.ransackable_attributes(auth_object)
424
+ end
425
+
426
+ def self.name
427
+ 'TestPersonWithNormalization'
428
+ end
429
+ end
430
+
431
+ stub_const('TestPersonWithNormalization', test_class)
432
+ example.run
433
+ end
434
+
435
+ it 'should not apply normalization to LIKE wildcards for cont predicate' do
436
+ # Create a person with characters that would be normalized
437
+ p = TestPersonWithNormalization.create!(name: 'foo%bar')
438
+ expect(p.reload.name).to eq('foo_bar') # Verify normalization happened on storage
439
+
440
+ # Search should find the person using the original search term
441
+ s = TestPersonWithNormalization.ransack(name_cont: 'foo')
442
+ expect(s.result.to_a).to eq [p]
443
+
444
+ # Verify the SQL contains proper LIKE wildcards, not normalized ones
445
+ sql = s.result.to_sql
446
+ expect(sql).to include("LIKE '%foo%'")
447
+ expect(sql).not_to include("LIKE '_foo_'")
448
+ end
449
+
450
+ it 'should not apply normalization to LIKE wildcards for other LIKE predicates' do
451
+ p = TestPersonWithNormalization.create!(name: 'foo%bar')
452
+
453
+ # Test start predicate
454
+ s = TestPersonWithNormalization.ransack(name_start: 'foo')
455
+ expect(s.result.to_a).to eq [p]
456
+ expect(s.result.to_sql).to include("LIKE 'foo%'")
457
+
458
+ # Test end predicate
459
+ s = TestPersonWithNormalization.ransack(name_end: 'bar')
460
+ expect(s.result.to_a).to eq [p]
461
+ expect(s.result.to_sql).to include("LIKE '%bar'")
462
+
463
+ # Test i_cont predicate
464
+ s = TestPersonWithNormalization.ransack(name_i_cont: 'FOO')
465
+ expect(s.result.to_a).to eq [p]
466
+ expect(s.result.to_sql).to include("LIKE '%foo%'")
467
+ end
468
+ end
469
+ end
470
+
377
471
  context 'searching by underscores' do
378
472
  # when escaping is supported right in LIKE expression without adding extra expressions
379
473
  def self.simple_escaping?
@@ -408,6 +502,15 @@ module Ransack
408
502
  expect(s.result.map(&:id)).to eq [3, 2, 1]
409
503
  end
410
504
 
505
+ it 'should function correctly with HABTM associations' do
506
+ article = Article.first
507
+ tag = article.tags.first
508
+ s = Person.ransack(article_tags_in: [tag.id])
509
+
510
+ expect(s.result.count).to be 1
511
+ expect(s.result.map(&:id)).to eq [article.person.id]
512
+ end
513
+
411
514
  it 'should function correctly when passing an array of strings' do
412
515
  a, b = Person.select(:id).order(:id).limit(2).map { |a| a.id.to_s }
413
516
 
@@ -97,6 +97,62 @@ module Ransack
97
97
 
98
98
  expect(search.result.to_sql).to match /.comments.\..person_id. = .people.\..id./
99
99
  end
100
+
101
+ it 'handles Arel::Nodes::And with children' do
102
+ # Create a mock Arel::Nodes::And with children for testing
103
+ search = Search.new(Person, { articles_title_not_eq: 'some_title', articles_body_not_eq: 'some_body' }, context: subject)
104
+ attribute = search.conditions.first.attributes.first
105
+ constraints = subject.build_correlated_subquery(attribute.parent).constraints
106
+ constraint = constraints.first
107
+
108
+ expect(constraints.length).to eql 1
109
+ expect(constraint.left.name).to eql 'person_id'
110
+ expect(constraint.left.relation.name).to eql 'articles'
111
+ expect(constraint.right.name).to eql 'id'
112
+ expect(constraint.right.relation.name).to eql 'people'
113
+ end
114
+
115
+ it 'correctly extracts correlated key from complex AND conditions' do
116
+ # Test with multiple nested conditions to ensure the children traversal works
117
+ search = Search.new(
118
+ Person,
119
+ {
120
+ articles_title_not_eq: 'title',
121
+ articles_body_not_eq: 'body',
122
+ articles_published_eq: true
123
+ },
124
+ context: subject
125
+ )
126
+
127
+ attribute = search.conditions.first.attributes.first
128
+ constraints = subject.build_correlated_subquery(attribute.parent).constraints
129
+ constraint = constraints.first
130
+
131
+ expect(constraints.length).to eql 1
132
+ expect(constraint.left.relation.name).to eql 'articles'
133
+ expect(constraint.left.name).to eql 'person_id'
134
+ expect(constraint.right.relation.name).to eql 'people'
135
+ expect(constraint.right.name).to eql 'id'
136
+ end
137
+
138
+ it 'build correlated subquery for polymorphic & default_scope when predicate is not_cont_all' do
139
+ search = Search.new(Article,
140
+ g: [
141
+ {
142
+ m: "and",
143
+ c: [
144
+ {
145
+ a: ["recent_notes_note"],
146
+ p: "not_eq",
147
+ v: ["some_note"],
148
+ }
149
+ ]
150
+ }
151
+ ],
152
+ )
153
+
154
+ expect(search.result.to_sql).to match /(.notes.\..note. != \'some_note\')/
155
+ end
100
156
  end
101
157
 
102
158
  describe 'sharing context across searches' do
@@ -141,6 +197,22 @@ module Ransack
141
197
  expect(attribute.relation.table_alias).to be_nil
142
198
  end
143
199
 
200
+ describe '#type_for' do
201
+ it 'returns nil when column does not exist instead of raising NoMethodError' do
202
+ # Create a mock attribute that references a non-existent column
203
+ mock_attr = double('attribute')
204
+ allow(mock_attr).to receive(:valid?).and_return(true)
205
+
206
+ mock_arel_attr = double('arel_attribute')
207
+ allow(mock_arel_attr).to receive(:relation).and_return(Person.arel_table)
208
+ allow(mock_arel_attr).to receive(:name).and_return('nonexistent_column')
209
+ allow(mock_attr).to receive(:arel_attribute).and_return(mock_arel_attr)
210
+ allow(mock_attr).to receive(:klass).and_return(Person)
211
+
212
+ # This should return nil instead of raising an error
213
+ expect(subject.type_for(mock_attr)).to be_nil
214
+ end
215
+ end
144
216
  end
145
217
  end
146
218
  end
@@ -3,7 +3,6 @@ require 'spec_helper'
3
3
  module Ransack
4
4
  module Helpers
5
5
  describe FormBuilder do
6
-
7
6
  router = ActionDispatch::Routing::RouteSet.new
8
7
  router.draw do
9
8
  resources :people, :comments, :notes
@@ -165,7 +164,6 @@ module Ransack
165
164
  def date_select_html(val)
166
165
  %(<option value="#{val}" selected="selected">#{val}</option>)
167
166
  end
168
-
169
167
  end
170
168
  end
171
169
  end