pursuit 0.4.5 → 1.1.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/.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 +86 -0
- data/lib/pursuit/predicate_transform.rb +231 -0
- data/lib/pursuit/query_error.rb +7 -0
- data/lib/pursuit/simple_search.rb +66 -0
- data/lib/pursuit/term_parser.rb +44 -0
- data/lib/pursuit/term_search.rb +74 -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 +3 -14
- data/spec/internal/app/models/product_category.rb +3 -1
- data/spec/internal/app/models/product_variation.rb +3 -1
- 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_parser_spec.rb +1604 -0
- data/spec/lib/pursuit/predicate_search_spec.rb +116 -0
- data/spec/lib/pursuit/predicate_transform_spec.rb +624 -0
- data/spec/lib/pursuit/simple_search_spec.rb +73 -0
- data/spec/lib/pursuit/term_parser_spec.rb +271 -0
- data/spec/lib/pursuit/term_search_spec.rb +85 -0
- data/spec/lib/pursuit/term_transform_spec.rb +105 -0
- metadata +50 -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
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pursuit
|
4
|
+
# Transform for a list of terms.
|
5
|
+
#
|
6
|
+
class TermTransform < Parslet::Transform
|
7
|
+
# String Types
|
8
|
+
|
9
|
+
rule(string_double_quotes: []) { '' }
|
10
|
+
rule(string_double_quotes: simple(:value)) { value.to_s.gsub(/\\(.)/, '\1') }
|
11
|
+
|
12
|
+
rule(string_single_quotes: []) { '' }
|
13
|
+
rule(string_single_quotes: simple(:value)) { value.to_s.gsub(/\\(.)/, '\1') }
|
14
|
+
|
15
|
+
rule(string_no_quotes: simple(:value)) { value.to_s }
|
16
|
+
|
17
|
+
# Terms
|
18
|
+
|
19
|
+
rule(term: simple(:term)) do |context|
|
20
|
+
value = ActiveRecord::Base.sanitize_sql_like(context[:term])
|
21
|
+
value = "%#{value}%"
|
22
|
+
|
23
|
+
context[:attributes].inject(nil) do |previous_node, attribute|
|
24
|
+
node = attribute.matches(value)
|
25
|
+
next node unless previous_node
|
26
|
+
|
27
|
+
previous_node.or(node)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Joins
|
32
|
+
|
33
|
+
rule(left: simple(:left), right: simple(:right)) { left.and(right) }
|
34
|
+
end
|
35
|
+
end
|
data/lib/pursuit.rb
CHANGED
@@ -1,7 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_support'
|
5
|
+
require 'bigdecimal'
|
6
|
+
require 'parslet'
|
7
|
+
|
3
8
|
require_relative 'pursuit/constants'
|
4
|
-
require_relative 'pursuit/
|
5
|
-
require_relative 'pursuit/
|
6
|
-
require_relative 'pursuit/
|
7
|
-
require_relative 'pursuit/
|
9
|
+
require_relative 'pursuit/error'
|
10
|
+
require_relative 'pursuit/query_error'
|
11
|
+
require_relative 'pursuit/aggregate_modifier_not_found'
|
12
|
+
require_relative 'pursuit/aggregate_modifier_required'
|
13
|
+
require_relative 'pursuit/aggregate_modifiers_not_available'
|
14
|
+
require_relative 'pursuit/attribute_not_found'
|
15
|
+
require_relative 'pursuit/predicate_parser'
|
16
|
+
require_relative 'pursuit/predicate_transform'
|
17
|
+
require_relative 'pursuit/predicate_search'
|
18
|
+
require_relative 'pursuit/term_parser'
|
19
|
+
require_relative 'pursuit/term_transform'
|
20
|
+
require_relative 'pursuit/term_search'
|
21
|
+
require_relative 'pursuit/simple_search'
|
data/pursuit.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.email = ['support@nialtoservices.co.uk']
|
13
13
|
|
14
14
|
spec.summary = 'Advanced key-based searching for ActiveRecord objects.'
|
15
|
-
spec.homepage = 'https://github.com/
|
15
|
+
spec.homepage = 'https://github.com/NialtoServices/pursuit'
|
16
16
|
spec.license = 'Apache-2.0'
|
17
17
|
|
18
18
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -23,6 +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', '
|
27
|
-
spec.add_runtime_dependency 'activesupport', '>= 5.2.0', '
|
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'
|
28
29
|
end
|
@@ -1,22 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Product <
|
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
|
-
searchable do |o|
|
11
|
-
o.relation :variations, :title, :stock_status
|
12
|
-
|
13
|
-
o.attribute :title
|
14
|
-
o.attribute :description
|
15
|
-
o.attribute :rating, unkeyed: false
|
16
|
-
o.attribute :title_length, unkeyed: false do
|
17
|
-
Arel::Nodes::NamedFunction.new('LENGTH', [arel_table[:title]])
|
18
|
-
end
|
19
|
-
|
20
|
-
o.attribute :category, :category_id
|
21
|
-
end
|
22
11
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class ProductCategory <
|
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
|
@@ -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
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProductVariationSearch
|
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 :stock_status
|
16
|
+
permit_attribute :currency
|
17
|
+
permit_attribute :amount
|
18
|
+
permit_attribute :product, Product.arel_table[:id]
|
19
|
+
permit_attribute :product_title, Product.arel_table[:title]
|
20
|
+
end
|
21
|
+
|
22
|
+
@predicate_search.apply(query, left_outer_joins(:product).group(:id))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Search for records matching the specified terms.
|
26
|
+
#
|
27
|
+
# @param query [String] The query with one or more terms.
|
28
|
+
# @return [ActiveRecord::Relation] The current relation filtered by the terms.
|
29
|
+
#
|
30
|
+
def term_search(query)
|
31
|
+
@term_search ||= Pursuit::TermSearch.new(default_table: arel_table) do
|
32
|
+
search_attribute :title
|
33
|
+
end
|
34
|
+
|
35
|
+
# Note that we're using `all` here, but this still works when used in a chain:
|
36
|
+
# => ProductVariation.where(stock_status: :in_stock).search('Green')
|
37
|
+
@term_search.apply(query, all)
|
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
|