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 +4 -4
- data/CHANGELOG.rdoc +7 -0
- data/lib/scoped_search/auto_complete_builder.rb +9 -3
- data/lib/scoped_search/definition.rb +1 -1
- data/lib/scoped_search/query_builder.rb +24 -4
- data/lib/scoped_search/query_language/tokenizer.rb +1 -1
- data/lib/scoped_search/version.rb +1 -1
- data/lib/scoped_search.rb +1 -0
- data/scoped_search.gemspec +7 -0
- data/spec/integration/auto_complete_spec.rb +20 -2
- data/spec/integration/uuid_query_spec.rb +2 -1
- data/spec/unit/query_builder_spec.rb +61 -0
- metadata +74 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bdc9b9ee45ee99807b1c22a0da4cef71c6ce539292213ee47c012c020f866368
|
4
|
+
data.tar.gz: 5b4b0acd6e86a3f3e3c940f537a854c193983ebf57019687eac6bf5b33728b42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
218
|
+
.map { |v| v.is_a?(String) ? "\"#{v.gsub('"', '\"')}\"" : v }
|
219
219
|
end
|
220
220
|
|
221
221
|
def completer_scope(field)
|
222
|
-
|
223
|
-
scope =
|
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
|
-
|
150
|
-
|
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
|
data/lib/scoped_search.rb
CHANGED
data/scoped_search.gemspec
CHANGED
@@ -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
|
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.
|
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:
|
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.
|
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
|