pursuit 1.0.1 → 1.1.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/gemfiles/ruby-2.6.gemfile +14 -0
- data/.github/gemfiles/ruby-2.7.gemfile +13 -0
- data/.github/gemfiles/ruby-3.0.gemfile +13 -0
- data/.github/gemfiles/ruby-3.1.gemfile +13 -0
- data/.github/gemfiles/ruby-3.2.gemfile +13 -0
- data/.github/gemfiles/ruby-3.3.gemfile +13 -0
- data/.github/gemfiles/ruby-3.4.gemfile +13 -0
- data/.github/gemfiles/ruby-head.gemfile +13 -0
- data/.github/gemfiles/ruby-jruby-head.gemfile +13 -0
- data/.github/gemfiles/ruby-jruby.gemfile +13 -0
- data/.github/gemfiles/ruby-truffleruby-head.gemfile +13 -0
- data/.github/gemfiles/ruby-truffleruby.gemfile +13 -0
- data/.github/workflows/continuous-integration.yaml +65 -0
- data/.rbenv-gemsets +1 -1
- data/.ruby-version +1 -1
- data/Gemfile +7 -7
- data/Gemfile.lock +144 -124
- data/README.md +21 -19
- data/lib/pursuit/constants.rb +1 -1
- data/lib/pursuit/predicate_parser.rb +4 -4
- data/lib/pursuit/predicate_search.rb +26 -23
- data/lib/pursuit/predicate_transform.rb +1 -1
- data/lib/pursuit/simple_search.rb +15 -13
- data/lib/pursuit/term_search.rb +18 -13
- data/pursuit.gemspec +11 -11
- data/spec/internal/app/models/product.rb +2 -29
- data/spec/internal/app/models/product_category.rb +2 -22
- data/spec/internal/app/models/product_variation.rb +7 -26
- data/spec/internal/app/searches/product_category_search.rb +50 -0
- data/spec/internal/app/searches/product_search.rb +53 -0
- data/spec/internal/app/searches/product_variation_search.rb +53 -0
- data/spec/lib/pursuit/predicate_search_spec.rb +65 -29
- data/spec/lib/pursuit/predicate_transform_spec.rb +16 -1
- data/spec/lib/pursuit/simple_search_spec.rb +33 -19
- data/spec/lib/pursuit/term_search_spec.rb +33 -19
- metadata +19 -9
- data/.github/workflows/rubygem.yaml +0 -46
- data/travis/gemfiles/7.1.gemfile +0 -8
data/README.md
CHANGED
@@ -6,7 +6,9 @@ Search your ActiveRecord objects with ease!
|
|
6
6
|
|
7
7
|
You can install **Pursuit** using the following command:
|
8
8
|
|
9
|
-
|
9
|
+
```sh
|
10
|
+
$ gem install pursuit
|
11
|
+
```
|
10
12
|
|
11
13
|
Or, by adding the following to your `Gemfile`:
|
12
14
|
|
@@ -28,10 +30,10 @@ Simple takes the entire query and generates a SQL `LIKE` (or `ILIKE` for *Postgr
|
|
28
30
|
added to the search instance. Here's an example of how you might use simple to search a hypothetical `Product` record:
|
29
31
|
|
30
32
|
```ruby
|
31
|
-
search = Pursuit::SimpleSearch.new(Product.
|
33
|
+
search = Pursuit::SimpleSearch.new(default_table: Product.arel_table)
|
32
34
|
search.search_attribute(:title)
|
33
35
|
search.search_attribute(:subtitle)
|
34
|
-
search.apply('Green Shirt')
|
36
|
+
search.apply('Green Shirt', Product.all)
|
35
37
|
```
|
36
38
|
|
37
39
|
Which results in the following SQL query:
|
@@ -50,25 +52,23 @@ The initializer method also accepts a block, which is evaluated within the insta
|
|
50
52
|
when declaring the searchable attributes:
|
51
53
|
|
52
54
|
```ruby
|
53
|
-
search = Pursuit::SimpleSearch.new(Product.
|
55
|
+
search = Pursuit::SimpleSearch.new(default_table: Product.arel_table) do
|
54
56
|
search_attribute :title
|
55
57
|
search_attribute :subtitle
|
56
58
|
end
|
57
59
|
|
58
|
-
search.apply('Green Shirt')
|
60
|
+
search.apply('Green Shirt', Product.all)
|
59
61
|
```
|
60
62
|
|
61
63
|
You can also pass custom `Arel::Attribute::Attribute` objects, which are especially useful when using joins:
|
62
64
|
|
63
65
|
```ruby
|
64
|
-
search = Pursuit::SimpleSearch.new(
|
65
|
-
Product.left_outer_joins(:variations).group(:id)
|
66
|
-
) do
|
66
|
+
search = Pursuit::SimpleSearch.new(default_table: Product.arel_table) do
|
67
67
|
search_attribute :title
|
68
68
|
search_attribute ProductVariation.arel_table[:title]
|
69
69
|
end
|
70
70
|
|
71
|
-
search.apply('Green Shirt')
|
71
|
+
search.apply('Green Shirt', Product.left_outer_joins(:variations).group(:id))
|
72
72
|
```
|
73
73
|
|
74
74
|
Which results in the following SQL query:
|
@@ -92,12 +92,12 @@ Term searches break a query into individual terms on spaces, while providing dou
|
|
92
92
|
means to include spaces. Here's an example of using term searches on the same `Product` record from earlier:
|
93
93
|
|
94
94
|
```ruby
|
95
|
-
search = Pursuit::TermSearch.new(Product.
|
95
|
+
search = Pursuit::TermSearch.new(default_table: Product.arel_table) do
|
96
96
|
search_attribute :title
|
97
97
|
search_attribute :subtitle
|
98
98
|
end
|
99
99
|
|
100
|
-
search.apply('Green "Luxury Shirt"')
|
100
|
+
search.apply('Green "Luxury Shirt"', Product.all)
|
101
101
|
```
|
102
102
|
|
103
103
|
Which results in a SQL query similar to the following:
|
@@ -128,9 +128,7 @@ You can also rename attributes, and add attributes for joined records.
|
|
128
128
|
Here's a more complex example of using predicate-based searches with joins on the `Product` record from earlier:
|
129
129
|
|
130
130
|
```ruby
|
131
|
-
search = Pursuit::PredicateSearch.new(
|
132
|
-
Product.left_outer_join(:category, :variations).group(:id)
|
133
|
-
) do
|
131
|
+
search = Pursuit::PredicateSearch.new(default_table: Product.arel_table) do
|
134
132
|
# Product Attributes
|
135
133
|
permit_attribute :title
|
136
134
|
|
@@ -143,7 +141,10 @@ search = Pursuit::PredicateSearch.new(
|
|
143
141
|
permit_attribute :variation_amount, ProductVariation.arel_table[:amount]
|
144
142
|
end
|
145
143
|
|
146
|
-
search.apply(
|
144
|
+
search.apply(
|
145
|
+
'title = "Luxury Shirt" & (variation_amount = 0 | variation_amount > 1000)',
|
146
|
+
Product.left_outer_join(:category, :variations).group(:id)
|
147
|
+
)
|
147
148
|
```
|
148
149
|
|
149
150
|
This translates to "a product whose title is 'Luxury Shirt' and has at least one variation with either an amount of 0,
|
@@ -181,9 +182,7 @@ Predicate searches also support "aggregate modifiers" which enable the use of ag
|
|
181
182
|
must be explicitly enabled and requires you to use a `GROUP BY` clause:
|
182
183
|
|
183
184
|
```ruby
|
184
|
-
search = Pursuit::PredicateSearch.new(
|
185
|
-
Product.left_outer_join(:category, :variations).group(:id)
|
186
|
-
) do
|
185
|
+
search = Pursuit::PredicateSearch.new(default_table: Product.arel_table, permit_aggregate_modifiers: true) do
|
187
186
|
# Product Attributes
|
188
187
|
permit_attribute :title
|
189
188
|
|
@@ -198,7 +197,10 @@ search = Pursuit::PredicateSearch.new(
|
|
198
197
|
permit_attribute :variation_amount, ProductVariation.arel_table[:amount]
|
199
198
|
end
|
200
199
|
|
201
|
-
search.apply(
|
200
|
+
search.apply(
|
201
|
+
'title = "Luxury Shirt" & #variation > 5',
|
202
|
+
Product.left_outer_join(:category, :variations).group(:id)
|
203
|
+
)
|
202
204
|
```
|
203
205
|
|
204
206
|
And the resulting SQL from this query:
|
data/lib/pursuit/constants.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pursuit
|
4
|
-
# Parser for predicate
|
4
|
+
# Parser for predicate queries.
|
5
5
|
#
|
6
|
-
# Predicate
|
7
|
-
# value to compare with.
|
6
|
+
# Predicate queries take an attribute, an operator (such as the equal sign), and a value to compare with.
|
8
7
|
#
|
9
8
|
# For example, to search for records where the `first_name` attribute is equal to "John" and the `last_name`
|
10
|
-
# attribute contains either "Doe" or "Smith", you
|
9
|
+
# attribute contains either "Doe" or "Smith", you might use:
|
10
|
+
#
|
11
11
|
# => "first_name = John & (last_name ~ Doe | last_name ~ Smith)"
|
12
12
|
#
|
13
13
|
class PredicateParser < Parslet::Parser
|
@@ -1,29 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pursuit
|
4
|
-
#
|
4
|
+
# Provides an interface for declaring which attributes can be used in a predicate query, and a method for applying
|
5
|
+
# a predicate query to an `ActiveRecord::Relation` instance.
|
6
|
+
#
|
7
|
+
# @see Pursuit::PredicateParser
|
8
|
+
# @see Pursuit::PredicateTransform
|
5
9
|
#
|
6
10
|
class PredicateSearch
|
7
|
-
# @return [
|
11
|
+
# @return [Arel::Table] The default table to retrieve attributes from.
|
8
12
|
#
|
9
|
-
attr_accessor :
|
13
|
+
attr_accessor :default_table
|
10
14
|
|
11
|
-
# @return [
|
15
|
+
# @return [Boolean] `true` when aggregate modifiers can be used, `false` otherwise.
|
12
16
|
#
|
13
|
-
|
17
|
+
attr_accessor :permit_aggregate_modifiers
|
14
18
|
|
15
|
-
# @return [
|
19
|
+
# @return [Hash<Symbol, Arel::Attributes::Attribute>] The attributes permitted for use in queries.
|
16
20
|
#
|
17
|
-
|
21
|
+
attr_accessor :permitted_attributes
|
18
22
|
|
19
23
|
# Creates a new predicate search instance.
|
20
24
|
#
|
21
|
-
# @param
|
22
|
-
# @param permit_aggregate_modifiers [Boolean]
|
23
|
-
# @param block [Proc]
|
25
|
+
# @param default_table [Arel::Table] The default table to retrieve attributes from.
|
26
|
+
# @param permit_aggregate_modifiers [Boolean] `true` when aggregate modifiers can be used, `false` otherwise.
|
27
|
+
# @param block [Proc] The proc to invoke in the search instance (optional).
|
24
28
|
#
|
25
|
-
def initialize(
|
26
|
-
@
|
29
|
+
def initialize(default_table: nil, permit_aggregate_modifiers: false, &block)
|
30
|
+
@default_table = default_table
|
27
31
|
@permit_aggregate_modifiers = permit_aggregate_modifiers
|
28
32
|
@permitted_attributes = HashWithIndifferentAccess.new
|
29
33
|
|
@@ -49,8 +53,8 @@ module Pursuit
|
|
49
53
|
# @return [Arel::Attributes::Attribute] The underlying attribute to query.
|
50
54
|
#
|
51
55
|
def permit_attribute(name, attribute = nil)
|
52
|
-
attribute =
|
53
|
-
permitted_attributes[name] = attribute ||
|
56
|
+
attribute = default_table[attribute] if attribute.is_a?(Symbol)
|
57
|
+
permitted_attributes[name] = attribute || default_table[name]
|
54
58
|
end
|
55
59
|
|
56
60
|
# Parse a predicate query into ARel nodes.
|
@@ -59,22 +63,21 @@ module Pursuit
|
|
59
63
|
# @return [Hash<Symbol, Arel::Nodes::Node>] The ARel nodes representing the predicate query.
|
60
64
|
#
|
61
65
|
def parse(query)
|
62
|
-
tree = parser.parse(query)
|
63
66
|
transform.apply(
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
+
parser.parse(query),
|
68
|
+
permit_aggregate_modifiers: permit_aggregate_modifiers,
|
69
|
+
permitted_attributes: permitted_attributes
|
67
70
|
)
|
68
71
|
end
|
69
72
|
|
70
|
-
#
|
73
|
+
# Applies the predicate clauses derived from `query` to `relation`.
|
71
74
|
#
|
72
|
-
# @param query
|
73
|
-
# @
|
75
|
+
# @param query [String] The predicate query.
|
76
|
+
# @param relation [ActiveRecord::Relation] The base relation to apply the predicate clauses to.
|
77
|
+
# @return [ActiveRecord::Relation] The base relation with the predicate clauses applied.
|
74
78
|
#
|
75
|
-
def apply(query)
|
79
|
+
def apply(query, relation)
|
76
80
|
nodes = parse(query)
|
77
|
-
relation = self.relation
|
78
81
|
relation = relation.where(nodes[:where]) if nodes[:where]
|
79
82
|
relation = relation.having(nodes[:having]) if nodes[:having]
|
80
83
|
relation
|
@@ -162,7 +162,7 @@ module Pursuit
|
|
162
162
|
attribute_name = context[:attribute].to_sym
|
163
163
|
attribute = context.dig(:permitted_attributes, attribute_name)
|
164
164
|
raise AttributeNotFound, attribute_name if attribute.blank?
|
165
|
-
raise AggregateModifierRequired, attribute_name if attribute.name == Arel.star
|
165
|
+
raise AggregateModifierRequired, attribute_name if attribute.respond_to?(:name) && attribute.name == Arel.star
|
166
166
|
|
167
167
|
attribute
|
168
168
|
end
|
@@ -1,25 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pursuit
|
4
|
-
#
|
4
|
+
# Provides an interface for declaring which attributes should be searched in a simple query, and a method for applying
|
5
|
+
# a simple query to an `ActiveRecord::Relation` instance.
|
5
6
|
#
|
6
7
|
class SimpleSearch
|
7
8
|
# @return [Set<Arel::Attributes::Attribute>] The attributes to match against.
|
8
9
|
#
|
9
|
-
|
10
|
+
attr_accessor :attributes
|
10
11
|
|
11
|
-
# @return [
|
12
|
+
# @return [Arel::Table] The default table to retrieve attributes from.
|
12
13
|
#
|
13
|
-
|
14
|
+
attr_accessor :default_table
|
14
15
|
|
15
16
|
# Creates a new simple search instance.
|
16
17
|
#
|
17
|
-
# @param
|
18
|
-
# @param block
|
18
|
+
# @param default_table [Arel::Table] The default table to retrieve attributes from.
|
19
|
+
# @param block [Proc] The proc to invoke in the search instance (optional).
|
19
20
|
#
|
20
|
-
def initialize(
|
21
|
+
def initialize(default_table: nil, &block)
|
21
22
|
@attributes = Set.new
|
22
|
-
@
|
23
|
+
@default_table = default_table
|
23
24
|
|
24
25
|
instance_eval(&block) if block
|
25
26
|
end
|
@@ -30,7 +31,7 @@ module Pursuit
|
|
30
31
|
# @return [Arel::Attributes::Attribute] The underlying attribute to query.
|
31
32
|
#
|
32
33
|
def search_attribute(attribute)
|
33
|
-
attribute =
|
34
|
+
attribute = default_table[attribute] if attribute.is_a?(Symbol)
|
34
35
|
attributes.add(attribute)
|
35
36
|
end
|
36
37
|
|
@@ -51,12 +52,13 @@ module Pursuit
|
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
54
|
-
#
|
55
|
+
# Applies the simple clauses derived from `query` to `relation`.
|
55
56
|
#
|
56
|
-
# @param query
|
57
|
-
# @
|
57
|
+
# @param query [String] The simple query.
|
58
|
+
# @param relation [ActiveRecord::Relation] The base relation to apply the simple clauses to.
|
59
|
+
# @return [ActiveRecord::Relation] The base relation with the simple clauses applied.
|
58
60
|
#
|
59
|
-
def apply(query)
|
61
|
+
def apply(query, relation)
|
60
62
|
node = parse(query)
|
61
63
|
node ? relation.where(node) : relation.none
|
62
64
|
end
|
data/lib/pursuit/term_search.rb
CHANGED
@@ -1,25 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pursuit
|
4
|
-
#
|
4
|
+
# Provides an interface for declaring which attributes should be searched in a term query, and a method for applying
|
5
|
+
# a term query to an `ActiveRecord::Relation` instance.
|
6
|
+
#
|
7
|
+
# @see Pursuit::TermParser
|
8
|
+
# @see Pursuit::TermTransform
|
5
9
|
#
|
6
10
|
class TermSearch
|
7
11
|
# @return [Set<Arel::Attributes::Attribute>] The attributes to match against.
|
8
12
|
#
|
9
|
-
|
13
|
+
attr_accessor :attributes
|
10
14
|
|
11
|
-
# @return [
|
15
|
+
# @return [Arel::Table] The default table to retrieve attributes from.
|
12
16
|
#
|
13
|
-
|
17
|
+
attr_accessor :default_table
|
14
18
|
|
15
19
|
# Creates a new term search instance.
|
16
20
|
#
|
17
|
-
# @param
|
18
|
-
# @param block
|
21
|
+
# @param default_table [Arel::Table] The default table to retrieve attributes from.
|
22
|
+
# @param block [Proc] The proc to invoke in the search instance (optional).
|
19
23
|
#
|
20
|
-
def initialize(
|
24
|
+
def initialize(default_table: nil, &block)
|
21
25
|
@attributes = Set.new
|
22
|
-
@
|
26
|
+
@default_table = default_table
|
23
27
|
|
24
28
|
instance_eval(&block) if block
|
25
29
|
end
|
@@ -42,7 +46,7 @@ module Pursuit
|
|
42
46
|
# @return [Arel::Attributes::Attribute] The underlying attribute to query.
|
43
47
|
#
|
44
48
|
def search_attribute(attribute)
|
45
|
-
attribute =
|
49
|
+
attribute = default_table[attribute] if attribute.is_a?(Symbol)
|
46
50
|
attributes.add(attribute)
|
47
51
|
end
|
48
52
|
|
@@ -56,12 +60,13 @@ module Pursuit
|
|
56
60
|
transform.apply(tree, attributes: attributes)
|
57
61
|
end
|
58
62
|
|
59
|
-
#
|
63
|
+
# Applies the term clauses derived from `query` to `relation`.
|
60
64
|
#
|
61
|
-
# @param query
|
62
|
-
# @
|
65
|
+
# @param query [String] The term query.
|
66
|
+
# @param relation [ActiveRecord::Relation] The base relation to apply the term clauses to.
|
67
|
+
# @return [ActiveRecord::Relation] The base relation with the term clauses applied.
|
63
68
|
#
|
64
|
-
def apply(query)
|
69
|
+
def apply(query, relation)
|
65
70
|
node = parse(query)
|
66
71
|
node ? relation.where(node) : relation.none
|
67
72
|
end
|
data/pursuit.gemspec
CHANGED
@@ -6,16 +6,16 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
6
|
require 'pursuit/constants'
|
7
7
|
|
8
8
|
Gem::Specification.new do |spec|
|
9
|
-
spec.name
|
10
|
-
spec.version
|
11
|
-
spec.authors
|
12
|
-
spec.email
|
9
|
+
spec.name = 'pursuit'
|
10
|
+
spec.version = Pursuit::VERSION
|
11
|
+
spec.authors = ['Nialto Services']
|
12
|
+
spec.email = ['support@nialtoservices.co.uk']
|
13
13
|
|
14
|
-
spec.summary
|
15
|
-
spec.homepage
|
16
|
-
spec.license
|
14
|
+
spec.summary = 'Advanced key-based searching for ActiveRecord objects.'
|
15
|
+
spec.homepage = 'https://github.com/NialtoServices/pursuit'
|
16
|
+
spec.license = 'Apache-2.0'
|
17
17
|
|
18
|
-
spec.files
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.required_ruby_version = '>= 2.6.0'
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
24
24
|
spec.metadata['yard.run'] = 'yri'
|
25
25
|
|
26
|
-
spec.
|
27
|
-
spec.
|
28
|
-
spec.
|
26
|
+
spec.add_dependency 'activerecord', '>= 5.2.0', '<= 8.0.0'
|
27
|
+
spec.add_dependency 'activesupport', '>= 5.2.0', '<= 8.0.0'
|
28
|
+
spec.add_dependency 'parslet', '~> 2.0'
|
29
29
|
end
|
@@ -1,38 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Product < ApplicationRecord
|
4
|
+
include ProductSearch
|
5
|
+
|
4
6
|
belongs_to :category, class_name: 'ProductCategory', inverse_of: :products, optional: true
|
5
7
|
|
6
8
|
has_many :variations, class_name: 'ProductVariation', inverse_of: :product
|
7
9
|
|
8
10
|
validates :title, presence: true
|
9
|
-
|
10
|
-
def self.predicate_search
|
11
|
-
@predicate_search ||= Pursuit::PredicateSearch.new(
|
12
|
-
left_outer_joins(:category, :variations).group(:id).order(:title)
|
13
|
-
) do
|
14
|
-
permit_attribute :title
|
15
|
-
permit_attribute :category, ProductCategory.arel_table[:id]
|
16
|
-
permit_attribute :category_name, ProductCategory.arel_table[:name]
|
17
|
-
permit_attribute :variation, ProductVariation.arel_table[:id]
|
18
|
-
permit_attribute :variation_title, ProductVariation.arel_table[:title]
|
19
|
-
permit_attribute :variation_currency, ProductVariation.arel_table[:currency]
|
20
|
-
permit_attribute :variation_amount, ProductVariation.arel_table[:amount]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.term_search
|
25
|
-
@term_search ||= Pursuit::TermSearch.new(
|
26
|
-
left_outer_joins(:category).group(:id).order(:title)
|
27
|
-
) do
|
28
|
-
search_attribute :title
|
29
|
-
search_attribute ProductCategory.arel_table[:name]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.search(query)
|
34
|
-
predicate_search.apply(query)
|
35
|
-
rescue Parslet::ParseFailed
|
36
|
-
term_search.apply(query)
|
37
|
-
end
|
38
11
|
end
|
@@ -1,29 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ProductCategory < ApplicationRecord
|
4
|
+
include ProductCategorySearch
|
5
|
+
|
4
6
|
has_many :products, class_name: 'Product', foreign_key: :category_id, inverse_of: :category, dependent: :nullify
|
5
7
|
|
6
8
|
validates :name, presence: true
|
7
|
-
|
8
|
-
def self.predicate_search
|
9
|
-
@predicate_search ||= Pursuit::PredicateSearch.new(
|
10
|
-
left_outer_joins(:products).group(:id)
|
11
|
-
) do
|
12
|
-
permit_attribute :name
|
13
|
-
permit_attribute :product, Product.arel_table[:id]
|
14
|
-
permit_attribute :product_title, Product.arel_table[:title]
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.term_search
|
19
|
-
@term_search ||= Pursuit::TermSearch.new(all) do
|
20
|
-
search_attribute :name
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.search(query)
|
25
|
-
predicate_search.apply(query)
|
26
|
-
rescue Parslet::ParseFailed
|
27
|
-
term_search.apply(query)
|
28
|
-
end
|
29
9
|
end
|
@@ -1,37 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ProductVariation < ApplicationRecord
|
4
|
+
include ProductVariationSearch
|
5
|
+
|
4
6
|
belongs_to :product
|
5
7
|
|
6
|
-
|
8
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('7.0.0')
|
9
|
+
enum :stock_status, { in_stock: 1, low_stock: 2, out_of_stock: 3 }
|
10
|
+
else
|
11
|
+
enum stock_status: { in_stock: 1, low_stock: 2, out_of_stock: 3 }
|
12
|
+
end
|
7
13
|
|
8
14
|
validates :title, presence: true
|
9
15
|
|
10
16
|
validates :currency, presence: true
|
11
17
|
validates :amount, presence: true, numericality: true
|
12
|
-
|
13
|
-
def self.predicate_search
|
14
|
-
@predicate_search ||= Pursuit::PredicateSearch.new(
|
15
|
-
left_outer_joins(:product).group(:id)
|
16
|
-
) do
|
17
|
-
permit_attribute :title
|
18
|
-
permit_attribute :stock_status
|
19
|
-
permit_attribute :currency
|
20
|
-
permit_attribute :amount
|
21
|
-
permit_attribute :product, Product.arel_table[:id]
|
22
|
-
permit_attribute :product_title, Product.arel_table[:title]
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.term_search
|
27
|
-
@term_search ||= Pursuit::TermSearch.new(all) do
|
28
|
-
search_attribute :title
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.search(query)
|
33
|
-
predicate_search.apply(query)
|
34
|
-
rescue Parslet::ParseFailed
|
35
|
-
term_search.apply(query)
|
36
|
-
end
|
37
18
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProductCategorySearch
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
# Search for records matching the specified predicate.
|
8
|
+
#
|
9
|
+
# @param query [String] The query with a predicate.
|
10
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the predicate.
|
11
|
+
#
|
12
|
+
def predicate_search(query)
|
13
|
+
@predicate_search ||= Pursuit::PredicateSearch.new(default_table: arel_table) do
|
14
|
+
permit_attribute :name
|
15
|
+
permit_attribute :product, Product.arel_table[:id]
|
16
|
+
permit_attribute :product_title, Product.arel_table[:title]
|
17
|
+
end
|
18
|
+
|
19
|
+
@predicate_search.apply(query, left_outer_joins(:products).group(:id))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Search for records matching the specified terms.
|
23
|
+
#
|
24
|
+
# @param query [String] The query with one or more terms.
|
25
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the terms.
|
26
|
+
#
|
27
|
+
def term_search(query)
|
28
|
+
@term_search ||= Pursuit::TermSearch.new(default_table: arel_table) do
|
29
|
+
search_attribute :name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Note that we're using `all` here, but this still works when used in a chain:
|
33
|
+
# => ProductVariation.where(stock_status: :in_stock).search('Green')
|
34
|
+
@term_search.apply(query, all)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Search for records matching the specified query.
|
38
|
+
#
|
39
|
+
# @param query [String] The query.
|
40
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the query.
|
41
|
+
#
|
42
|
+
def search(query)
|
43
|
+
return none if query.blank?
|
44
|
+
|
45
|
+
predicate_search(query)
|
46
|
+
rescue Parslet::ParseFailed
|
47
|
+
term_search(query)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProductSearch
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
# Search for records matching the specified predicate.
|
8
|
+
#
|
9
|
+
# @param query [String] The query with a predicate.
|
10
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the predicate.
|
11
|
+
#
|
12
|
+
def predicate_search(query)
|
13
|
+
@predicate_search ||= Pursuit::PredicateSearch.new(default_table: arel_table) do
|
14
|
+
permit_attribute :title
|
15
|
+
permit_attribute :category, ProductCategory.arel_table[:id]
|
16
|
+
permit_attribute :category_name, ProductCategory.arel_table[:name]
|
17
|
+
permit_attribute :variation, ProductVariation.arel_table[:id]
|
18
|
+
permit_attribute :variation_title, ProductVariation.arel_table[:title]
|
19
|
+
permit_attribute :variation_currency, ProductVariation.arel_table[:currency]
|
20
|
+
permit_attribute :variation_amount, ProductVariation.arel_table[:amount]
|
21
|
+
end
|
22
|
+
|
23
|
+
@predicate_search.apply(query, left_outer_joins(:category, :variations).group(:id).order(:title))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Search for records matching the specified terms.
|
27
|
+
#
|
28
|
+
# @param query [String] The query with one or more terms.
|
29
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the terms.
|
30
|
+
#
|
31
|
+
def term_search(query)
|
32
|
+
@term_search ||= Pursuit::TermSearch.new(default_table: arel_table) do
|
33
|
+
search_attribute :title
|
34
|
+
search_attribute ProductCategory.arel_table[:name]
|
35
|
+
end
|
36
|
+
|
37
|
+
@term_search.apply(query, left_outer_joins(:category).group(:id).order(:title))
|
38
|
+
end
|
39
|
+
|
40
|
+
# Search for records matching the specified query.
|
41
|
+
#
|
42
|
+
# @param query [String] The query.
|
43
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the query.
|
44
|
+
#
|
45
|
+
def search(query)
|
46
|
+
return none if query.blank?
|
47
|
+
|
48
|
+
predicate_search(query)
|
49
|
+
rescue Parslet::ParseFailed
|
50
|
+
term_search(query)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|