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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/gemfiles/ruby-2.6.gemfile +14 -0
  3. data/.github/gemfiles/ruby-2.7.gemfile +13 -0
  4. data/.github/gemfiles/ruby-3.0.gemfile +13 -0
  5. data/.github/gemfiles/ruby-3.1.gemfile +13 -0
  6. data/.github/gemfiles/ruby-3.2.gemfile +13 -0
  7. data/.github/gemfiles/ruby-3.3.gemfile +13 -0
  8. data/.github/gemfiles/ruby-3.4.gemfile +13 -0
  9. data/.github/gemfiles/ruby-head.gemfile +13 -0
  10. data/.github/gemfiles/ruby-jruby-head.gemfile +13 -0
  11. data/.github/gemfiles/ruby-jruby.gemfile +13 -0
  12. data/.github/gemfiles/ruby-truffleruby-head.gemfile +13 -0
  13. data/.github/gemfiles/ruby-truffleruby.gemfile +13 -0
  14. data/.github/workflows/continuous-integration.yaml +65 -0
  15. data/.rbenv-gemsets +1 -1
  16. data/.ruby-version +1 -1
  17. data/Gemfile +7 -7
  18. data/Gemfile.lock +144 -124
  19. data/README.md +21 -19
  20. data/lib/pursuit/constants.rb +1 -1
  21. data/lib/pursuit/predicate_parser.rb +4 -4
  22. data/lib/pursuit/predicate_search.rb +26 -23
  23. data/lib/pursuit/predicate_transform.rb +1 -1
  24. data/lib/pursuit/simple_search.rb +15 -13
  25. data/lib/pursuit/term_search.rb +18 -13
  26. data/pursuit.gemspec +11 -11
  27. data/spec/internal/app/models/product.rb +2 -29
  28. data/spec/internal/app/models/product_category.rb +2 -22
  29. data/spec/internal/app/models/product_variation.rb +7 -26
  30. data/spec/internal/app/searches/product_category_search.rb +50 -0
  31. data/spec/internal/app/searches/product_search.rb +53 -0
  32. data/spec/internal/app/searches/product_variation_search.rb +53 -0
  33. data/spec/lib/pursuit/predicate_search_spec.rb +65 -29
  34. data/spec/lib/pursuit/predicate_transform_spec.rb +16 -1
  35. data/spec/lib/pursuit/simple_search_spec.rb +33 -19
  36. data/spec/lib/pursuit/term_search_spec.rb +33 -19
  37. metadata +19 -9
  38. data/.github/workflows/rubygem.yaml +0 -46
  39. 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
- $ gem install pursuit
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.all)
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.all) do
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.all) do
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('title = "Luxury Shirt" & (variation_amount = 0 | variation_amount > 1000)')
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('title = "Luxury Shirt" & #variation > 5')
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:
@@ -3,5 +3,5 @@
3
3
  module Pursuit
4
4
  # @return [String] The gem's semantic version number.
5
5
  #
6
- VERSION = '1.0.1'
6
+ VERSION = '1.1.1'
7
7
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pursuit
4
- # Parser for predicate-based queries.
4
+ # Parser for predicate queries.
5
5
  #
6
- # Predicate-based queries take an attribute to compare the value of, an operator (such as the equal sign), and the
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 would enter:
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
- # :nodoc:
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 [Boolean] `true` when aggregate modifiers can be used in queries, `false` otherwise.
11
+ # @return [Arel::Table] The default table to retrieve attributes from.
8
12
  #
9
- attr_accessor :permit_aggregate_modifiers
13
+ attr_accessor :default_table
10
14
 
11
- # @return [Hash<Symbol, Arel::Attributes::Attribute>] The attributes permitted for use in queries.
15
+ # @return [Boolean] `true` when aggregate modifiers can be used, `false` otherwise.
12
16
  #
13
- attr_reader :permitted_attributes
17
+ attr_accessor :permit_aggregate_modifiers
14
18
 
15
- # @return [ActiveRecord::Relation] The relation to which the predicate clauses are added.
19
+ # @return [Hash<Symbol, Arel::Attributes::Attribute>] The attributes permitted for use in queries.
16
20
  #
17
- attr_reader :relation
21
+ attr_accessor :permitted_attributes
18
22
 
