scoped_search 4.1.13 → 4.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbd1e857e3b248d6d585ac7c41679b536949dc945f4c7e067b2b16e86ce325b2
4
- data.tar.gz: b4c1dc92102ff1d56b1f23406b16cf0523c6bdc17ab336ee92300993de5fd7f9
3
+ metadata.gz: bdc9b9ee45ee99807b1c22a0da4cef71c6ce539292213ee47c012c020f866368
4
+ data.tar.gz: 5b4b0acd6e86a3f3e3c940f537a854c193983ebf57019687eac6bf5b33728b42
5
5
  SHA512:
6
- metadata.gz: 1fe965444b5640251c4ca34c5fc1fca34857f2c8cc96a8f759096165391490ca7deb7f5b3f9d8ed8c9219a5ddfa82716660e2739adb5d7dfdb2d61ca95a968a7
7
- data.tar.gz: f9ce7c87b95ceec5f3f5d1cc46f6401ef121870bf58a8cffe1b07cb7de597b8bcd3bcd1069e98ef52007df2209b26998f3f1c05a9719eefe17351d1f6da1eebf
6
+ metadata.gz: 395e4bf94443d76d67c642fe27f92eaf14b2d240b23c5f784e412cfec811cb1900041ea04388ea4c086a2184cb1c771c6f1f4bf6a1ede7a8065920c2fbb7ab41
7
+ data.tar.gz: 3662b759dde0bb5c9584de7e9fc2cfdf22ae2244c6889f1c88b343950ad5ff0b8ad51b2b07505e05d0eb59a51efbb887ef6dabd80343fbceb99859528f8c6cb0
data/CHANGELOG.rdoc CHANGED
@@ -6,6 +6,13 @@ Please add an entry to the "Unreleased changes" section in your pull requests.
6
6
 
7
7
  === Unreleased changes
8
8
 
