pursuit 0.4.5 → 1.0.1
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/.github/workflows/rubygem.yaml +46 -0
- data/Gemfile +14 -13
- data/Gemfile.lock +14 -13
- data/README.md +210 -27
- data/bin/console +10 -0
- data/lib/pursuit/aggregate_modifier_not_found.rb +20 -0
- data/lib/pursuit/aggregate_modifier_required.rb +20 -0
- data/lib/pursuit/aggregate_modifiers_not_available.rb +13 -0
- data/lib/pursuit/attribute_not_found.rb +20 -0
- data/lib/pursuit/constants.rb +1 -1
- data/lib/pursuit/error.rb +7 -0
- data/lib/pursuit/predicate_parser.rb +181 -0
- data/lib/pursuit/predicate_search.rb +83 -0
- data/lib/pursuit/predicate_transform.rb +231 -0
- data/lib/pursuit/query_error.rb +7 -0
- data/lib/pursuit/simple_search.rb +64 -0
- data/lib/pursuit/term_parser.rb +44 -0
- data/lib/pursuit/term_search.rb +69 -0
- data/lib/pursuit/term_transform.rb +35 -0
- data/lib/pursuit.rb +18 -4
- data/pursuit.gemspec +4 -3
- data/spec/internal/app/models/application_record.rb +5 -0
- data/spec/internal/app/models/product.rb +25 -9
- data/spec/internal/app/models/product_category.rb +23 -1
- data/spec/internal/app/models/product_variation.rb +26 -1
- data/spec/lib/pursuit/predicate_parser_spec.rb +1604 -0
- data/spec/lib/pursuit/predicate_search_spec.rb +80 -0
- data/spec/lib/pursuit/predicate_transform_spec.rb +624 -0
- data/spec/lib/pursuit/simple_search_spec.rb +59 -0
- data/spec/lib/pursuit/term_parser_spec.rb +271 -0
- data/spec/lib/pursuit/term_search_spec.rb +71 -0
- data/spec/lib/pursuit/term_transform_spec.rb +105 -0
- metadata +47 -25
- data/.travis.yml +0 -26
- data/lib/pursuit/dsl.rb +0 -28
- data/lib/pursuit/railtie.rb +0 -13
- data/lib/pursuit/search.rb +0 -172
- data/lib/pursuit/search_options.rb +0 -86
- data/lib/pursuit/search_term_parser.rb +0 -46
- data/spec/lib/pursuit/dsl_spec.rb +0 -22
- data/spec/lib/pursuit/search_options_spec.rb +0 -146
- data/spec/lib/pursuit/search_spec.rb +0 -516
- data/spec/lib/pursuit/search_term_parser_spec.rb +0 -34
- data/travis/gemfiles/5.2.gemfile +0 -8
- data/travis/gemfiles/6.0.gemfile +0 -8
- data/travis/gemfiles/6.1.gemfile +0 -8
- data/travis/gemfiles/7.0.gemfile +0 -8
data/lib/pursuit/railtie.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pursuit
|
4
|
-
class Railtie < Rails::Railtie
|
5
|
-
initializer 'pursuit.active_record.inject_dsl' do
|
6
|
-
ActiveSupport.on_load(:active_record) do
|
7
|
-
require 'pursuit/dsl'
|
8
|
-
|
9
|
-
ActiveRecord::Base.include(Pursuit::DSL)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
data/lib/pursuit/search.rb
DELETED
@@ -1,172 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pursuit
|
4
|
-
class Search
|
5
|
-
# @return [SearchOptions] The options to use when building the search query.
|
6
|
-
#
|
7
|
-
attr_reader :options
|
8
|
-
|
9
|
-
# Create a new instance to search a specific ActiveRecord record class.
|
10
|
-
#
|
11
|
-
# @param options [SearchOptions]
|
12
|
-
#
|
13
|
-
def initialize(options)
|
14
|
-
@options = options
|
15
|
-
end
|
16
|
-
|
17
|
-
# Perform a search for the specified query.
|
18
|
-
#
|
19
|
-
# @param query [String] The query to transform into a SQL search.
|
20
|
-
# @return [ActiveRecord::Relation] The search results.
|
21
|
-
#
|
22
|
-
def perform(query)
|
23
|
-
options.record_class.where(build_arel(query))
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def build_arel(query)
|
29
|
-
parser = SearchTermParser.new(query, keys: options.keys)
|
30
|
-
unkeyed_arel = build_arel_for_unkeyed_term(parser.unkeyed_term)
|
31
|
-
keyed_arel = build_arel_for_keyed_terms(parser.keyed_terms)
|
32
|
-
|
33
|
-
if unkeyed_arel && keyed_arel
|
34
|
-
unkeyed_arel.and(keyed_arel)
|
35
|
-
else
|
36
|
-
unkeyed_arel || keyed_arel
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def build_arel_for_unkeyed_term(value)
|
41
|
-
return nil if value.blank?
|
42
|
-
|
43
|
-
sanitized_value = "%#{ActiveRecord::Base.sanitize_sql_like(value)}%"
|
44
|
-
options.unkeyed_attributes.reduce(nil) do |chain, (_attribute_name, node_builder)|
|
45
|
-
node = node_builder.call.matches(sanitized_value)
|
46
|
-
chain ? chain.or(node) : node
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def build_arel_for_keyed_terms(terms)
|
51
|
-
return nil if terms.blank?
|
52
|
-
|
53
|
-
terms.reduce(nil) do |chain, term|
|
54
|
-
attribute_name = term.key.to_sym
|
55
|
-
reflection = options.relations.key?(attribute_name) ? options.record_class.reflections[term.key] : nil
|
56
|
-
node = if reflection.present?
|
57
|
-
attribute_names = options.relations[attribute_name]
|
58
|
-
build_arel_for_reflection(reflection, attribute_names, term.operator, term.value)
|
59
|
-
else
|
60
|
-
node_builder = options.keyed_attributes[attribute_name]
|
61
|
-
build_arel_for_node(node_builder.call, term.operator, term.value)
|
62
|
-
end
|
63
|
-
|
64
|
-
chain ? chain.and(node) : node
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def build_arel_for_node(node, operator, value) # rubocop:disable Metrics/CyclomaticComplexity
|
69
|
-
sanitized_value = ActiveRecord::Base.sanitize_sql_like(value)
|
70
|
-
sanitized_value = sanitized_value.to_i if sanitized_value =~ /^[0-9]+$/
|
71
|
-
|
72
|
-
case operator
|
73
|
-
when '>' then node.gt(sanitized_value)
|
74
|
-
when '>=' then node.gteq(sanitized_value)
|
75
|
-
when '<' then node.lt(sanitized_value)
|
76
|
-
when '<=' then node.lteq(sanitized_value)
|
77
|
-
when '*=' then node.matches("%#{sanitized_value}%")
|
78
|
-
when '!*=' then node.does_not_match("%#{sanitized_value}%")
|
79
|
-
when '!=' then node.not_eq(sanitized_value)
|
80
|
-
when '=='
|
81
|
-
if value.present?
|
82
|
-
node.eq(sanitized_value)
|
83
|
-
else
|
84
|
-
node.eq(nil).or(node.eq(''))
|
85
|
-
end
|
86
|
-
else
|
87
|
-
raise "The operator '#{operator}' is not supported."
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def build_arel_for_reflection(reflection, attribute_names, operator, value)
|
92
|
-
nodes = build_arel_for_reflection_join(reflection)
|
93
|
-
count_nodes = build_arel_for_relation_count(nodes, operator, value) unless reflection.belongs_to?
|
94
|
-
return count_nodes if count_nodes.present?
|
95
|
-
|
96
|
-
match_nodes = attribute_names.reduce(nil) do |chain, attribute_name|
|
97
|
-
node = build_arel_for_node(reflection.klass.arel_table[attribute_name], operator, value)
|
98
|
-
chain ? chain.or(node) : node
|
99
|
-
end
|
100
|
-
|
101
|
-
return nil if match_nodes.blank?
|
102
|
-
|
103
|
-
nodes.where(match_nodes).project(1).exists
|
104
|
-
end
|
105
|
-
|
106
|
-
def build_arel_for_reflection_join(reflection)
|
107
|
-
if reflection.belongs_to?
|
108
|
-
build_arel_for_belongs_to_reflection_join(reflection)
|
109
|
-
else
|
110
|
-
build_arel_for_has_reflection_join(reflection)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def build_arel_for_belongs_to_reflection_join(reflection)
|
115
|
-
reflection_table = reflection.klass.arel_table
|
116
|
-
reflection_table.where(
|
117
|
-
reflection_table[reflection.join_primary_key].eq(
|
118
|
-
options.record_class.arel_table[reflection.join_foreign_key]
|
119
|
-
)
|
120
|
-
)
|
121
|
-
end
|
122
|
-
|
123
|
-
# rubocop:disable Metrics/AbcSize
|
124
|
-
def build_arel_for_has_reflection_join(reflection)
|
125
|
-
reflection_table = reflection.klass.arel_table
|
126
|
-
reflection_through = reflection.through_reflection
|
127
|
-
|
128
|
-
if reflection_through.present?
|
129
|
-
# :has_one through / :has_many through
|
130
|
-
reflection_through_table = reflection_through.klass.arel_table
|
131
|
-
reflection_table.join(reflection_through_table).on(
|
132
|
-
reflection_through_table[reflection.foreign_key].eq(reflection_table[reflection.klass.primary_key])
|
133
|
-
).where(
|
134
|
-
reflection_through_table[reflection_through.foreign_key].eq(
|
135
|
-
options.record_class.arel_table[options.record_class.primary_key]
|
136
|
-
)
|
137
|
-
)
|
138
|
-
else
|
139
|
-
# :has_one / :has_many
|
140
|
-
reflection_table.where(
|
141
|
-
reflection_table[reflection.foreign_key].eq(
|
142
|
-
options.record_class.arel_table[options.record_class.primary_key]
|
143
|
-
)
|
144
|
-
)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
# rubocop:enable Metrics/AbcSize
|
148
|
-
|
149
|
-
def build_arel_for_relation_count(nodes, operator, value) # rubocop:disable Metrics/CyclomaticComplexity
|
150
|
-
node_builder = proc do |klass|
|
151
|
-
count = ActiveRecord::Base.sanitize_sql_like(value).to_i
|
152
|
-
klass.new(nodes.project(Arel.star.count), count)
|
153
|
-
end
|
154
|
-
|
155
|
-
case operator
|
156
|
-
when '>' then node_builder.call(Arel::Nodes::GreaterThan)
|
157
|
-
when '>=' then node_builder.call(Arel::Nodes::GreaterThanOrEqual)
|
158
|
-
when '<' then node_builder.call(Arel::Nodes::LessThan)
|
159
|
-
when '<=' then node_builder.call(Arel::Nodes::LessThanOrEqual)
|
160
|
-
else
|
161
|
-
return nil unless value =~ /^([0-9]+)$/
|
162
|
-
|
163
|
-
case operator
|
164
|
-
when '==' then node_builder.call(Arel::Nodes::Equality)
|
165
|
-
when '!=' then node_builder.call(Arel::Nodes::NotEqual)
|
166
|
-
when '*=' then node_builder.call(Arel::Nodes::Matches)
|
167
|
-
when '!*=' then node_builder.call(Arel::Nodes::DoesNotMatch)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
@@ -1,86 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pursuit
|
4
|
-
class SearchOptions
|
5
|
-
# @return [Struct] The structure which holds the search options for a single attribute.
|
6
|
-
#
|
7
|
-
AttributeOptions = Struct.new(:keyed, :unkeyed, :block)
|
8
|
-
|
9
|
-
# @return [Class<ActiveRecord::Base>] The `ActiveRecord::Base` child class to search.
|
10
|
-
#
|
11
|
-
attr_reader :record_class
|
12
|
-
|
13
|
-
# @return [Hash<Symbol, Array<Symbol>>] The attribute names of the record's relatives which can be searched.
|
14
|
-
#
|
15
|
-
attr_reader :relations
|
16
|
-
|
17
|
-
# @return [Hash<Symbol, AttributeOptions>] The attributes which can be searched.
|
18
|
-
#
|
19
|
-
attr_reader :attributes
|
20
|
-
|
21
|
-
# Create a new `SearchOptions` and call the passed block to setup the options.
|
22
|
-
#
|
23
|
-
# @params record_class [Class<ActiveRecord::Base>]
|
24
|
-
# @params block [Proc]
|
25
|
-
#
|
26
|
-
def initialize(record_class, &block)
|
27
|
-
@record_class = record_class
|
28
|
-
@relations = {}
|
29
|
-
@attributes = {}
|
30
|
-
|
31
|
-
block&.call(self)
|
32
|
-
end
|
33
|
-
|
34
|
-
# @return [Hash<Symbol, Proc>] The attributes which can be queried using a keyed term.
|
35
|
-
#
|
36
|
-
def keyed_attributes
|
37
|
-
attributes.each_with_object({}) do |(name, options), keyed_attributes|
|
38
|
-
keyed_attributes[name] = options.block if options.keyed
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# @return [Hash<Symbol, Proc>] The attributes which can be queried using an unkeyed term.
|
43
|
-
#
|
44
|
-
def unkeyed_attributes
|
45
|
-
attributes.each_with_object({}) do |(name, options), unkeyed_attributes|
|
46
|
-
unkeyed_attributes[name] = options.block if options.unkeyed
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# @return [Array<String>] The collection of all possible attributes which can be used as a keyed term.
|
51
|
-
#
|
52
|
-
def keys
|
53
|
-
keys = relations.keys + attributes.select { |_, a| a.keyed }.keys
|
54
|
-
keys.map(&:to_s).uniq
|
55
|
-
end
|
56
|
-
|
57
|
-
# Add a relation to search.
|
58
|
-
#
|
59
|
-
# @param name [Symbol] The name of the relationship attribute.
|
60
|
-
# @param attribute_names [Splat] The name of the attributes within the relationship to search.
|
61
|
-
#
|
62
|
-
def relation(name, *attribute_names)
|
63
|
-
relations[name] = attribute_names
|
64
|
-
nil
|
65
|
-
end
|
66
|
-
|
67
|
-
# Add an attribute to search.
|
68
|
-
#
|
69
|
-
# @param term_name [Symbol] The keyed search term (can be an existing attribute, or a custom value when
|
70
|
-
# passing either the `attribute_name` or a block returning an Arel node).
|
71
|
-
# @param attribute_name [Symbol] The attribute name to search (defaults to the keyword, when left blank and no
|
72
|
-
# block is passed).
|
73
|
-
# @param keyed [Boolean] `true` when the attribute should be searchable using a keyed term,
|
74
|
-
# `false` otherwise.
|
75
|
-
# @param unkeyed [Boolean] `true` when the attribute should be searchable using an unkeyed term,
|
76
|
-
# `false` otherwise.
|
77
|
-
# @param block [Proc] A block which returns the Arel node to query against. When left blank, the
|
78
|
-
# matching attribute from `.arel_table` is queried instead.
|
79
|
-
#
|
80
|
-
def attribute(term_name, attribute_name = nil, keyed: true, unkeyed: true, &block)
|
81
|
-
block ||= -> { record_class.arel_table[attribute_name || term_name] }
|
82
|
-
attributes[term_name] = AttributeOptions.new(keyed, unkeyed, block)
|
83
|
-
nil
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Pursuit
|
4
|
-
class SearchTermParser
|
5
|
-
# @return [Struct] Represents a single keyed term extracted from a query.
|
6
|
-
#
|
7
|
-
KeyedTerm = Struct.new(:key, :operator, :value)
|
8
|
-
|
9
|
-
# @return [Array<Pursuit::SearchTermParser::KeyedTerm>] The keys which are permitted for use as keyed terms.
|
10
|
-
#
|
11
|
-
attr_reader :keyed_terms
|
12
|
-
|
13
|
-
# @return [String] The unkeyed term.
|
14
|
-
#
|
15
|
-
attr_reader :unkeyed_term
|
16
|
-
|
17
|
-
# Create a new search term parser by parsing the specified query into an 'unkeyed term' and 'keyed terms'.
|
18
|
-
#
|
19
|
-
# @param query [String] The query to parse.
|
20
|
-
# @param keys [Array<String>] The keys which are permitted for use as keyed terms.
|
21
|
-
#
|
22
|
-
def initialize(query, keys: [])
|
23
|
-
@keyed_terms = []
|
24
|
-
@unkeyed_term = query.gsub(/(\s+)?(\w+)(==|\*=|!=|!\*=|<=|>=|<|>)("([^"]+)?"|'([^']+)?'|[^\s]+)(\s+)?/) do |term|
|
25
|
-
key = Regexp.last_match(2)
|
26
|
-
next term unless keys.include?(key)
|
27
|
-
|
28
|
-
operator = Regexp.last_match(3)
|
29
|
-
value = Regexp.last_match(4)
|
30
|
-
value = value[1..-2] if value =~ /^(".*"|'.*')$/
|
31
|
-
|
32
|
-
@keyed_terms << KeyedTerm.new(key, operator, value)
|
33
|
-
|
34
|
-
# Both the starting and ending spaces surrounding the keyed term can be removed, so in this case we'll need to
|
35
|
-
# replace with a single space to ensure the unkeyed term's words are separated correctly.
|
36
|
-
if term =~ /^\s.*\s$/
|
37
|
-
' '
|
38
|
-
else
|
39
|
-
''
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
@unkeyed_term = @unkeyed_term.strip
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Pursuit::DSL do
|
4
|
-
subject(:record) { Product.new }
|
5
|
-
|
6
|
-
describe '.search_options' do
|
7
|
-
subject(:search_options) { record.class.search_options }
|
8
|
-
|
9
|
-
it { is_expected.to be_a(Pursuit::SearchOptions) }
|
10
|
-
end
|
11
|
-
|
12
|
-
describe '.search' do
|
13
|
-
subject(:search) { record.class.search('funky') }
|
14
|
-
|
15
|
-
let(:product_a) { Product.create!(title: 'Plain Shirt') }
|
16
|
-
let(:product_b) { Product.create!(title: 'Funky Shirt') }
|
17
|
-
|
18
|
-
it 'is expected to return the matching records' do
|
19
|
-
expect(search).to contain_exactly(product_b)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,146 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
RSpec.describe Pursuit::SearchOptions do
|
4
|
-
subject(:search_options) { described_class.new(Product) }
|
5
|
-
|
6
|
-
let(:title_length_node_builder) do
|
7
|
-
proc do
|
8
|
-
Arel::Nodes::NamedFunction.new('LENGTH', [Product.arel_table[:title]])
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
describe '#record_class' do
|
13
|
-
subject(:record_class) { search_options.record_class }
|
14
|
-
|
15
|
-
it 'is expected to eq the class passed during initialization' do
|
16
|
-
expect(record_class).to eq(Product)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
describe '#relations' do
|
21
|
-
subject(:relations) { search_options.relations }
|
22
|
-
|
23
|
-
before do
|
24
|
-
search_options.relation :variations, :title, :stock_status
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'is expected to contain the correct relations' do
|
28
|
-
expect(relations).to eq(variations: %i[title stock_status])
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
describe '#keyed_attributes' do
|
33
|
-
subject(:keyed_attributes) { search_options.keyed_attributes }
|
34
|
-
|
35
|
-
before do
|
36
|
-
search_options.attribute :title, keyed: false
|
37
|
-
search_options.attribute :title_length, &title_length_node_builder
|
38
|
-
search_options.attribute :description
|
39
|
-
search_options.attribute :rating, unkeyed: false
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'is expected to contain the correct keyed attributes' do
|
43
|
-
expect(keyed_attributes.keys).to contain_exactly(:title_length, :description, :rating)
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'is expected to set a default node builder for attributes declared without a block' do
|
47
|
-
expect(keyed_attributes[:description].call).to eq(Product.arel_table[:description])
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'is expected to set a custom node builder for attributes declared with a block' do
|
51
|
-
expect(keyed_attributes[:title_length]).to eq(title_length_node_builder)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '#unkeyed_attributes' do
|
56
|
-
subject(:unkeyed_attributes) { search_options.unkeyed_attributes }
|
57
|
-
|
58
|
-
before do
|
59
|
-
search_options.attribute :title, keyed: false
|
60
|
-
search_options.attribute :title_length, &title_length_node_builder
|
61
|
-
search_options.attribute :description
|
62
|
-
search_options.attribute :rating, unkeyed: false
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'is expected to contain the correct unkeyed attributes' do
|
66
|
-
expect(unkeyed_attributes.keys).to contain_exactly(:title, :title_length, :description)
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'is expected to set a default node builder for attributes declared without a block' do
|
70
|
-
expect(unkeyed_attributes[:title].call).to eq(Product.arel_table[:title])
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'is expected to set a custom node builder for attributes declared with a block' do
|
74
|
-
expect(unkeyed_attributes[:title_length]).to eq(title_length_node_builder)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe '#relation' do
|
79
|
-
subject(:relation) { search_options.relation(:variations, :title, :stock_status) }
|
80
|
-
|
81
|
-
it 'is expected to add the relation to #relations' do
|
82
|
-
expect { relation }.to change(search_options, :relations).from({}).to(variations: %i[title stock_status])
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
describe '#attribute' do
|
87
|
-
subject(:attribute) { search_options.attribute(:description) }
|
88
|
-
|
89
|
-
it { is_expected.to be_nil }
|
90
|
-
|
91
|
-
it 'is expected to add the attribute to #attributes' do
|
92
|
-
expect { attribute }.to change(search_options.attributes, :keys).from([]).to(%i[description])
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'is expected to allow keyed searching by default' do
|
96
|
-
attribute
|
97
|
-
expect(search_options.attributes[:description].keyed).to be(true)
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'is expected to allow unkeyed searching by default' do
|
101
|
-
attribute
|
102
|
-
expect(search_options.attributes[:description].unkeyed).to be(true)
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'is expected to query the #term_name attribute' do
|
106
|
-
attribute
|
107
|
-
expect(search_options.attributes[:description].block.call).to eq(Product.arel_table[:description])
|
108
|
-
end
|
109
|
-
|
110
|
-
context 'when passing the attribute name to search' do
|
111
|
-
subject(:attribute) { search_options.attribute(:desc, :description) }
|
112
|
-
|
113
|
-
it 'is expected to query the #attribute_name attribute' do
|
114
|
-
attribute
|
115
|
-
expect(search_options.attributes[:desc].block.call).to eq(Product.arel_table[:description])
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
context 'when passing :keyed eq false' do
|
120
|
-
subject(:attribute) { search_options.attribute(:description, keyed: false) }
|
121
|
-
|
122
|
-
it 'is expected to disallow keyed searching' do
|
123
|
-
attribute
|
124
|
-
expect(search_options.attributes[:description].keyed).to be(false)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
context 'when passing :unkeyed eq false' do
|
129
|
-
subject(:attribute) { search_options.attribute(:description, unkeyed: false) }
|
130
|
-
|
131
|
-
it 'is expected to disallow unkeyed searching' do
|
132
|
-
attribute
|
133
|
-
expect(search_options.attributes[:description].unkeyed).to be(false)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
context 'when passing a block' do
|
138
|
-
subject(:attribute) { search_options.attribute(:description, &title_length_node_builder) }
|
139
|
-
|
140
|
-
it 'is expected to query the result of the passed block' do
|
141
|
-
attribute
|
142
|
-
expect(search_options.attributes[:description].block).to eq(title_length_node_builder)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|