19
23
  # Creates a new predicate search instance.
20
24
  #
21
- # @param relation [ActiveRecord::Relation] The relation to which the predicate clauses are added.
22
- # @param permit_aggregate_modifiers [Boolean] Whether aggregate modifiers can be used or not.
23
- # @param block [Proc] The proc to invoke in the search instance (optional).
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(relation, permit_aggregate_modifiers: false, &block)
26
- @relation = relation
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 = relation.klass.arel_table[attribute] if attribute.is_a?(Symbol)
53
- permitted_attributes[name] = attribute || relation.klass.arel_table[name]
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
- tree,
65
- permitted_attributes: permitted_attributes,
66
- permit_aggregate_modifiers: permit_aggregate_modifiers
67
+ parser.parse(query),
68
+ permit_aggregate_modifiers: permit_aggregate_modifiers,
69
+ permitted_attributes: permitted_attributes
67
70
  )
68
71
  end
69
72
 
70
- # Returns #relation filtered by the predicate query.
73
+ # Applies the predicate clauses derived from `query` to `relation`.
71
74
  #
72
- # @param query [String] The predicate query.
73
- # @return [ActiveRecord::Relation] The updated relation with the predicate clauses added.
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
- # :nodoc:
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
- attr_reader :attributes
10
+ attr_accessor :attributes
10
11
 
11
- # @return [ActiveRecord::Relation] The relation to which the clauses are added.
12
+ # @return [Arel::Table] The default table to retrieve attributes from.
12
13
  #
13
- attr_reader :relation
14
+ attr_accessor :default_table
14
15
 
15
16
  # Creates a new simple search instance.
16
17
  #
17
- # @param relation [ActiveRecord::Relation] The relation to which the clauses are added.
18
- # @param block [Proc] The proc to invoke in the search instance (optional).
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(relation, &block)
21
+ def initialize(default_table: nil, &block)
21
22
  @attributes = Set.new
22
- @relation = relation
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 = relation.klass.arel_table[attribute] if attribute.is_a?(Symbol)
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
- # Returns #relation filtered by the query.
55
+ # Applies the simple clauses derived from `query` to `relation`.
55
56
  #
56
- # @param query [String] The simple query.
57
- # @return [ActiveRecord::Relation] The updated relation with the clauses added.
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
@@ -1,25 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pursuit
4
- # :nodoc:
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
- attr_reader :attributes
13
+ attr_accessor :attributes
10
14
 
11
- # @return [ActiveRecord::Relation] The relation to which the term clauses are added.
15
+ # @return [Arel::Table] The default table to retrieve attributes from.
12
16
  #
13
- attr_reader :relation
17
+ attr_accessor :default_table
14
18
 
15
19
  # Creates a new term search instance.
16
20
  #
17
- # @param relation [ActiveRecord::Relation] The relation to which the term clauses are added.
18
- # @param block [Proc] The proc to invoke in the search instance (optional).
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(relation, &block)
24
+ def initialize(default_table: nil, &block)
21
25
  @attributes = Set.new
22
- @relation = relation
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 = relation.klass.arel_table[attribute] if attribute.is_a?(Symbol)
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
- # Returns #relation filtered by the term query.
63
+ # Applies the term clauses derived from `query` to `relation`.
60
64
  #
61
- # @param query [String] The term query.
62
- # @return [ActiveRecord::Relation] The updated relation with the term clauses added.
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 = 'pursuit'
10
- spec.version = Pursuit::VERSION
11
- spec.authors = ['Nialto Services']
12
- spec.email = ['support@nialtoservices.co.uk']
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 = 'Advanced key-based searching for ActiveRecord objects.'
15
- spec.homepage = 'https://github.com/NialtoServices/pursuit'
16
- spec.license = 'Apache-2.0'
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 = `git ls-files -z`.split("\x0")
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.add_runtime_dependency 'activerecord', '>= 5.2.0', '<= 8.0.0'
27
- spec.add_runtime_dependency 'activesupport', '>= 5.2.0', '<= 8.0.0'
28
- spec.add_runtime_dependency 'parslet', '~> 2.0'
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
- enum stock_status: { in_stock: 1, low_stock: 2, out_of_stock: 3 }
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