9
+ === Version 4.3.0
10
+
11
+ - Prevent scoped_search from modifying an empty string on newer rubies (#229)
12
+ - Autocomplete now wraps all strings in quotes, not just those containing whitespace (#230)
13
+ - UUID search is now case insensitive (#232)
14
+ - Autocomplete now honors scopes set on associations (#231)
15
+
9
16
  === Version 4.1.10
10
17
 
11
18
  - Fix querying through associations in Rails 6.1 (undefined method join_keys) (#201)
@@ -215,12 +215,18 @@ module ScopedSearch
215
215
  .distinct
216
216
  .map(&field.field)
217
217
  .compact
218
- .map { |v| v.to_s =~ /\s/ ? "\"#{v.gsub('"', '\"')}\"" : v }
218
+ .map { |v| v.is_a?(String) ? "\"#{v.gsub('"', '\"')}\"" : v }
219
219
  end
220
220
 
221
221
  def completer_scope(field)
222
- klass = field.klass
223
- scope = klass.respond_to?(:completer_scope) ? klass.completer_scope(@options) : klass
222
+ scope = field.klass
223
+ scope = scope.completer_scope(@options) if scope.respond_to?(:completer_scope)
224
+
225
+ if field.klass != field.definition.klass
226
+ reflection = field.definition.reflection_by_name(field.definition.klass, field.relation)
227
+ scope = scope.instance_exec(&reflection.scope) if reflection.try(:has_scope?)
228
+ end
229
+
224
230
  scope.respond_to?(:reorder) ? scope.reorder(Arel.sql(field.quoted_field)) : scope.scoped(:order => field.quoted_field)
225
231
  end
226
232
 
@@ -260,7 +260,7 @@ module ScopedSearch
260
260
 
261
261
  NUMERICAL_REGXP = /^\-?\d+(\.\d+)?$/
262
262
  INTEGER_REGXP = /^\-?\d+$/
263
- UUID_REGXP = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/
263
+ UUID_REGXP = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i
264
264
 
265
265
  # Returns a list of appropriate fields to search in given a search keyword and operator.
266
266
  def default_fields_for(value, operator = nil)
@@ -146,8 +146,22 @@ module ScopedSearch
146
146
  # but the field is of datetime type. Change the comparison to return
147
147
  # more logical results.
148
148
  if field.datetime?
149
- span = 1.minute if(value =~ /\A\s*\d+\s+\bminutes?\b\s+\bago\b\s*\z/i)
150
- span ||= (timestamp.day_fraction == 0) ? 1.day : 1.hour
149
+ if value =~ time_unit_regex("minutes?|hours?")
150
+ span = 1.minute
151
+ elsif value =~ time_unit_regex("days?|weeks?|months?|years?") || value =~ /\b(today|tomorrow|yesterday)\b/i
152
+ span = 1.day
153
+ else
154
+ tokens = DateTime._parse(value)
155
+ # find the smallest unit of time given in input and determine span for further adjustment of the search query
156
+ span = {
157
+ sec: 1.second,
158
+ min: 1.minute,
159
+ hour: 1.hour,
160
+ mday: 1.day,
161
+ mon: 1.month
162
+ }.find { |key, _| tokens[key] }&.last || 1.year
163
+ end
164
+
151
165
  if [:eq, :ne].include?(operator)
152
166
  # Instead of looking for an exact (non-)match, look for dates that
153
167
  # fall inside/outside the range of timestamps of that day.
@@ -155,13 +169,13 @@ module ScopedSearch
155
169
  field_sql = field.to_sql(operator, &block)
156
170
  return ["#{negate}(#{field_sql} >= ? AND #{field_sql} < ?)", timestamp, timestamp + span]
157
171
 
158
- elsif operator == :gt
172
+ elsif span >= 1.day && operator == :gt
159
173
  # Make sure timestamps on the given date are not included in the results
160
174
  # by moving the date to the next day.
161
175
  timestamp += span
162
176
  operator = :gte
163
177
 
164
- elsif operator == :lte
178
+ elsif span >= 1.day && operator == :lte
165
179
  # Make sure the timestamps of the given date are included by moving the
166
180
  # date to the next date.
167
181
  timestamp += span
@@ -320,6 +334,12 @@ module ScopedSearch
320
334
  definition.reflection_by_name(reflection.klass, as).options[:polymorphic]
321
335
  end
322
336
 
337
+ private
338
+
339
+ def time_unit_regex(time_unit)
340
+ /\A\s*\d+\s+\b(?:#{time_unit})\b\s+\b(ago|from\s+now)\b\s*\z/i
341
+ end
342
+
323
343
  # This module gets included into the Field class to add SQL generation.
324
344
  module Field
325
345
 
@@ -69,7 +69,7 @@ module ScopedSearch::QueryLanguage::Tokenizer
69
69
  # Tokenizes a keyword that is quoted using double quotes. Allows escaping
70
70
  # of double quote characters by backslashes.
71
71
  def tokenize_quoted_keyword(&block)
72
- keyword = ""
72
+ keyword = String.new
73
73
  until next_char.nil? || current_char == '"'
74
74
  keyword << (current_char == "\\" ? next_char : current_char)
75
75
  end
@@ -1,3 +1,3 @@
1
1
  module ScopedSearch
2
- VERSION = "4.1.13"
2
+ VERSION = "4.3.0"
3
3
  end
data/lib/scoped_search.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'logger' # a workaround for https://github.com/rails/rails/issues/54263
1
2
  require 'active_record'
2
3
 
3
4
  # ScopedSearch is the base module for the scoped_search plugin. This file
@@ -34,6 +34,13 @@ Gem::Specification.new do |gem|
34
34
  gem.add_development_dependency('rspec', '~> 3.0')
35
35
  gem.add_development_dependency('rake')
36
36
 
37
+ # Rails require these, but don't explicitly depend on them
38
+ gem.add_development_dependency('base64')
39
+ gem.add_development_dependency('benchmark')
40
+ gem.add_development_dependency('bigdecimal')
41
+ gem.add_development_dependency('logger')
42
+ gem.add_development_dependency('mutex_m')
43
+
37
44
  gem.rdoc_options << '--title' << gem.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
38
45
  gem.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.rdoc', 'CONTRIBUTING.rdoc', 'LICENSE']
39
46
  end
@@ -41,6 +41,10 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
41
41
  end
42
42
  end
43
43
 
44
+ ActiveRecord::Migration.create_table(:bazs, :force => true) do |t|
45
+ t.integer :foo_id
46
+ end
47
+
44
48
  class ::Bar < ActiveRecord::Base
45
49
  belongs_to :foo
46
50
  end
@@ -65,6 +69,12 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
65
69
  scoped_search :on => :other_c, :relation => :bars, :rename => 'bars.other_c'.to_sym
66
70
  end
67
71
 
72
+ class ::Baz < ActiveRecord::Base
73
+ belongs_to :foo, -> { where(string: 'foo') }
74
+
75
+ scoped_search :on => :string, :relation => :foo, :complete_value => true
76
+ end
77
+
68
78
  class ::Infoo < ::Foo
69
79
  end
70
80
 
@@ -83,9 +93,11 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
83
93
  after(:all) do
84
94
  ActiveRecord::Migration.drop_table(:foos)
85
95
  ActiveRecord::Migration.drop_table(:bars)
96
+ ActiveRecord::Migration.drop_table(:bazs)
86
97
 
87
98
  Object.send :remove_const, :Foo
88
99
  Object.send :remove_const, :Bar
100
+ Object.send :remove_const, :Baz
89
101
  Object.send :remove_const, :Infoo
90
102
 
91
103
  ScopedSearch::RSpec::Database.close_connection
@@ -169,10 +181,10 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
169
181
  end
170
182
 
171
183
  it "should complete values should contain baz" do
172
- Foo.complete_for('explicit = ').should contain('explicit = baz')
184
+ Foo.complete_for('explicit = ').should contain('explicit = "baz"')
173
185
  end
174
186
 
175
- it "should complete values with quotes where required" do
187
+ it "should complete values with quotes where value is a string" do
176
188
  Foo.complete_for('alias = ').should contain('alias = "temp \"2\""')
177
189
  end
178
190
  end
@@ -267,5 +279,11 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
267
279
  Foo.complete_for('int =', value_filter: { qux_id: 99 }).should == []
268
280
  end
269
281
  end
282
+
283
+ context 'autocompleting with scopes' do
284
+ it 'should honor the scope' do
285
+ ::Baz.complete_for('string =').should == ['string = foo']
286
+ end
287
+ end
270
288
  end
271
289
  end
@@ -43,8 +43,9 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
43
43
  @class.search_for("uuid != #{@record3.uuid}").length.should == 2
44
44
  end
45
45
 
46
- it "should find a record by just specifying the uuid" do
46
+ it "should find a record by just specifying the uuid (case insensitive)" do
47
47
  @class.search_for(@record1.uuid).first.uuid.should == @record1.uuid
48
+ @class.search_for(@record1.uuid.upcase).first.uuid.should == @record1.uuid
48
49
  end
49
50
 
50
51
  it "should not find a record if the uuid is not a valid uuid" do
@@ -155,4 +155,65 @@ describe ScopedSearch::QueryBuilder do
155
155
  lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field = test_val') }.should raise_error(ScopedSearch::QueryNotSupported, /failed with error: test/)
156
156
  end
157
157
  end
158
+
159
+ context 'datetime_test' do
160
+ before(:each) do
161
+ @field = double('field')
162
+ @query_builder = ScopedSearch::QueryBuilder.new(@definition, nil, nil)
163
+
164
+ @field.stub(:datetime?).and_return(true)
165
+ @field.stub(:date?).and_return(false)
166
+ @field.stub(:to_sql).and_return('started_at')
167
+
168
+ [:virtual?, :set?, :temporal?, :relation, :offset].each { |key| @field.stub(key).and_return(false) }
169
+ end
170
+
171
+ it "should return correct SQL literal for equality operator" do
172
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2023, 10, 10))
173
+ result = @query_builder.datetime_test(@field, :eq, '2023-10-10') { |type, value| }
174
+ result.should eq(["(started_at >= ? AND started_at < ?)", DateTime.new(2023, 10, 10), DateTime.new(2023, 10, 11)])
175
+ end
176
+
177
+ it "should return correct SQL literal for inequality operator" do
178
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2023, 10, 10))
179
+ result = @query_builder.datetime_test(@field, :ne, '2023-10-10') { |type, value| }
180
+ result.should eq(["NOT (started_at >= ? AND started_at < ?)", DateTime.new(2023, 10, 10), DateTime.new(2023, 10, 11)])
181
+ end
182
+
183
+ it "should return correct SQL literal for greater operator" do
184
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2023, 10, 9))
185
+ result = @query_builder.datetime_test(@field, :gt, '2023-10-9') { |type, value| }
186
+ result.should eq(["started_at >= ?", DateTime.new(2023, 10, 10)])
187
+ end
188
+
189
+ it "should return correct SQL literal for less than or equal operator" do
190
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2023, 10, 10))
191
+ result = @query_builder.datetime_test(@field, :lte, '2023-10-10') { |type, value| }
192
+ result.should eq(["started_at < ?", DateTime.new(2023, 10, 11)])
193
+ end
194
+
195
+ it "should return empty array for invalid date" do
196
+ @definition.stub(:parse_temporal).and_return(nil)
197
+ result = @query_builder.datetime_test(@field, :eq, 'invalid-date') { |type, value| }
198
+ result.should eq([])
199
+ end
200
+
201
+ it "should count with 1 month deviation if only year and month is provided" do
202
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2024, 1, 1))
203
+ result = @query_builder.datetime_test(@field, :gt, 'January 2024') { |type, value| }
204
+ result.should eq(["started_at >= ?", DateTime.new(2024, 2, 1)])
205
+ end
206
+
207
+ it "should not count with deviation if minute is the smallest unit provided" do
208
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2023, 10, 10, 13, 0, 0))
209
+ result = @query_builder.datetime_test(@field, :gt, '2023-10-10 13:00') { |type, value| }
210
+ result.should eq(["started_at > ?", DateTime.new(2023, 10, 10, 13, 0, 0)])
211
+ end
212
+
213
+ it "should not count with deviation if second is the smallest unit provided" do
214
+ @definition.stub(:parse_temporal).and_return(DateTime.new(2023, 10, 10, 13, 0, 0, 1))
215
+ result = @query_builder.datetime_test(@field, :gt, '2023-10-10 13:00:01') { |type, value| }
216
+ result.should eq(["started_at > ?", DateTime.new(2023, 10, 10, 13, 0, 0, 1)])
217
+ end
218
+ end
158
219
  end
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scoped_search
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.13
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amos Benari
8
8
  - Willem van Bergen
