scoped_search 2.7.1 → 3.0.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/.travis.yml +14 -23
- data/CHANGELOG.rdoc +193 -0
- data/CONTRIBUTING.rdoc +38 -0
- data/{Gemfile.activerecord4 → Gemfile.activerecord40} +1 -1
- data/{Gemfile.activerecord2 → Gemfile.activerecord41} +2 -2
- data/README.rdoc +15 -20
- data/app/assets/stylesheets/scoped_search.scss +0 -4
- data/lib/scoped_search.rb +2 -27
- data/lib/scoped_search/auto_complete_builder.rb +40 -34
- data/lib/scoped_search/definition.rb +7 -5
- data/lib/scoped_search/query_builder.rb +3 -34
- data/lib/scoped_search/query_language.rb +0 -3
- data/lib/scoped_search/rails_helper.rb +45 -46
- data/lib/scoped_search/railtie.rb +11 -6
- data/lib/scoped_search/version.rb +1 -1
- data/scoped_search.gemspec +8 -7
- data/spec/integration/api_spec.rb +2 -41
- data/spec/integration/auto_complete_spec.rb +5 -5
- data/spec/integration/key_value_querying_spec.rb +9 -9
- data/spec/integration/ordinal_querying_spec.rb +46 -48
- data/spec/integration/relation_querying_spec.rb +30 -30
- data/spec/integration/set_query_spec.rb +7 -7
- data/spec/integration/string_querying_spec.rb +48 -50
- data/spec/lib/matchers.rb +2 -2
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/ast_spec.rb +3 -3
- metadata +26 -22
- data/init.rb +0 -1
@@ -1,17 +1,16 @@
|
|
1
1
|
module ScopedSearch
|
2
2
|
|
3
|
-
|
4
|
-
LOGICAL_INFIX_OPERATORS = ScopedSearch::QueryLanguage::Parser::LOGICAL_INFIX_OPERATORS
|
5
|
-
LOGICAL_PREFIX_OPERATORS = ScopedSearch::QueryLanguage::Parser::LOGICAL_PREFIX_OPERATORS
|
6
|
-
NULL_PREFIX_OPERATORS = ScopedSearch::QueryLanguage::Parser::NULL_PREFIX_OPERATORS
|
7
|
-
NULL_PREFIX_COMPLETER = ['has']
|
8
|
-
COMPARISON_OPERATORS = ScopedSearch::QueryLanguage::Parser::COMPARISON_OPERATORS
|
9
|
-
PREFIX_OPERATORS = LOGICAL_PREFIX_OPERATORS + NULL_PREFIX_OPERATORS
|
10
|
-
|
11
3
|
# The AutoCompleteBuilder class builds suggestions to complete query based on
|
12
4
|
# the query language syntax.
|
13
5
|
class AutoCompleteBuilder
|
14
6
|
|
7
|
+
LOGICAL_INFIX_OPERATORS = ScopedSearch::QueryLanguage::Parser::LOGICAL_INFIX_OPERATORS
|
8
|
+
LOGICAL_PREFIX_OPERATORS = ScopedSearch::QueryLanguage::Parser::LOGICAL_PREFIX_OPERATORS
|
9
|
+
NULL_PREFIX_OPERATORS = ScopedSearch::QueryLanguage::Parser::NULL_PREFIX_OPERATORS
|
10
|
+
NULL_PREFIX_COMPLETER = ['has']
|
11
|
+
COMPARISON_OPERATORS = ScopedSearch::QueryLanguage::Parser::COMPARISON_OPERATORS
|
12
|
+
PREFIX_OPERATORS = LOGICAL_PREFIX_OPERATORS + NULL_PREFIX_OPERATORS
|
13
|
+
|
15
14
|
attr_reader :ast, :definition, :query, :tokens
|
16
15
|
|
17
16
|
# This method will parse the query string and build suggestion list using the
|
@@ -174,10 +173,14 @@ module ScopedSearch
|
|
174
173
|
quoted_table = field.key_klass.connection.quote_table_name(field.key_klass.table_name)
|
175
174
|
quoted_field = field.key_klass.connection.quote_column_name(field.key_field)
|
176
175
|
field_name = "#{quoted_table}.#{quoted_field}"
|
177
|
-
select_clause = "DISTINCT #{field_name}"
|
178
|
-
opts = value_conditions(field_name, val).merge(:select => select_clause, :limit => 20)
|
179
176
|
|
180
|
-
field.key_klass
|
177
|
+
field.key_klass
|
178
|
+
.where(value_conditions(field_name, val))
|
179
|
+
.select("DISTINCT #{field_name}")
|
180
|
+
.limit(20)
|
181
|
+
.map(&field.key_field)
|
182
|
+
.compact
|
183
|
+
.map { |f| "#{name}.#{f} " }
|
181
184
|
end
|
182
185
|
|
183
186
|
# this method auto-completes values of fields that have a :complete_value marker
|
@@ -197,10 +200,13 @@ module ScopedSearch
|
|
197
200
|
return complete_date_value if field.temporal?
|
198
201
|
return complete_key_value(field, token, val) if field.key_field
|
199
202
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
203
|
+
completer_scope(field)
|
204
|
+
.where(value_conditions(field.quoted_field, val))
|
205
|
+
.select("DISTINCT #{field.quoted_field}")
|
206
|
+
.limit(20)
|
207
|
+
.map(&field.field)
|
208
|
+
.compact
|
209
|
+
.map { |v| v.to_s =~ /\s/ ? "\"#{v}\"" : v }
|
204
210
|
end
|
205
211
|
|
206
212
|
def completer_scope(field)
|
@@ -215,7 +221,7 @@ module ScopedSearch
|
|
215
221
|
end
|
216
222
|
# date value completer
|
217
223
|
def complete_date_value
|
218
|
-
options =[]
|
224
|
+
options = []
|
219
225
|
options << '"30 minutes ago"'
|
220
226
|
options << '"1 hour ago"'
|
221
227
|
options << '"2 hours ago"'
|
@@ -233,34 +239,34 @@ module ScopedSearch
|
|
233
239
|
# complete values in a key-value schema
|
234
240
|
def complete_key_value(field, token, val)
|
235
241
|
key_name = token.sub(/^.*\./,"")
|
236
|
-
|
237
|
-
key_klass = field.key_klass.first(key_opts)
|
242
|
+
key_klass = field.key_klass.where(field.key_field => key_name).first
|
238
243
|
raise ScopedSearch::QueryNotSupported, "Field '#{key_name}' not recognized for searching!" if key_klass.nil?
|
239
244
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
opts.merge!(key_opts)
|
245
|
+
query = completer_scope(field)
|
246
|
+
|
247
|
+
if field.key_klass != field.klass
|
248
|
+
key = field.key_klass.to_s.gsub(/.*::/,'').underscore.to_sym
|
249
|
+
fk = field.klass.reflections[key].association_foreign_key.to_sym
|
250
|
+
query = query.where(fk => key_klass.id)
|
247
251
|
end
|
248
|
-
|
252
|
+
|
253
|
+
query
|
254
|
+
.where(value_conditions(field, val))
|
255
|
+
.select("DISTINCT #{field.quoted_field}")
|
256
|
+
.limit(20)
|
257
|
+
.map(&field.field)
|
258
|
+
.compact
|
259
|
+
.map { |v| v.to_s =~ /\s/ ? "\"#{v}\"" : v }
|
249
260
|
end
|
250
261
|
|
251
|
-
#
|
252
|
-
def value_conditions(
|
253
|
-
|
262
|
+
# This method returns conditions for selecting completion from partial value
|
263
|
+
def value_conditions(field, val)
|
264
|
+
val.blank? ? nil : "#{field.quoted_field} LIKE '#{val.gsub("'","''")}%'".tr_s('%*', '%')
|
254
265
|
end
|
255
266
|
|
256
267
|
# This method complete infix operators by field type
|
257
268
|
def complete_operator(node)
|
258
269
|
definition.operator_by_field_name(node.value)
|
259
270
|
end
|
260
|
-
|
261
271
|
end
|
262
|
-
|
263
272
|
end
|
264
|
-
|
265
|
-
# Load lib files
|
266
|
-
require 'scoped_search/query_builder'
|
@@ -84,7 +84,11 @@ module ScopedSearch
|
|
84
84
|
if klass.columns_hash.has_key?(field.to_s)
|
85
85
|
klass.columns_hash[field.to_s]
|
86
86
|
else
|
87
|
-
|
87
|
+
if "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}".to_f < 4.1
|
88
|
+
raise ActiveRecord::UnknownAttributeError, "#{klass.inspect} doesn't have column #{field.inspect}."
|
89
|
+
else
|
90
|
+
raise ActiveRecord::UnknownAttributeError.new( klass, field )
|
91
|
+
end
|
88
92
|
end
|
89
93
|
end
|
90
94
|
end
|
@@ -243,8 +247,6 @@ module ScopedSearch
|
|
243
247
|
definition = self
|
244
248
|
if @klass.ancestors.include?(ActiveRecord::Base)
|
245
249
|
case ActiveRecord::VERSION::MAJOR
|
246
|
-
when 2
|
247
|
-
@klass.named_scope(:search_for, lambda { |*args| ScopedSearch::QueryBuilder.build_query(definition, args[0], args[1]) })
|
248
250
|
when 3
|
249
251
|
@klass.scope(:search_for, lambda { |*args|
|
250
252
|
find_options = ScopedSearch::QueryBuilder.build_query(definition, args[0], args[1])
|
@@ -258,7 +260,7 @@ module ScopedSearch
|
|
258
260
|
when 4
|
259
261
|
@klass.scope(:search_for, lambda { |*args|
|
260
262
|
find_options = ScopedSearch::QueryBuilder.build_query(definition, args[0], args[1])
|
261
|
-
search_scope = @klass
|
263
|
+
search_scope = @klass
|
262
264
|
search_scope = search_scope.where(find_options[:conditions]) if find_options[:conditions]
|
263
265
|
search_scope = search_scope.includes(find_options[:include]) if find_options[:include]
|
264
266
|
search_scope = search_scope.references(find_options[:include]) if find_options[:include]
|
@@ -270,7 +272,7 @@ module ScopedSearch
|
|
270
272
|
raise "This ActiveRecord version is currently not supported!"
|
271
273
|
end
|
272
274
|
else
|
273
|
-
raise "Currently, only ActiveRecord
|
275
|
+
raise "Currently, only ActiveRecord 3 or newer is supported!"
|
274
276
|
end
|
275
277
|
end
|
276
278
|
|
@@ -479,20 +479,6 @@ module ScopedSearch
|
|
479
479
|
# The MysqlAdapter makes sure that case sensitive comparisons are used
|
480
480
|
# when using the (not) equals operator, regardless of the field's
|
481
481
|
# collation setting.
|
482
|
-
class MysqlAdapter < ScopedSearch::QueryBuilder
|
483
|
-
|
484
|
-
# Patches the default <tt>sql_operator</tt> method to add
|
485
|
-
# <tt>BINARY</tt> after the equals and not equals operator to force
|
486
|
-
# case-sensitive comparisons.
|
487
|
-
def sql_operator(operator, field)
|
488
|
-
if [:ne, :eq].include?(operator) && field.textual?
|
489
|
-
"#{SQL_OPERATORS[operator]} BINARY"
|
490
|
-
else
|
491
|
-
super(operator, field)
|
492
|
-
end
|
493
|
-
end
|
494
|
-
end
|
495
|
-
|
496
482
|
class Mysql2Adapter < ScopedSearch::QueryBuilder
|
497
483
|
# Patches the default <tt>sql_operator</tt> method to add
|
498
484
|
# <tt>BINARY</tt> after the equals and not equals operator to force
|
@@ -506,6 +492,8 @@ module ScopedSearch
|
|
506
492
|
end
|
507
493
|
end
|
508
494
|
|
495
|
+
MysqlAdapter = Mysql2Adapter
|
496
|
+
|
509
497
|
# The PostgreSQLAdapter make sure that searches are case sensitive when
|
510
498
|
# using the like/unlike operators, by using the PostrgeSQL-specific
|
511
499
|
# <tt>ILIKE operator</tt> instead of <tt>LIKE</tt>.
|
@@ -525,7 +513,7 @@ module ScopedSearch
|
|
525
513
|
end
|
526
514
|
end
|
527
515
|
|
528
|
-
# Switches out the default LIKE operator in the default <tt>sql_operator</tt>
|
516
|
+
# Switches out the default LIKE operator in the default <tt>sql_operator</tt>
|
529
517
|
# method for ILIKE or @@ if full text searching is enabled.
|
530
518
|
def sql_operator(operator, field)
|
531
519
|
raise ScopedSearch::QueryNotSupported, "the operator '#{operator}' is not supported for field type '#{field.type}'" if [:like, :unlike].include?(operator) and !field.textual?
|
@@ -548,25 +536,6 @@ module ScopedSearch
|
|
548
536
|
sql
|
549
537
|
end
|
550
538
|
end
|
551
|
-
|
552
|
-
# The Oracle adapter also requires some tweaks to make the case insensitive LIKE work.
|
553
|
-
class OracleEnhancedAdapter < ScopedSearch::QueryBuilder
|
554
|
-
|
555
|
-
def sql_test(field, operator, value, lhs, &block) # :yields: finder_option_type, value
|
556
|
-
if field.key_field
|
557
|
-
yield(:parameter, lhs.sub(/^.*\./,''))
|
558
|
-
end
|
559
|
-
if field.textual? && [:like, :unlike].include?(operator)
|
560
|
-
yield(:parameter, (value !~ /^\%|\*/ && value !~ /\%|\*$/) ? "%#{value}%" : value.to_s.tr_s('%*', '%'))
|
561
|
-
return "LOWER(#{field.to_sql(operator, &block)}) #{self.sql_operator(operator, field)} LOWER(?)"
|
562
|
-
elsif field.temporal?
|
563
|
-
return datetime_test(field, operator, value, &block)
|
564
|
-
else
|
565
|
-
yield(:parameter, value)
|
566
|
-
return "#{field.to_sql(operator, &block)} #{self.sql_operator(operator, field)} ?"
|
567
|
-
end
|
568
|
-
end
|
569
|
-
end
|
570
539
|
end
|
571
540
|
|
572
541
|
# Include the modules into the corresponding classes
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module ScopedSearch
|
3
2
|
module RailsHelper
|
4
3
|
# Creates a link that alternates between ascending and descending.
|
@@ -36,7 +35,7 @@ module ScopedSearch
|
|
36
35
|
|
37
36
|
if selected
|
38
37
|
css_classes = html_options[:class] ? html_options[:class].split(" ") : []
|
39
|
-
if new_sort == ascend
|
38
|
+
if new_sort == ascend
|
40
39
|
options[:as] = "▲ #{options[:as]}"
|
41
40
|
css_classes << "ascending"
|
42
41
|
else
|
@@ -124,52 +123,51 @@ module ScopedSearch
|
|
124
123
|
end
|
125
124
|
|
126
125
|
def auto_complete_field_jquery(method, url, options = {})
|
127
|
-
function = <<-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
} else {
|
145
|
-
if(typeof(self._renderItemData) === "function") {
|
146
|
-
self._renderItemData( ul, item );
|
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 );
|
147
143
|
} else {
|
148
|
-
self.
|
144
|
+
if(typeof(self._renderItemData) === "function") {
|
145
|
+
self._renderItemData( ul, item );
|
146
|
+
} else {
|
147
|
+
self._renderItem( ul, item );
|
148
|
+
}
|
149
149
|
}
|
150
|
-
}
|
151
|
-
}
|
152
|
-
}
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
EOF
|
172
|
-
|
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
|
173
171
|
|
174
172
|
javascript_tag(function)
|
175
173
|
end
|
@@ -218,6 +216,7 @@ module ScopedSearch
|
|
218
216
|
text_field_tag(method, val, options) + auto_complete_clear_value_button(method) +
|
219
217
|
auto_complete_field_jquery(method, url, completion_options)
|
220
218
|
end
|
219
|
+
deprecate :auto_complete_field_tag_jquery, :auto_complete_field_tag, :auto_complete_result
|
221
220
|
|
222
221
|
end
|
223
222
|
end
|
@@ -1,8 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'scoped_search/engine'
|
2
|
+
|
3
|
+
module ScopedSearch
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
initializer "scoped_search.setup_rails_helper" do |app|
|
7
|
+
ActiveSupport.on_load :action_controller do
|
8
|
+
require "scoped_search/rails_helper"
|
9
|
+
ActionController::Base.helper(ScopedSearch::RailsHelper)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
8
13
|
end
|
data/scoped_search.gemspec
CHANGED
@@ -12,26 +12,27 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.summary = %q{Easily search you ActiveRecord models with a simple query language using a named scope}
|
13
13
|
gem.description = <<-EOS
|
14
14
|
Scoped search makes it easy to search your ActiveRecord-based models.
|
15
|
-
|
15
|
+
|
16
16
|
It will create a named scope :search_for that can be called with a query string. It will build an SQL query using
|
17
17
|
the provided query string and a definition that specifies on what fields to search. Because the functionality is
|
18
18
|
built on named_scope, the result of the search_for call can be used like any other named_scope, so it can be
|
19
19
|
chained with another scope or combined with will_paginate.
|
20
|
-
|
20
|
+
|
21
21
|
Because it uses standard SQL, it does not require any setup, indexers or daemons. This makes scoped_search
|
22
22
|
suitable to quickly add basic search functionality to your application with little hassle. On the other hand,
|
23
23
|
it may not be the best choice if it is going to be used on very large datasets or by a large user base.
|
24
24
|
EOS
|
25
|
-
|
25
|
+
|
26
26
|
gem.files = `git ls-files`.split($/)
|
27
27
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
28
28
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
29
29
|
gem.require_paths = ["lib"]
|
30
30
|
|
31
|
-
gem.
|
32
|
-
gem.
|
33
|
-
gem.add_development_dependency('
|
31
|
+
gem.required_ruby_version = '>= 1.9.3'
|
32
|
+
gem.add_runtime_dependency('activerecord', '>= 3.0.0')
|
33
|
+
gem.add_development_dependency('rspec', '~> 3.0')
|
34
|
+
gem.add_development_dependency('rake')
|
34
35
|
|
35
36
|
gem.rdoc_options << '--title' << gem.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
|
36
|
-
gem.extra_rdoc_files = ['README.rdoc']
|
37
|
+
gem.extra_rdoc_files = ['README.rdoc', 'CHANGELOG.rdoc', 'CONTRIBUTING.rdoc', 'LICENSE']
|
37
38
|
end
|
@@ -44,54 +44,15 @@ describe ScopedSearch, "API" do
|
|
44
44
|
@class.should respond_to(:search_for)
|
45
45
|
end
|
46
46
|
|
47
|
-
if ActiveRecord::VERSION::MAJOR ==
|
48
|
-
|
49
|
-
it "should return a ActiveRecord::NamedScope::Scope instance" do
|
50
|
-
@class.search_for('query').class.should eql(ActiveRecord::NamedScope::Scope)
|
51
|
-
end
|
52
|
-
|
53
|
-
elsif ActiveRecord::VERSION::MAJOR == 3
|
54
|
-
|
47
|
+
if ActiveRecord::VERSION::MAJOR == 3
|
55
48
|
it "should return a ActiveRecord::Relation instance" do
|
56
49
|
@class.search_for('query').class.should eql(ActiveRecord::Relation)
|
57
50
|
end
|
58
|
-
|
51
|
+
|
59
52
|
elsif ActiveRecord::VERSION::MAJOR == 4
|
60
|
-
|
61
53
|
it "should return a ActiveRecord::Relation instance" do
|
62
54
|
@class.search_for('query').class.superclass.should eql(ActiveRecord::Relation)
|
63
55
|
end
|
64
|
-
|
65
56
|
end
|
66
57
|
end
|
67
|
-
|
68
|
-
context 'having backwards compatibility' do
|
69
|
-
|
70
|
-
before(:each) do
|
71
|
-
class ::Foo < ActiveRecord::Base
|
72
|
-
belongs_to :bar
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
after(:each) do
|
77
|
-
Object.send :remove_const, :Foo
|
78
|
-
end
|
79
|
-
|
80
|
-
it "should respond to :searchable_on" do
|
81
|
-
Foo.should respond_to(:searchable_on)
|
82
|
-
end
|
83
|
-
|
84
|
-
it "should create a Field instance for every argument" do
|
85
|
-
ScopedSearch::Definition::Field.should_receive(:new).exactly(3).times
|
86
|
-
Foo.searchable_on(:field_1, :field_2, :field_3)
|
87
|
-
end
|
88
|
-
|
89
|
-
it "should create a Field with a valid relation when using the underscore notation" do
|
90
|
-
ScopedSearch::Definition::Field.should_receive(:new).with(
|
91
|
-
instance_of(ScopedSearch::Definition), hash_including(:in => :bar, :on => :related_field))
|
92
|
-
Foo.searchable_on(:bar_related_field)
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
58
|
end
|