scoped_search 3.3.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -8
- data/CHANGELOG.rdoc +20 -0
- data/Gemfile.activerecord42 +3 -3
- data/Gemfile.activerecord50 +4 -4
- data/README.rdoc +3 -0
- data/lib/scoped_search.rb +1 -0
- data/lib/scoped_search/auto_complete_builder.rb +3 -3
- data/lib/scoped_search/definition.rb +73 -58
- data/lib/scoped_search/query_builder.rb +17 -3
- data/lib/scoped_search/rails_helper.rb +27 -176
- data/lib/scoped_search/validators.rb +7 -0
- data/lib/scoped_search/version.rb +1 -1
- data/scoped_search.gemspec +2 -2
- data/spec/integration/auto_complete_spec.rb +10 -6
- data/spec/integration/key_value_querying_spec.rb +1 -1
- data/spec/integration/ordinal_querying_spec.rb +0 -10
- data/spec/integration/relation_querying_spec.rb +63 -10
- data/spec/integration/string_querying_spec.rb +1 -1
- data/spec/unit/ast_spec.rb +3 -3
- data/spec/unit/definition_spec.rb +36 -0
- data/spec/unit/query_builder_spec.rb +22 -0
- data/spec/unit/rails_helper_spec.rb +20 -0
- data/spec/unit/validators_spec.rb +27 -0
- metadata +23 -23
- data/Gemfile.activerecord32 +0 -16
- data/Gemfile.activerecord40 +0 -16
- data/Gemfile.activerecord41 +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df5ac5265b984d6208df025b20dd336950d9bdbb
|
4
|
+
data.tar.gz: 3643c8835b43e6333e24d44473b1b29c3ec7ac91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d760759a261eda13744cad755263a056213315859bc925e5ea814cd0e214e19941dc6b3e07572f80b0bf10058359d6895f663a9249b894fa53297c9f81a7efa
|
7
|
+
data.tar.gz: 4b9580f1b55aeaf6f74b64847318361d58f1e3a7477376b1db4921861ec95db3ba8594f7a6f5915f097fdd9313b2fb9bd30b26afc53c2199d3f367d8fa54c336
|
data/.travis.yml
CHANGED
@@ -11,7 +11,6 @@ script:
|
|
11
11
|
- bundle exec rake
|
12
12
|
|
13
13
|
rvm:
|
14
|
-
- "1.9"
|
15
14
|
- "2.0"
|
16
15
|
- "2.1"
|
17
16
|
- "2.2.2"
|
@@ -21,9 +20,6 @@ rvm:
|
|
21
20
|
- jruby-head
|
22
21
|
|
23
22
|
gemfile:
|
24
|
-
- Gemfile.activerecord32
|
25
|
-
- Gemfile.activerecord40
|
26
|
-
- Gemfile.activerecord41
|
27
23
|
- Gemfile.activerecord42
|
28
24
|
- Gemfile.activerecord50
|
29
25
|
|
@@ -33,11 +29,7 @@ matrix:
|
|
33
29
|
- rvm: jruby-head
|
34
30
|
- rvm: jruby-19mode
|
35
31
|
exclude:
|
36
|
-
- rvm: "1.9"
|
37
|
-
gemfile: Gemfile.activerecord50
|
38
32
|
- rvm: "2.0"
|
39
33
|
gemfile: Gemfile.activerecord50
|
40
34
|
- rvm: "2.1"
|
41
35
|
gemfile: Gemfile.activerecord50
|
42
|
-
- rvm: "2.3.1"
|
43
|
-
gemfile: Gemfile.activerecord32
|
data/CHANGELOG.rdoc
CHANGED
@@ -8,6 +8,26 @@ Please add an entry to the "Unreleased changes" section in your pull requests.
|
|
8
8
|
|
9
9
|
*Nothing yet*
|
10
10
|
|
11
|
+
=== Version 4.0.0
|
12
|
+
|
13
|
+
- Drop support for Ruby 1.9
|
14
|
+
- Drop support for ActiveRecord 3, 4.0, and 4.1
|
15
|
+
- `scoped_search` registration deprecates `:in` in favor of `:relation`, and
|
16
|
+
`:alias` in favor of `aliases: [..]` to avoid clash with Ruby reserved words.
|
17
|
+
- `sort` helper: any HTML options must now be given as a keyword argument,
|
18
|
+
instead of the third argument, i.e. `html_options: {..}`
|
19
|
+
- `sort` helper now takes hash of `url_options`, defaulting to an unfiltered
|
20
|
+
`params`. Call `params.permit(..)` and pass in the result to safely generate
|
21
|
+
URLs and prevent non-sanitized errors under Rails 5.
|
22
|
+
- Auto completion: remove autocomplete_* JavaScript helpers in favor of jQuery
|
23
|
+
method provided via asset pipeline. Call `$(..).scopedSearch` on autocomplete
|
24
|
+
text boxes to activate.
|
25
|
+
- Auto completion: escape quotes in listed values (#138)
|
26
|
+
- Add :validator option to definitions to validate user's search inputs
|
27
|
+
- Fix incorrect association conditions being used on through relations
|
28
|
+
- Use `#distinct` on Rails 4+
|
29
|
+
- Test Rails 5.0.x releases, test latest AR adapters
|
30
|
+
|
11
31
|
=== Version 3.3.0
|
12
32
|
|
13
33
|
- Support for ActiveRecord 5.0
|
data/Gemfile.activerecord42
CHANGED
data/Gemfile.activerecord50
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
gemspec
|
3
3
|
|
4
|
-
gem 'activerecord', '5.0.0
|
4
|
+
gem 'activerecord', '~> 5.0.0'
|
5
5
|
|
6
6
|
platforms :jruby do
|
7
7
|
gem 'activerecord-jdbcsqlite3-adapter'
|
@@ -10,7 +10,7 @@ platforms :jruby do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
platforms :ruby do
|
13
|
-
gem 'sqlite3'
|
14
|
-
gem 'mysql2', '
|
15
|
-
gem 'pg'
|
13
|
+
gem 'sqlite3', '~> 1.3.6'
|
14
|
+
gem 'mysql2', '>= 0.3.18', '< 0.5'
|
15
|
+
gem 'pg', '~> 0.18'
|
16
16
|
end
|
data/README.rdoc
CHANGED
@@ -22,6 +22,9 @@ Add the following line in your Gemfile, and run <tt>bundle install</tt>:
|
|
22
22
|
|
23
23
|
gem "scoped_search"
|
24
24
|
|
25
|
+
Scoped search 4.x supports Rails 4.2 to 5.0, with Ruby 2.0.0 or higher. Use
|
26
|
+
previous versions, e.g. 3.x to support older versions of Rails or Ruby.
|
27
|
+
|
25
28
|
== Usage
|
26
29
|
|
27
30
|
Scoped search requires you to define the fields you want to search in:
|
data/lib/scoped_search.rb
CHANGED
@@ -65,6 +65,7 @@ require 'scoped_search/definition'
|
|
65
65
|
require 'scoped_search/query_language'
|
66
66
|
require 'scoped_search/query_builder'
|
67
67
|
require 'scoped_search/auto_complete_builder'
|
68
|
+
require 'scoped_search/validators'
|
68
69
|
|
69
70
|
# Import the search_on method in the ActiveReocrd::Base class
|
70
71
|
ActiveRecord::Base.send(:extend, ScopedSearch::ClassMethods)
|
@@ -178,7 +178,7 @@ module ScopedSearch
|
|
178
178
|
.where(value_conditions(field_name, val))
|
179
179
|
.select(field_name)
|
180
180
|
.limit(20)
|
181
|
-
.
|
181
|
+
.distinct
|
182
182
|
.map(&field.key_field)
|
183
183
|
.compact
|
184
184
|
.map { |f| "#{name}.#{f} " }
|
@@ -205,10 +205,10 @@ module ScopedSearch
|
|
205
205
|
.where(value_conditions(field.quoted_field, val))
|
206
206
|
.select(field.quoted_field)
|
207
207
|
.limit(20)
|
208
|
-
.
|
208
|
+
.distinct
|
209
209
|
.map(&field.field)
|
210
210
|
.compact
|
211
|
-
.map { |v| v.to_s =~ /\s/ ? "\"#{v}\"" : v }
|
211
|
+
.map { |v| v.to_s =~ /\s/ ? "\"#{v.gsub('"', '\"')}\"" : v }
|
212
212
|
end
|
213
213
|
|
214
214
|
def completer_scope(field)
|
@@ -16,44 +16,76 @@ module ScopedSearch
|
|
16
16
|
class Field
|
17
17
|
|
18
18
|
attr_reader :definition, :field, :only_explicit, :relation, :key_relation, :full_text_search,
|
19
|
-
:key_field, :complete_value, :complete_enabled, :offset, :word_size, :ext_method, :operators
|
19
|
+
:key_field, :complete_value, :complete_enabled, :offset, :word_size, :ext_method, :operators,
|
20
|
+
:validator
|
20
21
|
|
21
22
|
# Initializes a Field instance given the definition passed to the
|
22
23
|
# scoped_search call on the ActiveRecord-based model class.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
24
|
+
#
|
25
|
+
# Field name may be given in positional 'field' argument or 'on' named
|
26
|
+
# argument.
|
27
|
+
def initialize(definition,
|
28
|
+
field = nil,
|
29
|
+
aliases: [],
|
30
|
+
complete_enabled: true,
|
31
|
+
complete_value: nil,
|
32
|
+
default_operator: nil,
|
33
|
+
default_order: nil,
|
34
|
+
ext_method: nil,
|
35
|
+
full_text_search: nil,
|
36
|
+
in_key: nil,
|
37
|
+
offset: nil,
|
38
|
+
on: field,
|
39
|
+
on_key: nil,
|
40
|
+
only_explicit: nil,
|
41
|
+
operators: nil,
|
42
|
+
profile: nil,
|
43
|
+
relation: nil,
|
44
|
+
rename: nil,
|
45
|
+
validator: nil,
|
46
|
+
word_size: 1,
|
47
|
+
**kwargs)
|
48
|
+
|
49
|
+
# Prefer 'on' kw arg if given, defaults to the 'field' positional to allow either syntax
|
50
|
+
raise ArgumentError, "Missing field or 'on' keyword argument" if on.nil?
|
51
|
+
@field = on.to_sym
|
52
|
+
|
53
|
+
# Reserved Ruby keywords so access via kwargs instead, but deprecate them for future versions
|
54
|
+
if kwargs.key?(:in)
|
55
|
+
relation = kwargs.delete(:in)
|
56
|
+
ActiveSupport::Deprecation.warn("'in' argument deprecated, prefer 'relation' since scoped_search 4.0.0", caller(6))
|
47
57
|
end
|
58
|
+
if kwargs.key?(:alias)
|
59
|
+
aliases += [kwargs.delete(:alias)]
|
60
|
+
ActiveSupport::Deprecation.warn("'alias' argument deprecated, prefer aliases: [..] since scoped_search 4.0.0", caller(6))
|
61
|
+
end
|
62
|
+
raise ArgumentError, "Unknown arguments to scoped_search: #{kwargs.keys.join(', ')}" unless kwargs.empty?
|
48
63
|
|
49
|
-
|
50
|
-
definition.
|
51
|
-
definition.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
64
|
+
@definition = definition
|
65
|
+
@definition.profile = profile if profile
|
66
|
+
@definition.default_order ||= generate_default_order(default_order, rename || @field) if default_order
|
67
|
+
|
68
|
+
# Set attributes from keyword arguments
|
69
|
+
@complete_enabled = complete_enabled
|
70
|
+
@complete_value = complete_value
|
71
|
+
@default_operator = default_operator
|
72
|
+
@ext_method = ext_method
|
73
|
+
@full_text_search = full_text_search
|
74
|
+
@key_field = on_key
|
75
|
+
@key_relation = in_key
|
76
|
+
@offset = offset
|
77
|
+
@only_explicit = !!only_explicit
|
78
|
+
@operators = operators
|
79
|
+
@relation = relation
|
80
|
+
@validator = validator
|
81
|
+
@word_size = word_size
|
82
|
+
|
83
|
+
# Store this field in the field array
|
84
|
+
definition.fields[rename ? rename.to_sym : @field] ||= self
|
85
|
+
definition.unique_fields << self
|
86
|
+
|
87
|
+
# Store definition for aliases as well
|
88
|
+
aliases.each { |al| definition.fields[al.to_sym] ||= self }
|
57
89
|
end
|
58
90
|
|
59
91
|
# The ActiveRecord-based class that belongs to this field.
|
@@ -82,11 +114,7 @@ module ScopedSearch
|
|
82
114
|
if klass.columns_hash.has_key?(field.to_s)
|
83
115
|
klass.columns_hash[field.to_s]
|
84
116
|
else
|
85
|
-
|
86
|
-
raise ActiveRecord::UnknownAttributeError, "#{klass.inspect} doesn't have column #{field.inspect}."
|
87
|
-
else
|
88
|
-
raise ActiveRecord::UnknownAttributeError.new(klass, field)
|
89
|
-
end
|
117
|
+
raise ActiveRecord::UnknownAttributeError.new(klass, field)
|
90
118
|
end
|
91
119
|
end
|
92
120
|
end
|
@@ -135,11 +163,9 @@ module ScopedSearch
|
|
135
163
|
end
|
136
164
|
end
|
137
165
|
|
138
|
-
def default_order
|
139
|
-
|
140
|
-
|
141
|
-
order = (options[:default_order].to_s.downcase.include?('desc')) ? "DESC" : "ASC"
|
142
|
-
return "#{field_name} #{order}"
|
166
|
+
def generate_default_order(default_order, field)
|
167
|
+
order = (default_order.to_s.downcase.include?('desc')) ? "DESC" : "ASC"
|
168
|
+
return "#{field} #{order}"
|
143
169
|
end
|
144
170
|
|
145
171
|
# Return 'table'.'column' with the correct database quotes
|
@@ -234,8 +260,8 @@ module ScopedSearch
|
|
234
260
|
end
|
235
261
|
|
236
262
|
# Defines a new search field for this search definition.
|
237
|
-
def define(
|
238
|
-
Field.new(self,
|
263
|
+
def define(*args)
|
264
|
+
Field.new(self, *args)
|
239
265
|
end
|
240
266
|
|
241
267
|
# Returns a reflection for a given klass and name
|
@@ -252,24 +278,13 @@ module ScopedSearch
|
|
252
278
|
@klass.scope(:search_for, proc { |query, options|
|
253
279
|
klass = definition.klass
|
254
280
|
|
255
|
-
search_scope =
|
256
|
-
when 3
|
257
|
-
klass.scoped
|
258
|
-
when 4
|
259
|
-
(ActiveRecord::VERSION::MINOR < 1) ? klass.where(nil) : klass.all
|
260
|
-
when 5
|
261
|
-
klass.all
|
262
|
-
else
|
263
|
-
raise ScopedSearch::DefinitionError, 'version ' \
|
264
|
-
"#{ActiveRecord::VERSION::MAJOR} of activerecord is not supported"
|
265
|
-
end
|
266
|
-
|
281
|
+
search_scope = klass.all
|
267
282
|
find_options = ScopedSearch::QueryBuilder.build_query(definition, query || '', options || {})
|
268
283
|
search_scope = search_scope.where(find_options[:conditions]) if find_options[:conditions]
|
269
284
|
search_scope = search_scope.includes(find_options[:include]) if find_options[:include]
|
270
285
|
search_scope = search_scope.joins(find_options[:joins]) if find_options[:joins]
|
271
286
|
search_scope = search_scope.reorder(find_options[:order]) if find_options[:order]
|
272
|
-
search_scope = search_scope.references(find_options[:include]) if find_options[:include]
|
287
|
+
search_scope = search_scope.references(find_options[:include]) if find_options[:include]
|
273
288
|
|
274
289
|
search_scope
|
275
290
|
})
|
@@ -251,9 +251,9 @@ module ScopedSearch
|
|
251
251
|
|
252
252
|
def find_has_many_through_association(field, through)
|
253
253
|
middle_table_association = nil
|
254
|
-
field.klass.reflect_on_all_associations(:has_many).each do |reflection|
|
254
|
+
field.klass.reflect_on_all_associations(:has_many).each do |reflection|
|
255
255
|
class_name = reflection.options[:class_name].constantize.table_name if reflection.options[:class_name]
|
256
|
-
middle_table_association = reflection.name if class_name == through.to_s
|
256
|
+
middle_table_association = reflection.name if class_name == through.to_s
|
257
257
|
middle_table_association = reflection.plural_name if reflection.plural_name == through.to_s
|
258
258
|
end
|
259
259
|
middle_table_association
|
@@ -271,7 +271,7 @@ module ScopedSearch
|
|
271
271
|
|
272
272
|
# primary and foreign keys + optional condition for the many to middle join
|
273
273
|
pk1, fk1 = field.reflection_keys(definition.reflection_by_name(many_class, through))
|
274
|
-
condition1 = field.reflection_conditions(definition.reflection_by_name(field.klass,
|
274
|
+
condition1 = field.reflection_conditions(definition.reflection_by_name(field.klass, middle_table_name))
|
275
275
|
|
276
276
|
# primary and foreign keys + optional condition for the endpoint to middle join
|
277
277
|
middle_table_association = find_has_many_through_association(field, through) || middle_table_name
|
@@ -468,6 +468,10 @@ module ScopedSearch
|
|
468
468
|
# Search only on the given field.
|
469
469
|
field = definition.field_by_name(lhs.value)
|
470
470
|
raise ScopedSearch::QueryNotSupported, "Field '#{lhs.value}' not recognized for searching!" unless field
|
471
|
+
|
472
|
+
# see if the value passes user defined validation
|
473
|
+
validate_value(field, rhs.value)
|
474
|
+
|
471
475
|
builder.sql_test(field, operator, rhs.value,lhs.value, &block)
|
472
476
|
end
|
473
477
|
|
@@ -485,6 +489,16 @@ module ScopedSearch
|
|
485
489
|
raise ScopedSearch::QueryNotSupported, "Don't know how to handle this operator node: #{operator.inspect} with #{children.inspect}!"
|
486
490
|
end
|
487
491
|
end
|
492
|
+
|
493
|
+
private
|
494
|
+
|
495
|
+
def validate_value(field, value)
|
496
|
+
validator = field.validator
|
497
|
+
if validator
|
498
|
+
valid = validator.call(value)
|
499
|
+
raise ScopedSearch::QueryNotSupported, "Value '#{value}' is not valid for field '#{field.field}'" unless valid
|
500
|
+
end
|
501
|
+
end
|
488
502
|
end
|
489
503
|
|
490
504
|
# Defines the to_sql method for AST AND/OR operators
|
@@ -4,20 +4,31 @@ module ScopedSearch
|
|
4
4
|
#
|
5
5
|
# Examples:
|
6
6
|
#
|
7
|
-
# sort
|
8
|
-
# sort
|
9
|
-
# sort
|
7
|
+
# sort :username
|
8
|
+
# sort :created_at, as: "Created"
|
9
|
+
# sort :created_at, default: "DESC"
|
10
|
+
#
|
11
|
+
# * <tt>field</tt> - the name of the named scope. This helper will prepend this value with "ascend_by_" and "descend_by_"
|
10
12
|
#
|
11
13
|
# This helper accepts the following options:
|
12
14
|
#
|
13
|
-
# * <tt>:
|
14
|
-
# * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
|
15
|
+
# * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to `field`
|
15
16
|
# * <tt>:default</tt> - default sorting order, DESC or ASC
|
16
|
-
|
17
|
+
# * <tt>:html_options</tt> - is a hash of HTML options for the anchor tag
|
18
|
+
# * <tt>:url_options</tt> - is a hash of URL parameters, defaulting to `params`, to preserve the current URL
|
19
|
+
# parameters.
|
20
|
+
#
|
21
|
+
# On Rails 5 or higher, parameter whitelisting prevents any parameter being used in a link by
|
22
|
+
# default, so `params.permit(..)` should be passed for `url_options` for all known and
|
23
|
+
# permitted URL parameters, e.g.
|
24
|
+
#
|
25
|
+
# sort :username, url_options: params.permit(:search)
|
26
|
+
#
|
27
|
+
def sort(field, as: nil, default: "ASC", html_options: {}, url_options: params)
|
17
28
|
|
18
|
-
unless
|
19
|
-
id
|
20
|
-
|
29
|
+
unless as
|
30
|
+
id = field.to_s.downcase == "id"
|
31
|
+
as = id ? field.to_s.upcase : field.to_s.humanize
|
21
32
|
end
|
22
33
|
|
23
34
|
ascend = "#{field} ASC"
|
@@ -30,151 +41,27 @@ module ScopedSearch
|
|
30
41
|
when descend
|
31
42
|
new_sort = ascend
|
32
43
|
else
|
33
|
-
new_sort = ["ASC", "DESC"].include?(
|
44
|
+
new_sort = ["ASC", "DESC"].include?(default) ? "#{field} #{default}" : ascend
|
34
45
|
end
|
35
46
|
|
36
47
|
unless selected_sort.nil?
|
37
48
|
css_classes = html_options[:class] ? html_options[:class].split(" ") : []
|
38
49
|
if selected_sort == ascend
|
39
|
-
|
50
|
+
as = "▲ #{as}"
|
40
51
|
css_classes << "ascending"
|
41
52
|
else
|
42
|
-
|
53
|
+
as = "▼ #{as}"
|
43
54
|
css_classes << "descending"
|
44
55
|
end
|
45
56
|
html_options[:class] = css_classes.join(" ")
|
46
57
|
end
|
47
58
|
|
48
|
-
url_options =
|
49
|
-
|
50
|
-
options[:as] = raw(options[:as]) if defined?(RailsXss)
|
51
|
-
|
52
|
-
a_link(options[:as], html_escape(url_for(url_options)),html_options)
|
53
|
-
end
|
54
|
-
|
55
|
-
# Adds AJAX auto complete functionality to the text input field with the
|
56
|
-
# DOM ID specified by +field_id+.
|
57
|
-
#
|
58
|
-
# Required +options+ is:
|
59
|
-
# <tt>:url</tt>:: URL to call for auto completion results
|
60
|
-
# in url_for format.
|
61
|
-
#
|
62
|
-
# Additional +options+ are:
|
63
|
-
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
64
|
-
# innerHTML should be updated with the auto complete
|
65
|
-
# entries returned by the AJAX request.
|
66
|
-
# Defaults to <tt>field_id</tt> + '_auto_complete'
|
67
|
-
# <tt>:with</tt>:: A JavaScript expression specifying the
|
68
|
-
# parameters for the XMLHttpRequest. This defaults
|
69
|
-
# to 'fieldname=value'.
|
70
|
-
# <tt>:frequency</tt>:: Determines the time to wait after the last keystroke
|
71
|
-
# for the AJAX request to be initiated.
|
72
|
-
# <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
|
73
|
-
# displayed while auto complete is running.
|
74
|
-
# <tt>:tokens</tt>:: A string or an array of strings containing
|
75
|
-
# separator tokens for tokenized incremental
|
76
|
-
# auto completion. Example: <tt>:tokens => ','</tt> would
|
77
|
-
# allow multiple auto completion entries, separated
|
78
|
-
# by commas.
|
79
|
-
# <tt>:min_chars</tt>:: The minimum number of characters that should be
|
80
|
-
# in the input field before an Ajax call is made
|
81
|
-
# to the server.
|
82
|
-
# <tt>:on_hide</tt>:: A Javascript expression that is called when the
|
83
|
-
# auto completion div is hidden. The expression
|
84
|
-
# should take two variables: element and update.
|
85
|
-
# Element is a DOM element for the field, update
|
86
|
-
# is a DOM element for the div from which the
|
87
|
-
# innerHTML is replaced.
|
88
|
-
# <tt>:on_show</tt>:: Like on_hide, only now the expression is called
|
89
|
-
# then the div is shown.
|
90
|
-
# <tt>:after_update_element</tt>:: A Javascript expression that is called when the
|
91
|
-
# user has selected one of the proposed values.
|
92
|
-
# The expression should take two variables: element and value.
|
93
|
-
# Element is a DOM element for the field, value
|
94
|
-
# is the value selected by the user.
|
95
|
-
# <tt>:select</tt>:: Pick the class of the element from which the value for
|
96
|
-
# insertion should be extracted. If this is not specified,
|
97
|
-
# the entire element is used.
|
98
|
-
# <tt>:method</tt>:: Specifies the HTTP verb to use when the auto completion
|
99
|
-
# request is made. Defaults to POST.
|
100
|
-
def auto_complete_field(field_id, options = {})
|
101
|
-
function = "var #{field_id}_auto_completer = new Ajax.Autocompleter("
|
102
|
-
function << "'#{field_id}', "
|
103
|
-
function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
|
104
|
-
function << "'#{url_for(options[:url])}'"
|
105
|
-
|
106
|
-
js_options = {}
|
107
|
-
js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens]
|
108
|
-
js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with]
|
109
|
-
js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator]
|
110
|
-
js_options[:select] = "'#{options[:select]}'" if options[:select]
|
111
|
-
js_options[:paramName] = "'#{options[:param_name]}'" if options[:param_name]
|
112
|
-
js_options[:frequency] = "#{options[:frequency]}" if options[:frequency]
|
113
|
-
js_options[:method] = "'#{options[:method].to_s}'" if options[:method]
|
114
|
-
|
115
|
-
{ :after_update_element => :afterUpdateElement,
|
116
|
-
:on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v|
|
117
|
-
js_options[v] = options[k] if options[k]
|
118
|
-
end
|
59
|
+
url_options = url_options.to_h if url_options.respond_to?(:permit) # convert ActionController::Parameters if given
|
60
|
+
url_options = url_options.merge(:order => new_sort)
|
119
61
|
|
120
|
-
|
62
|
+
as = raw(as) if defined?(RailsXss)
|
121
63
|
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
def auto_complete_field_jquery(method, url, options = {})
|
126
|
-
function = <<-JAVASCRIPT
|
127
|
-
$.widget( "custom.catcomplete", $.ui.autocomplete, {
|
128
|
-
_renderMenu: function( ul, items ) {
|
129
|
-
var self = this,
|
130
|
-
currentCategory = "";
|
131
|
-
$.each( items, function( index, item ) {
|
132
|
-
if ( item.category != undefined && item.category != currentCategory ) {
|
133
|
-
ul.append( "<li class='ui-autocomplete-category'>" + item.category + "</li>" );
|
134
|
-
currentCategory = item.category;
|
135
|
-
}
|
136
|
-
if ( item.error != undefined ) {
|
137
|
-
ul.append( "<li class='ui-autocomplete-error'>" + item.error + "</li>" );
|
138
|
-
}
|
139
|
-
if( item.completed != undefined ) {
|
140
|
-
$( "<li></li>" ).data( "item.autocomplete", item )
|
141
|
-
.append( "<a>" + "<strong class='ui-autocomplete-completed'>" + item.completed + "</strong>" + item.part + "</a>" )
|
142
|
-
.appendTo( ul );
|
143
|
-
} else {
|
144
|
-
if(typeof(self._renderItemData) === "function") {
|
145
|
-
self._renderItemData( ul, item );
|
146
|
-
} else {
|
147
|
-
self._renderItem( ul, item );
|
148
|
-
}
|
149
|
-
}
|
150
|
-
});
|
151
|
-
}
|
152
|
-
});
|
153
|
-
|
154
|
-
$("##{method}")
|
155
|
-
.catcomplete({
|
156
|
-
source: function( request, response ) { $.getJSON( "#{url}", { #{method}: request.term }, response ); },
|
157
|
-
minLength: #{options[:min_length] || 0},
|
158
|
-
delay: #{options[:delay] || 200},
|
159
|
-
select: function(event, ui) { $( this ).catcomplete( "search" , ui.item.value); },
|
160
|
-
search: function(event, ui) { $(".auto_complete_clear").hide(); },
|
161
|
-
open: function(event, ui) { $(".auto_complete_clear").show(); }
|
162
|
-
});
|
163
|
-
|
164
|
-
$("##{method}").bind( "focus", function( event ) {
|
165
|
-
if( $( this )[0].value == "" ) {
|
166
|
-
$( this ).catcomplete( "search" );
|
167
|
-
}
|
168
|
-
});
|
169
|
-
|
170
|
-
JAVASCRIPT
|
171
|
-
|
172
|
-
javascript_tag(function)
|
173
|
-
end
|
174
|
-
|
175
|
-
def auto_complete_clear_value_button(field_id)
|
176
|
-
html_options = {:tabindex => '-1',:class=>"auto_complete_clear",:title =>'Clear Search', :onclick=>"document.getElementById('#{field_id}').value = '';"}
|
177
|
-
a_link("", "#", html_options)
|
64
|
+
a_link(as, html_escape(url_for(url_options)),html_options)
|
178
65
|
end
|
179
66
|
|
180
67
|
def a_link(name, href, html_options)
|
@@ -182,41 +69,5 @@ module ScopedSearch
|
|
182
69
|
link = "<a href=\"#{href}\"#{tag_options}>#{name}</a>"
|
183
70
|
return link.respond_to?(:html_safe) ? link.html_safe : link
|
184
71
|
end
|
185
|
-
|
186
|
-
# Use this method in your view to generate a return for the AJAX auto complete requests.
|
187
|
-
#
|
188
|
-
# The auto_complete_result can of course also be called from a view belonging to the
|
189
|
-
# auto_complete action if you need to decorate it further.
|
190
|
-
def auto_complete_result(entries, phrase = nil)
|
191
|
-
return unless entries
|
192
|
-
items = entries.map { |entry| content_tag("li", phrase ? highlight(entry, phrase) : h(entry)) }
|
193
|
-
content_tag("ul", items)
|
194
|
-
end
|
195
|
-
|
196
|
-
# Wrapper for text_field with added AJAX auto completion functionality.
|
197
|
-
#
|
198
|
-
# In your controller, you'll need to define an action called
|
199
|
-
# auto_complete_method to respond the AJAX calls,
|
200
|
-
def auto_complete_field_tag(method, val,tag_options = {}, completion_options = {})
|
201
|
-
auto_completer_options = { :url => { :action => "auto_complete_#{method}" } }.update(completion_options)
|
202
|
-
options = tag_options.merge(:class => "auto_complete_input " << tag_options[:class].to_s)
|
203
|
-
text_field_tag(method, val,options) +
|
204
|
-
auto_complete_clear_value_button(method) +
|
205
|
-
content_tag("div", "", :id => "#{method}_auto_complete", :class => "auto_complete") +
|
206
|
-
auto_complete_field(method, auto_completer_options)
|
207
|
-
end
|
208
|
-
|
209
|
-
# Wrapper for text_field with added JQuery auto completion functionality.
|
210
|
-
#
|
211
|
-
# In your controller, you'll need to define an action called
|
212
|
-
# auto_complete_method to respond the JQuery calls,
|
213
|
-
def auto_complete_field_tag_jquery(method, val,tag_options = {}, completion_options = {})
|
214
|
-
url = url_for(:action => "auto_complete_#{method}", :filter => completion_options[:filter])
|
215
|
-
options = tag_options.merge(:class => "auto_complete_input " << tag_options[:class].to_s)
|
216
|
-
text_field_tag(method, val, options) + auto_complete_clear_value_button(method) +
|
217
|
-
auto_complete_field_jquery(method, url, completion_options)
|
218
|
-
end
|
219
|
-
deprecate :auto_complete_field_tag_jquery, :auto_complete_field_tag, :auto_complete_result
|
220
|
-
|
221
72
|
end
|
222
73
|
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module ScopedSearch
|
2
|
+
# This class will be used to store standard validators.
|
3
|
+
class Validators
|
4
|
+
NUMERIC = ->(value) { !!(value =~ ScopedSearch::Definition::NUMERICAL_REGXP) }
|
5
|
+
INTEGER = ->(value) { !!(value =~ ScopedSearch::Definition::INTEGER_REGXP) }
|
6
|
+
end
|
7
|
+
end
|
data/scoped_search.gemspec
CHANGED
@@ -29,8 +29,8 @@ Gem::Specification.new do |gem|
|
|
29
29
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
30
30
|
gem.require_paths = ["lib"]
|
31
31
|
|
32
|
-
gem.required_ruby_version = '>=
|
33
|
-
gem.add_runtime_dependency('activerecord', '>=
|
32
|
+
gem.required_ruby_version = '>= 2.0.0'
|
33
|
+
gem.add_runtime_dependency('activerecord', '>= 4.2.0')
|
34
34
|
gem.add_development_dependency('rspec', '~> 3.0')
|
35
35
|
gem.add_development_dependency('rake')
|
36
36
|
|
@@ -37,20 +37,20 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
37
37
|
|
38
38
|
scoped_search :on => [:string, :date]
|
39
39
|
scoped_search :on => [:int], :complete_value => true
|
40
|
-
scoped_search :on => :another, :default_operator => :eq, :
|
40
|
+
scoped_search :on => :another, :default_operator => :eq, :aliases => [:alias], :complete_value => true
|
41
41
|
scoped_search :on => :explicit, :only_explicit => true, :complete_value => true
|
42
42
|
scoped_search :on => :deprecated, :complete_enabled => false
|
43
|
-
scoped_search :on => :related, :
|
44
|
-
scoped_search :on => :other_a, :
|
45
|
-
scoped_search :on => :other_b, :
|
46
|
-
scoped_search :on => :other_c, :
|
43
|
+
scoped_search :on => :related, :relation => :bars, :rename => 'bars.related'.to_sym
|
44
|
+
scoped_search :on => :other_a, :relation => :bars, :rename => 'bars.other_a'.to_sym
|
45
|
+
scoped_search :on => :other_b, :relation => :bars, :rename => 'bars.other_b'.to_sym
|
46
|
+
scoped_search :on => :other_c, :relation => :bars, :rename => 'bars.other_c'.to_sym
|
47
47
|
end
|
48
48
|
|
49
49
|
class ::Infoo < ::Foo
|
50
50
|
end
|
51
51
|
|
52
52
|
@foo_1 = Foo.create!(:string => 'foo', :another => 'temp 1', :explicit => 'baz', :int => 9 , :date => 'February 8, 2011' , :unindexed => 10)
|
53
|
-
Foo.create!(:string => 'bar', :another => 'temp 2', :explicit => 'baz', :int => 22 , :date => 'February 10, 2011', :unindexed => 10)
|
53
|
+
Foo.create!(:string => 'bar', :another => 'temp "2"', :explicit => 'baz', :int => 22 , :date => 'February 10, 2011', :unindexed => 10)
|
54
54
|
Foo.create!(:string => 'baz', :another => nil, :explicit => nil , :int => nil, :date => nil , :unindexed => nil)
|
55
55
|
20.times { Foo.create!(:explicit => "aaa") }
|
56
56
|
|
@@ -143,6 +143,10 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
143
143
|
it "should complete values should contain baz" do
|
144
144
|
Foo.complete_for('explicit = ').should contain('explicit = baz')
|
145
145
|
end
|
146
|
+
|
147
|
+
it "should complete values with quotes where required" do
|
148
|
+
Foo.complete_for('alias = ').should contain('alias = "temp \"2\""')
|
149
|
+
end
|
146
150
|
end
|
147
151
|
|
148
152
|
context 'auto complete relations' do
|
@@ -32,7 +32,7 @@ require "spec_helper"
|
|
32
32
|
has_many :facts
|
33
33
|
has_many :keys, :through => :facts
|
34
34
|
|
35
|
-
scoped_search :
|
35
|
+
scoped_search :relation => :facts, :on => :value, :rename => :facts, :in_key => :keys, :on_key => :name, :complete_value => true
|
36
36
|
end
|
37
37
|
class ::MyItem < ::Item
|
38
38
|
end
|
@@ -114,16 +114,6 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
114
114
|
@class.search_for('date = 09-01-02').length.should == 1
|
115
115
|
end
|
116
116
|
|
117
|
-
if RUBY_VERSION.to_f == 1.8
|
118
|
-
it "should accept MM/DD/YY as date format" do
|
119
|
-
@class.search_for('date = 01/02/09').length.should == 1
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should accept MM/DD/YYYY as date format" do
|
123
|
-
@class.search_for('date = 01/02/2009').length.should == 1
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
117
|
it "should accept YYYY/MM/DD as date format" do
|
128
118
|
@class.search_for('date = 2009/01/02').length.should == 1
|
129
119
|
end
|
@@ -46,7 +46,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
46
46
|
ActiveRecord::Migration.create_table(:loos) { |t| t.string :foo; t.integer :har_id }
|
47
47
|
class Loo < ActiveRecord::Base
|
48
48
|
belongs_to :har
|
49
|
-
scoped_search :
|
49
|
+
scoped_search :relation => :har, :on => :related
|
50
50
|
end
|
51
51
|
|
52
52
|
@har_record = Har.create!(:related => 'bar')
|
@@ -93,7 +93,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
93
93
|
class Goo < ActiveRecord::Base
|
94
94
|
has_many :jars
|
95
95
|
scoped_search :on => :foo
|
96
|
-
scoped_search :
|
96
|
+
scoped_search :relation => :jars, :on => :related
|
97
97
|
end
|
98
98
|
|
99
99
|
@foo_1 = Goo.create!(:foo => 'foo')
|
@@ -158,7 +158,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
158
158
|
class Hoo < ActiveRecord::Base
|
159
159
|
has_one :car
|
160
160
|
scoped_search :on => :foo
|
161
|
-
scoped_search :
|
161
|
+
scoped_search :relation => :car, :on => :related
|
162
162
|
end
|
163
163
|
|
164
164
|
@hoo_1 = Hoo.create!(:foo => 'foo')
|
@@ -206,7 +206,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
206
206
|
# The class on which to call search_for
|
207
207
|
class Joo < ActiveRecord::Base
|
208
208
|
has_and_belongs_to_many :dars
|
209
|
-
scoped_search :
|
209
|
+
scoped_search :relation => :dars, :on => :related
|
210
210
|
end
|
211
211
|
|
212
212
|
@joo_1 = Joo.create!(:foo => 'foo')
|
@@ -265,7 +265,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
265
265
|
# as an indication for a polymorphic relation.
|
266
266
|
has_many :bazs, :through => :mars, :source => :baz
|
267
267
|
|
268
|
-
scoped_search :
|
268
|
+
scoped_search :relation => :bazs, :on => :related
|
269
269
|
end
|
270
270
|
|
271
271
|
@koo_1 = Koo.create!(:foo => 'foo')
|
@@ -316,7 +316,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
316
316
|
belongs_to :zar
|
317
317
|
has_many :bazs, :through => :zar
|
318
318
|
|
319
|
-
scoped_search :
|
319
|
+
scoped_search :relation => :bazs, :on => :related
|
320
320
|
end
|
321
321
|
|
322
322
|
baz_1 = Baz.create(:related => 'baz')
|
@@ -362,7 +362,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
362
362
|
has_many :taggables
|
363
363
|
has_many :dogs, :through => :taggables, :source => :taggable, :source_type => 'Dog'
|
364
364
|
|
365
|
-
scoped_search :
|
365
|
+
scoped_search :relation => :dogs, :on => :related, :rename => :dog
|
366
366
|
end
|
367
367
|
|
368
368
|
# The class on which to call search_for
|
@@ -370,7 +370,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
370
370
|
has_many :taggables, :as => :taggable
|
371
371
|
has_many :tags, :through => :taggables
|
372
372
|
|
373
|
-
scoped_search :
|
373
|
+
scoped_search :relation => :tags, :on => :foo
|
374
374
|
end
|
375
375
|
|
376
376
|
class Cat < ActiveRecord::Base
|
@@ -438,7 +438,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
438
438
|
has_many :zaps
|
439
439
|
has_many :pazs, :through => :zaps
|
440
440
|
|
441
|
-
scoped_search :
|
441
|
+
scoped_search :relation => :pazs, :on => :related
|
442
442
|
end
|
443
443
|
|
444
444
|
@moo_1 = Moo.create!(:foo => 'foo')
|
@@ -471,6 +471,59 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
471
471
|
end
|
472
472
|
end
|
473
473
|
|
474
|
+
context 'querying a :has_many => :through relation with same name on target class with custom condition' do
|
475
|
+
|
476
|
+
before do
|
477
|
+
|
478
|
+
# Create some tables
|
479
|
+
ActiveRecord::Migration.create_table(:user_groups) { |t| t.integer :user_id; t.integer :group_id }
|
480
|
+
ActiveRecord::Migration.create_table(:conflicts) { |t| t.integer :group_id; t.integer :user_id }
|
481
|
+
ActiveRecord::Migration.create_table(:groups) { |t| t.string :related; t.integer :user_id }
|
482
|
+
ActiveRecord::Migration.create_table(:users) { |t| t.string :foo }
|
483
|
+
|
484
|
+
# The related classes
|
485
|
+
class UserGroup < ActiveRecord::Base; belongs_to :user; belongs_to :group; end
|
486
|
+
class Conflict < ActiveRecord::Base; belongs_to :user; belongs_to :group; end
|
487
|
+
class Group < ActiveRecord::Base
|
488
|
+
has_many :user_groups
|
489
|
+
has_many :users, :through => :conflicts, :source_type => 'User', :source => :user
|
490
|
+
end
|
491
|
+
|
492
|
+
# The class on which to call search_for
|
493
|
+
class User < ActiveRecord::Base
|
494
|
+
has_many :user_groups
|
495
|
+
has_many :groups, :through => :user_groups
|
496
|
+
|
497
|
+
scoped_search :relation => :groups, :on => :related
|
498
|
+
end
|
499
|
+
|
500
|
+
@user_1 = User.create!(:foo => 'foo')
|
501
|
+
@user_2 = User.create!(:foo => 'foo too')
|
502
|
+
@user_3 = User.create!(:foo => 'foo three')
|
503
|
+
|
504
|
+
@group_1 = Group.create(:related => 'value')
|
505
|
+
@group_2 = Group.create(:related => 'value too!')
|
506
|
+
|
507
|
+
@bar_1 = UserGroup.create!(:user => @user_1, :group => @group_1)
|
508
|
+
@bar_2 = UserGroup.create!(:user => @user_1)
|
509
|
+
@bar_3 = UserGroup.create!(:user => @user_2, :group => @group_1)
|
510
|
+
@bar_3 = UserGroup.create!(:user => @user_2, :group => @group_2)
|
511
|
+
@bar_3 = UserGroup.create!(:user => @user_2, :group => @group_2)
|
512
|
+
@bar_4 = UserGroup.create!(:user => @user_3)
|
513
|
+
end
|
514
|
+
|
515
|
+
after do
|
516
|
+
ActiveRecord::Migration.drop_table(:user_groups)
|
517
|
+
ActiveRecord::Migration.drop_table(:users)
|
518
|
+
ActiveRecord::Migration.drop_table(:groups)
|
519
|
+
ActiveRecord::Migration.drop_table(:conflicts)
|
520
|
+
end
|
521
|
+
|
522
|
+
it "should find the one record that is related based on forward groups relation" do
|
523
|
+
User.search_for('related=value AND related="value too!"').length.should == 1
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
474
527
|
|
475
528
|
context 'querying a :has_many => :through relation with modules' do
|
476
529
|
|
@@ -492,7 +545,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
492
545
|
has_many :bazs, :through => :mars
|
493
546
|
self.table_name = "zan_koos"
|
494
547
|
|
495
|
-
scoped_search :
|
548
|
+
scoped_search :relation => :bazs, :on => :related
|
496
549
|
end
|
497
550
|
end
|
498
551
|
|
@@ -16,7 +16,7 @@ ScopedSearch::RSpec::Database.test_databases.each do |db|
|
|
16
16
|
:description => :string
|
17
17
|
) do |klass|
|
18
18
|
klass.scoped_search :on => :string
|
19
|
-
klass.scoped_search :on => :another, :default_operator => :eq, :
|
19
|
+
klass.scoped_search :on => :another, :default_operator => :eq, :aliases => [:alias], :default_order => :desc
|
20
20
|
klass.scoped_search :on => :explicit, :only_explicit => true
|
21
21
|
klass.scoped_search :on => :description
|
22
22
|
end
|
data/spec/unit/ast_spec.rb
CHANGED
@@ -150,7 +150,7 @@ describe ScopedSearch::QueryLanguage::AST::OperatorNode do
|
|
150
150
|
end
|
151
151
|
|
152
152
|
it "should raise an error of the LHS is requested" do
|
153
|
-
lambda { @node.lhs }.should raise_error
|
153
|
+
lambda { @node.lhs }.should raise_error(ScopedSearch::Exception, "Operator does not have a LHS")
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
@@ -187,11 +187,11 @@ describe ScopedSearch::QueryLanguage::AST::OperatorNode do
|
|
187
187
|
end
|
188
188
|
|
189
189
|
it "should raise an error of the LHS is requested" do
|
190
|
-
lambda { @node.lhs }.should raise_error
|
190
|
+
lambda { @node.lhs }.should raise_error(ScopedSearch::Exception, "Operators with more than 2 children do not have LHS/RHS")
|
191
191
|
end
|
192
192
|
|
193
193
|
it "should raise an error of the RHS is requested" do
|
194
|
-
lambda { @node.rhs }.should raise_error
|
194
|
+
lambda { @node.rhs }.should raise_error(ScopedSearch::Exception, "Operators with more than 2 children do not have LHS/RHS")
|
195
195
|
end
|
196
196
|
end
|
197
197
|
end
|
@@ -9,6 +9,42 @@ describe ScopedSearch::Definition do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe ScopedSearch::Definition::Field do
|
12
|
+
describe '#initialize' do
|
13
|
+
it "should raise an exception with missing field or 'on' keyword" do
|
14
|
+
lambda {
|
15
|
+
@definition.define
|
16
|
+
}.should raise_error(ArgumentError, "Missing field or 'on' keyword argument")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should raise an exception with unknown keyword arguments" do
|
20
|
+
lambda {
|
21
|
+
@definition.define(:field, :nonexisting => 'foo')
|
22
|
+
}.should raise_error(ArgumentError, "Unknown arguments to scoped_search: nonexisting")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should alias :in to :relation" do
|
26
|
+
ActiveSupport::Deprecation.should_receive(:warn).with("'in' argument deprecated, prefer 'relation' since scoped_search 4.0.0", anything)
|
27
|
+
@definition.define(:field, :in => 'foo').relation.should eq('foo')
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should accept :relation" do
|
31
|
+
ActiveSupport::Deprecation.should_not_receive(:warn)
|
32
|
+
@definition.define(:field, :relation => 'foo').relation.should eq('foo')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should alias :alias to :aliases" do
|
36
|
+
ActiveSupport::Deprecation.should_receive(:warn).with("'alias' argument deprecated, prefer aliases: [..] since scoped_search 4.0.0", anything)
|
37
|
+
@definition.define(:field, :alias => 'foo')
|
38
|
+
@definition.fields.keys.should eq([:field, :foo])
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should accept :relation" do
|
42
|
+
ActiveSupport::Deprecation.should_not_receive(:warn)
|
43
|
+
@definition.define(:field, :aliases => ['foo'])
|
44
|
+
@definition.fields.keys.should eq([:field, :foo])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
12
48
|
describe '#column' do
|
13
49
|
it "should raise an exception when using an unknown field" do
|
14
50
|
lambda {
|
@@ -33,4 +33,26 @@ describe ScopedSearch::QueryBuilder do
|
|
33
33
|
@definition.klass.connection.stub("class").and_return(connection)
|
34
34
|
ScopedSearch::QueryBuilder.class_for(@definition).should == ScopedSearch::QueryBuilder::PostgreSQLAdapter
|
35
35
|
end
|
36
|
+
|
37
|
+
it "should validate value if validator selected" do
|
38
|
+
field = double('field')
|
39
|
+
field.stub(:only_explicit).and_return(true)
|
40
|
+
field.stub(:field).and_return(:test_field)
|
41
|
+
field.stub(:validator).and_return(->(_value) { false })
|
42
|
+
|
43
|
+
@definition.stub(:field_by_name).and_return(field)
|
44
|
+
|
45
|
+
lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field = test_val') }.should raise_error(ScopedSearch::QueryNotSupported)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should display custom error from validator" do
|
49
|
+
field = double('field')
|
50
|
+
field.stub(:only_explicit).and_return(true)
|
51
|
+
field.stub(:field).and_return(:test_field)
|
52
|
+
field.stub(:validator).and_return(->(_value) { raise ScopedSearch::QueryNotSupported, 'my custom message' })
|
53
|
+
|
54
|
+
@definition.stub(:field_by_name).and_return(field)
|
55
|
+
|
56
|
+
lambda { ScopedSearch::QueryBuilder.build_query(@definition, 'test_field = test_val') }.should raise_error('my custom message')
|
57
|
+
end
|
36
58
|
end
|
@@ -92,4 +92,24 @@ describe ScopedSearch::RailsHelper do
|
|
92
92
|
params[:order] = "field DESC"
|
93
93
|
sort("field")
|
94
94
|
end
|
95
|
+
|
96
|
+
context 'with ActionController::Parameters' do
|
97
|
+
let(:ac_params) { double('ActionController::Parameters') }
|
98
|
+
|
99
|
+
it "should call to_h on passed params object" do
|
100
|
+
should_receive(:url_for).with(
|
101
|
+
"controller" => "resources",
|
102
|
+
"action" => "search",
|
103
|
+
"walrus" => "unicorns",
|
104
|
+
"order" => "field ASC"
|
105
|
+
).and_return("/example")
|
106
|
+
|
107
|
+
params[:walrus] = "unicorns"
|
108
|
+
|
109
|
+
ac_params.should_receive(:respond_to?).with(:permit).and_return(true)
|
110
|
+
ac_params.should_receive(:to_h).and_return(params)
|
111
|
+
|
112
|
+
sort("field", url_options: ac_params)
|
113
|
+
end
|
114
|
+
end
|
95
115
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ScopedSearch::Validators do
|
4
|
+
describe 'NUMERIC' do
|
5
|
+
it 'should accept integer value' do
|
6
|
+
ScopedSearch::Validators::NUMERIC.call('123').should eq(true)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should accept float value' do
|
10
|
+
ScopedSearch::Validators::NUMERIC.call('123.5').should eq(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should reject string value' do
|
14
|
+
ScopedSearch::Validators::NUMERIC.call('abc').should eq(false)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'INTEGER' do
|
19
|
+
it 'should accept numeric value' do
|
20
|
+
ScopedSearch::Validators::INTEGER.call('123').should eq(true)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should reject string value' do
|
24
|
+
ScopedSearch::Validators::INTEGER.call('abc').should eq(false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scoped_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amos Benari
|
@@ -10,48 +10,48 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2016-
|
13
|
+
date: 2016-12-05 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 4.2.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
|
-
- -
|
26
|
+
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
28
|
+
version: 4.2.0
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: rspec
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
|
-
- - ~>
|
33
|
+
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
35
|
version: '3.0'
|
36
36
|
type: :development
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
|
-
- - ~>
|
40
|
+
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
version: '3.0'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: rake
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- -
|
47
|
+
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: '0'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- -
|
54
|
+
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '0'
|
57
57
|
description: |2
|
@@ -77,14 +77,11 @@ extra_rdoc_files:
|
|
77
77
|
- CONTRIBUTING.rdoc
|
78
78
|
- LICENSE
|
79
79
|
files:
|
80
|
-
- .gitignore
|
81
|
-
- .travis.yml
|
80
|
+
- ".gitignore"
|
81
|
+
- ".travis.yml"
|
82
82
|
- CHANGELOG.rdoc
|
83
83
|
- CONTRIBUTING.rdoc
|
84
84
|
- Gemfile
|
85
|
-
- Gemfile.activerecord32
|
86
|
-
- Gemfile.activerecord40
|
87
|
-
- Gemfile.activerecord41
|
88
85
|
- Gemfile.activerecord42
|
89
86
|
- Gemfile.activerecord50
|
90
87
|
- LICENSE
|
@@ -105,6 +102,7 @@ files:
|
|
105
102
|
- lib/scoped_search/query_language/tokenizer.rb
|
106
103
|
- lib/scoped_search/rails_helper.rb
|
107
104
|
- lib/scoped_search/railtie.rb
|
105
|
+
- lib/scoped_search/validators.rb
|
108
106
|
- lib/scoped_search/version.rb
|
109
107
|
- scoped_search.gemspec
|
110
108
|
- spec/database.jruby.yml
|
@@ -128,33 +126,34 @@ files:
|
|
128
126
|
- spec/unit/query_builder_spec.rb
|
129
127
|
- spec/unit/rails_helper_spec.rb
|
130
128
|
- spec/unit/tokenizer_spec.rb
|
129
|
+
- spec/unit/validators_spec.rb
|
131
130
|
homepage: https://github.com/wvanbergen/scoped_search/wiki
|
132
131
|
licenses:
|
133
132
|
- MIT
|
134
133
|
metadata: {}
|
135
134
|
post_install_message:
|
136
135
|
rdoc_options:
|
137
|
-
- --title
|
136
|
+
- "--title"
|
138
137
|
- scoped_search
|
139
|
-
- --main
|
138
|
+
- "--main"
|
140
139
|
- README.rdoc
|
141
|
-
- --line-numbers
|
142
|
-
- --inline-source
|
140
|
+
- "--line-numbers"
|
141
|
+
- "--inline-source"
|
143
142
|
require_paths:
|
144
143
|
- lib
|
145
144
|
required_ruby_version: !ruby/object:Gem::Requirement
|
146
145
|
requirements:
|
147
|
-
- -
|
146
|
+
- - ">="
|
148
147
|
- !ruby/object:Gem::Version
|
149
|
-
version:
|
148
|
+
version: 2.0.0
|
150
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
150
|
requirements:
|
152
|
-
- -
|
151
|
+
- - ">="
|
153
152
|
- !ruby/object:Gem::Version
|
154
153
|
version: '0'
|
155
154
|
requirements: []
|
156
155
|
rubyforge_project:
|
157
|
-
rubygems_version: 2.
|
156
|
+
rubygems_version: 2.6.8
|
158
157
|
signing_key:
|
159
158
|
specification_version: 4
|
160
159
|
summary: Easily search you ActiveRecord models with a simple query language using
|
@@ -181,3 +180,4 @@ test_files:
|
|
181
180
|
- spec/unit/query_builder_spec.rb
|
182
181
|
- spec/unit/rails_helper_spec.rb
|
183
182
|
- spec/unit/tokenizer_spec.rb
|
183
|
+
- spec/unit/validators_spec.rb
|
data/Gemfile.activerecord32
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
gemspec
|
3
|
-
|
4
|
-
gem 'activerecord', '~> 3.2.0'
|
5
|
-
|
6
|
-
platforms :jruby do
|
7
|
-
gem 'activerecord-jdbcsqlite3-adapter'
|
8
|
-
gem 'activerecord-jdbcmysql-adapter'
|
9
|
-
gem 'activerecord-jdbcpostgresql-adapter'
|
10
|
-
end
|
11
|
-
|
12
|
-
platforms :ruby do
|
13
|
-
gem 'sqlite3'
|
14
|
-
gem 'mysql2', '~> 0.3.11'
|
15
|
-
gem 'pg'
|
16
|
-
end
|
data/Gemfile.activerecord40
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
gemspec
|
3
|
-
|
4
|
-
gem 'activerecord', '~> 4.0.0'
|
5
|
-
|
6
|
-
platforms :jruby do
|
7
|
-
gem 'activerecord-jdbcsqlite3-adapter'
|
8
|
-
gem 'activerecord-jdbcmysql-adapter'
|
9
|
-
gem 'activerecord-jdbcpostgresql-adapter'
|
10
|
-
end
|
11
|
-
|
12
|
-
platforms :ruby do
|
13
|
-
gem 'sqlite3'
|
14
|
-
gem 'mysql2', '~> 0.3.11'
|
15
|
-
gem 'pg'
|
16
|
-
end
|
data/Gemfile.activerecord41
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
source 'https://rubygems.org'
|
2
|
-
gemspec
|
3
|
-
|
4
|
-
gem 'activerecord', '~> 4.1.0'
|
5
|
-
|
6
|
-
platforms :jruby do
|
7
|
-
gem 'activerecord-jdbcsqlite3-adapter'
|
8
|
-
gem 'activerecord-jdbcmysql-adapter'
|
9
|
-
gem 'activerecord-jdbcpostgresql-adapter'
|
10
|
-
end
|
11
|
-
|
12
|
-
platforms :ruby do
|
13
|
-
gem 'sqlite3'
|
14
|
-
gem 'mysql2', '~> 0.3.11'
|
15
|
-
gem 'pg'
|
16
|
-
end
|