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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubygem.yaml +46 -0
  3. data/Gemfile +14 -13
  4. data/Gemfile.lock +14 -13
  5. data/README.md +210 -27
  6. data/bin/console +10 -0
  7. data/lib/pursuit/aggregate_modifier_not_found.rb +20 -0
  8. data/lib/pursuit/aggregate_modifier_required.rb +20 -0
  9. data/lib/pursuit/aggregate_modifiers_not_available.rb +13 -0
  10. data/lib/pursuit/attribute_not_found.rb +20 -0
  11. data/lib/pursuit/constants.rb +1 -1
  12. data/lib/pursuit/error.rb +7 -0
  13. data/lib/pursuit/predicate_parser.rb +181 -0
  14. data/lib/pursuit/predicate_search.rb +83 -0
  15. data/lib/pursuit/predicate_transform.rb +231 -0
  16. data/lib/pursuit/query_error.rb +7 -0
  17. data/lib/pursuit/simple_search.rb +64 -0
  18. data/lib/pursuit/term_parser.rb +44 -0
  19. data/lib/pursuit/term_search.rb +69 -0
  20. data/lib/pursuit/term_transform.rb +35 -0
  21. data/lib/pursuit.rb +18 -4
  22. data/pursuit.gemspec +4 -3
  23. data/spec/internal/app/models/application_record.rb +5 -0
  24. data/spec/internal/app/models/product.rb +25 -9
  25. data/spec/internal/app/models/product_category.rb +23 -1
  26. data/spec/internal/app/models/product_variation.rb +26 -1
  27. data/spec/lib/pursuit/predicate_parser_spec.rb +1604 -0
  28. data/spec/lib/pursuit/predicate_search_spec.rb +80 -0
  29. data/spec/lib/pursuit/predicate_transform_spec.rb +624 -0
  30. data/spec/lib/pursuit/simple_search_spec.rb +59 -0
  31. data/spec/lib/pursuit/term_parser_spec.rb +271 -0
  32. data/spec/lib/pursuit/term_search_spec.rb +71 -0
  33. data/spec/lib/pursuit/term_transform_spec.rb +105 -0
  34. metadata +47 -25
  35. data/.travis.yml +0 -26
  36. data/lib/pursuit/dsl.rb +0 -28
  37. data/lib/pursuit/railtie.rb +0 -13
  38. data/lib/pursuit/search.rb +0 -172
  39. data/lib/pursuit/search_options.rb +0 -86
  40. data/lib/pursuit/search_term_parser.rb +0 -46
  41. data/spec/lib/pursuit/dsl_spec.rb +0 -22
  42. data/spec/lib/pursuit/search_options_spec.rb +0 -146
  43. data/spec/lib/pursuit/search_spec.rb +0 -516
  44. data/spec/lib/pursuit/search_term_parser_spec.rb +0 -34
  45. data/travis/gemfiles/5.2.gemfile +0 -8
  46. data/travis/gemfiles/6.0.gemfile +0 -8
  47. data/travis/gemfiles/6.1.gemfile +0 -8
  48. 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/search_options'
5
- require_relative 'pursuit/search_term_parser'
6
- require_relative 'pursuit/search'
7
- require_relative 'pursuit/railtie' if defined?(Rails::Railtie)
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/nialtoservices/pursuit'
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', '< 7.2.0'
27
- spec.add_runtime_dependency 'activesupport', '>= 5.2.0', '< 7.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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
@@ -1,22 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Product < ActiveRecord::Base
3
+ class Product < ApplicationRecord
4
4
  belongs_to :category, class_name: 'ProductCategory', inverse_of: :products, optional: true
5
5
 
6
6
  has_many :variations, class_name: 'ProductVariation', inverse_of: :product
7
7
 
8
8
  validates :title, presence: true
9
9
 
10
- searchable do |o|
11
- o.relation :variations, :title, :stock_status
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
12
23
 
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]])
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]
18
30
  end
31
+ end
19
32
 
20
- o.attribute :category, :category_id
33
+ def self.search(query)
34
+ predicate_search.apply(query)
35
+ rescue Parslet::ParseFailed
36
+ term_search.apply(query)
21
37
  end
22
38
  end
@@ -1,7 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ProductCategory < ActiveRecord::Base
3
+ class ProductCategory < ApplicationRecord
4
4
  has_many :products, class_name: 'Product', foreign_key: :category_id, inverse_of: :category, dependent: :nullify
5
5
 
6
6
  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
7
29
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ProductVariation < ActiveRecord::Base
3
+ class ProductVariation < ApplicationRecord
4
4
  belongs_to :product
5
5
 
6
6
  enum stock_status: { in_stock: 1, low_stock: 2, out_of_stock: 3 }
@@ -9,4 +9,29 @@ class ProductVariation < ActiveRecord::Base
9
9
 
10
10
  validates :currency, presence: true
11
11
  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
12
37
  end