pursuit 0.4.5 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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