9
9
  - Wes Hays
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2024-12-03 00:00:00.000000000 Z
12
+ date: 1980-01-01 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: activerecord
@@ -54,6 +53,76 @@ dependencies:
54
53
  - - ">="
55
54
  - !ruby/object:Gem::Version
56
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: base64
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: benchmark
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: bigdecimal
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: logger
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: mutex_m
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
57
126
  description: |2
58
127
  Scoped search makes it easy to search your ActiveRecord-based models.
59
128
 
@@ -72,10 +141,10 @@ email:
72
141
  executables: []
73
142
  extensions: []
74
143
  extra_rdoc_files:
75
- - README.rdoc
76
144
  - CHANGELOG.rdoc
77
145
  - CONTRIBUTING.rdoc
78
146
  - LICENSE
147
+ - README.rdoc
79
148
  files:
80
149
  - ".github/workflows/ruby.yml"
81
150
  - ".gitignore"
@@ -144,7 +213,6 @@ homepage: https://github.com/wvanbergen/scoped_search/wiki
144
213
  licenses:
145
214
  - MIT
146
215
  metadata: {}
147
- post_install_message:
148
216
  rdoc_options:
149
217
  - "--title"
150
218
  - scoped_search
@@ -165,8 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
233
  - !ruby/object:Gem::Version
166
234
  version: '0'
167
235
  requirements: []
168
- rubygems_version: 3.3.27
169
- signing_key:
236
+ rubygems_version: 3.6.9
170
237
  specification_version: 4
171
238
  summary: Easily search you ActiveRecord models with a simple query language using
172
239
  a named scope