pursuit 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b49ae3ec2734b8b67fef2fb0686b85af3a694745db4654de863942d4a598d53
4
- data.tar.gz: 8d8a6e71f23b31d0ebf33a1c2554a9875c0c189786954574c3d5f58ced5fe304
3
+ metadata.gz: e692c274eaa14b0f1311c56917447a1526285274722fd420fccf3892ac1fcb37
4
+ data.tar.gz: e7133d0015ea96a8930340e458fb427a2ebf81f4e15c95662b4b9f9a0ed5be81
5
5
  SHA512:
6
- metadata.gz: faebb7031e643b0fe4011cee2fda71a916dd12aa2819845a26bf79ef6ca0f0411bcd3e8b5ea1f092624909933639d97d0e59cc2b80ba38ea10f2dcbf0254c154
7
- data.tar.gz: 0c06093eb83369d20f5e64a843483e98a019a5facb594f75b845949cfdcc0b7027da78ccb8be92d6a739cc76720e1d28165019aaaf7220718deb550c2816440a
6
+ metadata.gz: b3b007b9d56efdaa21fc7cb1d7d2d7a8bd9c7988269f614aee7db9cd3e620a92317ede365528aff8f61dcb2fb531c95ded3b29a5a4730fce6b9c3e400ea4c807
7
+ data.tar.gz: 7340594ab1b4dc75dcc6722304b46b7535591968d5bf65d3f8544a559000162bde3d8d808374e951aa564d8a67e752ef89590b41f23f77e2868b12930d2fa4e6
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.2
1
+ 3.1.0
data/Gemfile.lock CHANGED
@@ -1,50 +1,49 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pursuit (0.2.0)
5
- activerecord (>= 5.2.0, < 6.2.0)
6
- activesupport (>= 5.2.0, < 6.2.0)
4
+ pursuit (0.4.0)
5
+ activerecord (>= 5.2.0, < 7.1.0)
6
+ activesupport (>= 5.2.0, < 7.1.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actionpack (6.0.4.1)
12
- actionview (= 6.0.4.1)
13
- activesupport (= 6.0.4.1)
14
- rack (~> 2.0, >= 2.0.8)
11
+ actionpack (7.0.1)
12
+ actionview (= 7.0.1)
13
+ activesupport (= 7.0.1)
14
+ rack (~> 2.0, >= 2.2.0)
15
15
  rack-test (>= 0.6.3)
16
16
  rails-dom-testing (~> 2.0)
17
17
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
18
- actionview (6.0.4.1)
19
- activesupport (= 6.0.4.1)
18
+ actionview (7.0.1)
19
+ activesupport (= 7.0.1)
20
20
  builder (~> 3.1)
21
21
  erubi (~> 1.4)
22
22
  rails-dom-testing (~> 2.0)
23
23
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
24
- activemodel (6.0.4.1)
25
- activesupport (= 6.0.4.1)
26
- activerecord (6.0.4.1)
27
- activemodel (= 6.0.4.1)
28
- activesupport (= 6.0.4.1)
29
- activesupport (6.0.4.1)
24
+ activemodel (7.0.1)
25
+ activesupport (= 7.0.1)
26
+ activerecord (7.0.1)
27
+ activemodel (= 7.0.1)
28
+ activesupport (= 7.0.1)
29
+ activesupport (7.0.1)
30
30
  concurrent-ruby (~> 1.0, >= 1.0.2)
31
- i18n (>= 0.7, < 2)
32
- minitest (~> 5.1)
33
- tzinfo (~> 1.1)
34
- zeitwerk (~> 2.2, >= 2.2.2)
31
+ i18n (>= 1.6, < 2)
32
+ minitest (>= 5.1)
33
+ tzinfo (~> 2.0)
35
34
  ast (2.4.2)
36
35
  builder (3.2.4)
37
36
  coderay (1.1.3)
38
- combustion (1.3.3)
37
+ combustion (1.3.5)
39
38
  activesupport (>= 3.0.0)
40
39
  railties (>= 3.0.0)
41
40
  thor (>= 0.14.6)
42
41
  concurrent-ruby (1.1.9)
43
42
  crass (1.0.6)
44
- diff-lcs (1.4.4)
43
+ diff-lcs (1.5.0)
45
44
  erubi (1.10.0)
46
- ffi (1.15.4)
47
- formatador (0.3.0)
45
+ ffi (1.15.5)
46
+ formatador (1.0.0)
48
47
  guard (2.18.0)
49
48
  formatador (>= 0.2.4)
50
49
  listen (>= 2.7, < 4.0)
@@ -61,24 +60,23 @@ GEM
61
60
  rspec (>= 2.99.0, < 4.0)
62
61
  i18n (1.8.11)
63
62
  concurrent-ruby (~> 1.0)
64
- jaro_winkler (1.5.4)
65
- listen (3.7.0)
63
+ listen (3.7.1)
66
64
  rb-fsevent (~> 0.10, >= 0.10.3)
67
65
  rb-inotify (~> 0.9, >= 0.9.10)
68
- loofah (2.12.0)
66
+ loofah (2.13.0)
69
67
  crass (~> 1.0.2)
70
68
  nokogiri (>= 1.5.9)
71
69
  lumberjack (1.2.8)
72
70
  method_source (1.0.0)
73
- minitest (5.14.4)
71
+ minitest (5.15.0)
74
72
  nenv (0.3.0)
75
- nokogiri (1.12.5-x86_64-darwin)
73
+ nokogiri (1.13.1-x86_64-darwin)
76
74
  racc (~> 1.4)
77
75
  notiffany (0.1.3)
78
76
  nenv (~> 0.1)
79
77
  shellany (~> 0.0)
80
78
  parallel (1.21.0)
81
- parser (3.0.2.0)
79
+ parser (3.1.0.0)
82
80
  ast (~> 2.4.1)
83
81
  pry (0.14.1)
84
82
  coderay (~> 1.1)
@@ -92,70 +90,78 @@ GEM
92
90
  nokogiri (>= 1.6)
93
91
  rails-html-sanitizer (1.4.2)
94
92
  loofah (~> 2.3)
95
- railties (6.0.4.1)
96
- actionpack (= 6.0.4.1)
97
- activesupport (= 6.0.4.1)
93
+ railties (7.0.1)
94
+ actionpack (= 7.0.1)
95
+ activesupport (= 7.0.1)
98
96
  method_source
99
- rake (>= 0.8.7)
100
- thor (>= 0.20.3, < 2.0)
101
- rainbow (3.0.0)
97
+ rake (>= 12.2)
98
+ thor (~> 1.0)
99
+ zeitwerk (~> 2.5)
100
+ rainbow (3.1.1)
102
101
  rake (13.0.6)
103
102
  rb-fsevent (0.11.0)
104
103
  rb-inotify (0.10.1)
105
104
  ffi (~> 1.0)
106
- rspec (3.9.0)
107
- rspec-core (~> 3.9.0)
108
- rspec-expectations (~> 3.9.0)
109
- rspec-mocks (~> 3.9.0)
110
- rspec-core (3.9.3)
111
- rspec-support (~> 3.9.3)
112
- rspec-expectations (3.9.4)
105
+ regexp_parser (2.2.0)
106
+ rexml (3.2.5)
107
+ rspec (3.10.0)
108
+ rspec-core (~> 3.10.0)
109
+ rspec-expectations (~> 3.10.0)
110
+ rspec-mocks (~> 3.10.0)
111
+ rspec-core (3.10.1)
112
+ rspec-support (~> 3.10.0)
113
+ rspec-expectations (3.10.2)
113
114
  diff-lcs (>= 1.2.0, < 2.0)
114
- rspec-support (~> 3.9.0)
115
- rspec-mocks (3.9.1)
115
+ rspec-support (~> 3.10.0)
116
+ rspec-mocks (3.10.2)
116
117
  diff-lcs (>= 1.2.0, < 2.0)
117
- rspec-support (~> 3.9.0)
118
- rspec-rails (3.9.1)
119
- actionpack (>= 3.0)
120
- activesupport (>= 3.0)
121
- railties (>= 3.0)
122
- rspec-core (~> 3.9.0)
123
- rspec-expectations (~> 3.9.0)
124
- rspec-mocks (~> 3.9.0)
125
- rspec-support (~> 3.9.0)
126
- rspec-support (3.9.4)
127
- rubocop (0.77.0)
128
- jaro_winkler (~> 1.5.1)
118
+ rspec-support (~> 3.10.0)
119
+ rspec-rails (5.0.2)
120
+ actionpack (>= 5.2)
121
+ activesupport (>= 5.2)
122
+ railties (>= 5.2)
123
+ rspec-core (~> 3.10)
124
+ rspec-expectations (~> 3.10)
125
+ rspec-mocks (~> 3.10)
126
+ rspec-support (~> 3.10)
127
+ rspec-support (3.10.3)
128
+ rubocop (1.25.0)
129
129
  parallel (~> 1.10)
130
- parser (>= 2.6)
130
+ parser (>= 3.1.0.0)
131
131
  rainbow (>= 2.2.2, < 4.0)
132
+ regexp_parser (>= 1.8, < 3.0)
133
+ rexml
134
+ rubocop-ast (>= 1.15.1, < 2.0)
132
135
  ruby-progressbar (~> 1.7)
133
- unicode-display_width (>= 1.4.0, < 1.7)
136
+ unicode-display_width (>= 1.4.0, < 3.0)
137
+ rubocop-ast (1.15.1)
138
+ parser (>= 3.0.1.1)
134
139
  ruby-progressbar (1.11.0)
135
140
  shellany (0.0.1)
136
141
  sqlite3 (1.4.2)
137
- thor (1.1.0)
138
- thread_safe (0.3.6)
139
- tzinfo (1.2.9)
140
- thread_safe (~> 0.1)
141
- unicode-display_width (1.6.1)
142
- yard (0.9.26)
143
- zeitwerk (2.5.1)
142
+ thor (1.2.1)
143
+ tzinfo (2.0.4)
144
+ concurrent-ruby (~> 1.0)
145
+ unicode-display_width (2.1.0)
146
+ webrick (1.7.0)
147
+ yard (0.9.27)
148
+ webrick (~> 1.7.0)
149
+ zeitwerk (2.5.3)
144
150
 
145
151
  PLATFORMS
146
152
  x86_64-darwin-21
147
153
 
148
154
  DEPENDENCIES
149
155
  bundler (~> 2.0)
150
- combustion (~> 1.1)
156
+ combustion (~> 1.3)
151
157
  guard-rspec (~> 4.7)
152
158
  pursuit!
153
159
  rake (~> 13.0)
154
- rspec (~> 3.8)
155
- rspec-rails (~> 3.8)
156
- rubocop (~> 0.77.0)
160
+ rspec (~> 3.10)
161
+ rspec-rails (~> 5.0)
162
+ rubocop (~> 1.25)
157
163
  sqlite3 (~> 1.4)
158
- yard (~> 0.9.20)
164
+ yard (~> 0.9)
159
165
 
160
166
  BUNDLED WITH
161
- 2.2.22
167
+ 2.3.3
data/README.md CHANGED
@@ -23,19 +23,24 @@ class Product < ActiveRecord::Base
23
23
  searchable do |o|
24
24
  o.relation :variations, :title, :stock_status
25
25
 
26
- o.keyed :title
27
- o.keyed :description
28
- o.keyed :rating
29
-
30
- # You can also create virtual attributes to search by passing in a block that returns an arel node.
31
- o.keyed :title_length do
26
+ # Attributes can be used for both keyed and unkeyed searching by default, but you can pass either `keyed: false` or
27
+ # `unkeyed: false` to restrict when the attribute is searched.
28
+ o.attribute :title
29
+ o.attribute :description
30
+ o.attribute :rating, unkeyed: false
31
+
32
+ # You can shorten the search keyword by passing the desired search term first, and then the real attribute name
33
+ # as the second argument.
34
+ # => "category*=shirts"
35
+ o.attribute :category, :category_id
36
+
37
+ # It's also possible to query entirely custom Arel nodes by passing a block which returns the Arel node to query.
38
+ # You could use this to query a person's full name by concatenating their first and last name columns, for example.
39
+ o.attribute :title_length, unkeyed: false do
32
40
  Arel::Nodes::NamedFunction.new('LENGTH', [
33
41
  arel_table[:title]
34
42
  ])
35
43
  end
36
-
37
- o.unkeyed :title
38
- o.unkeyed :description
39
44
  end
40
45
  end
41
46
  ```
@@ -3,5 +3,5 @@
3
3
  module Pursuit
4
4
  # @return [String] The gem's semantic version number.
5
5
  #
6
- VERSION = '0.2.0'
6
+ VERSION = '0.4.0'
7
7
  end
@@ -51,12 +51,13 @@ module Pursuit
51
51
  return nil if terms.blank?
52
52
 
53
53
  terms.reduce(nil) do |chain, term|
54
- reflection = options.record_class.reflections[term.key]
54
+ attribute_name = term.key.to_sym
55
+ reflection = options.relations.key?(attribute_name) ? options.record_class.reflections[term.key] : nil
55
56
  node = if reflection.present?
56
- attribute_names = options.relations[term.key.to_sym]
57
+ attribute_names = options.relations[attribute_name]
57
58
  build_arel_for_reflection(reflection, attribute_names, term.operator, term.value)
58
59
  else
59
- node_builder = options.keyed_attributes[term.key.to_sym]
60
+ node_builder = options.keyed_attributes[attribute_name]
60
61
  build_arel_for_node(node_builder.call, term.operator, term.value)
61
62
  end
62
63
 
@@ -2,44 +2,59 @@
2
2
 
3
3
  module Pursuit
4
4
  class SearchOptions
5
+ # @return [Struct] The structure which holds the search options for a single attribute.
6
+ #
7
+ AttributeOptions = Struct.new(:keyed, :unkeyed, :block)
8
+
5
9
  # @return [Class<ActiveRecord::Base>] The `ActiveRecord::Base` child class to search.
6
10
  #
7
11
  attr_reader :record_class
8
12
 
9
- # @return [Hash<Symbol, Array<Symbol>>] The record's relatives and the attribute names that can be searched.
13
+ # @return [Hash<Symbol, Array<Symbol>>] The attribute names of the record's relatives which can be searched.
10
14
  #
11
15
  attr_reader :relations
12
16
 
13
- # @return [Hash<Symbol, Proc>] The attribute names which can be searched with a keyed term (e.g. 'last_name:*herb').
14
- #
15
- attr_reader :keyed_attributes
16
-
17
- # @return [Hash<Symbol, Proc>] The attribute names which can be searched with an unkeyed term (e.g. 'herb').
17
+ # @return [Hash<Symbol, AttributeOptions>] The attributes which can be searched.
18
18
  #
19
- attr_reader :unkeyed_attributes
19
+ attr_reader :attributes
20
20
 
21
- # Create a new `SearchOptions` ready for adding options.
21
+ # Create a new `SearchOptions` and call the passed block to setup the options.
22
22
  #
23
23
  # @params record_class [Class<ActiveRecord::Base>]
24
- # @params block [Proc]
24
+ # @params block [Proc]
25
25
  #
26
26
  def initialize(record_class, &block)
27
27
  @record_class = record_class
28
28
  @relations = {}
29
- @keyed_attributes = {}
30
- @unkeyed_attributes = {}
29
+ @attributes = {}
31
30
 
32
31
  block.call(self) if block
33
32
  end
34
33
 
34
+ # @return [Hash<Symbol, Proc>] The attributes which can be queried using a keyed term.
35
+ #
36
+ def keyed_attributes
37
+ attributes.each_with_object({}) do |(name, options), keyed_attributes|
38
+ keyed_attributes[name] = options.block if options.keyed
39
+ end
40
+ end
41
+
42
+ # @return [Hash<Symbol, Proc>] The attributes which can be queried using an unkeyed term.
43
+ #
44
+ def unkeyed_attributes
45
+ attributes.each_with_object({}) do |(name, options), unkeyed_attributes|
46
+ unkeyed_attributes[name] = options.block if options.unkeyed
47
+ end
48
+ end
49
+
35
50
  # @return [Array<String>] The collection of all possible attributes which can be used as a keyed term.
36
51
  #
37
52
  def keys
38
- keys = relations.keys + keyed_attributes.keys
53
+ keys = relations.keys + attributes.select { |_, a| a.keyed }.keys
39
54
  keys.map(&:to_s).uniq
40
55
  end
41
56
 
42
- # Add a relation to the search options.
57
+ # Add a relation to search.
43
58
  #
44
59
  # @param name [Symbol] The name of the relationship attribute.
45
60
  # @param attribute_names [Splat] The name of the attributes within the relationship to search.
@@ -49,23 +64,22 @@ module Pursuit
49
64
  nil
50
65
  end
51
66
 
52
- # Add a keyed attribute to search.
53
- #
54
- # @param name [Symbol] The name of the attribute.
55
- # @param block [Proc] A block which returns an arel node to query against instead of a real attribute.
56
- #
57
- def keyed(name, &block)
58
- keyed_attributes[name] = block || -> { record_class.arel_table[name] }
59
- nil
60
- end
61
-
62
- # Add an unkeyed attribute to search.
67
+ # Add an attribute to search.
63
68
  #
64
- # @param name [Symbol] The name of the attribute.
65
- # @param block [Proc] A block which returns an arel node to query against instead of a real attribute.
69
+ # @param term_name [Symbol] The keyed search term (can be an existing attribute, or a custom value when
70
+ # passing either the `attribute_name` or a block returning an Arel node).
71
+ # @param attribute_name [Symbol] The attribute name to search (defaults to the keyword, when left blank and no
72
+ # block is passed).
73
+ # @param keyed [Boolean] `true` when the attribute should be searchable using a keyed term,
74
+ # `false` otherwise.
75
+ # @param unkeyed [Boolean] `true` when the attribute should be searchable using an unkeyed term,
76
+ # `false` otherwise.
77
+ # @param block [Proc] A block which returns the Arel node to query against. When left blank, the
78
+ # matching attribute from `.arel_table` is queried instead.
66
79
  #
67
- def unkeyed(name, &block)
68
- unkeyed_attributes[name] = block || -> { record_class.arel_table[name] }
80
+ def attribute(term_name, attribute_name = nil, keyed: true, unkeyed: true, &block)
81
+ block ||= -> { record_class.arel_table[attribute_name || term_name] }
82
+ attributes[term_name] = AttributeOptions.new(keyed, unkeyed, block)
69
83
  nil
70
84
  end
71
85
  end
data/pursuit.gemspec CHANGED
@@ -21,16 +21,16 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.metadata['yard.run'] = 'yri'
23
23
 
24
- spec.add_runtime_dependency 'activerecord', '>= 5.2.0', '< 6.2.0'
25
- spec.add_runtime_dependency 'activesupport', '>= 5.2.0', '< 6.2.0'
24
+ spec.add_runtime_dependency 'activerecord', '>= 5.2.0', '< 7.1.0'
25
+ spec.add_runtime_dependency 'activesupport', '>= 5.2.0', '< 7.1.0'
26
26
 
27
27
  spec.add_development_dependency 'bundler', '~> 2.0'
28
- spec.add_development_dependency 'combustion', '~> 1.1'
28
+ spec.add_development_dependency 'combustion', '~> 1.3'
29
29
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
30
30
  spec.add_development_dependency 'rake', '~> 13.0'
31
- spec.add_development_dependency 'rspec', '~> 3.8'
32
- spec.add_development_dependency 'rspec-rails', '~> 3.8'
33
- spec.add_development_dependency 'rubocop', '~> 0.77.0'
34
- spec.add_development_dependency 'yard', '~> 0.9.20'
31
+ spec.add_development_dependency 'rspec', '~> 3.10'
32
+ spec.add_development_dependency 'rspec-rails', '~> 5.0'
33
+ spec.add_development_dependency 'rubocop', '~> 1.25'
34
+ spec.add_development_dependency 'yard', '~> 0.9'
35
35
  spec.add_development_dependency 'sqlite3', '~> 1.4'
36
36
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Product < ActiveRecord::Base
4
+ belongs_to :category, class_name: 'ProductCategory', inverse_of: :products, optional: true
5
+
4
6
  has_many :variations, class_name: 'ProductVariation', inverse_of: :product
5
7
 
6
8
  validates :title, presence: true
@@ -8,17 +10,15 @@ class Product < ActiveRecord::Base
8
10
  searchable do |o|
9
11
  o.relation :variations, :title, :stock_status
10
12
 
11
- o.keyed :title
12
- o.keyed :description
13
- o.keyed :rating
14
-
15
- o.keyed :title_length do
13
+ o.attribute :title
14
+ o.attribute :description
15
+ o.attribute :rating, unkeyed: false
16
+ o.attribute :title_length, unkeyed: false do
16
17
  Arel::Nodes::NamedFunction.new('LENGTH', [
17
18
  arel_table[:title]
18
19
  ])
19
20
  end
20
21
 
21
- o.unkeyed :title
22
- o.unkeyed :description
22
+ o.attribute :category, :category_id
23
23
  end
24
24
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ProductCategory < ActiveRecord::Base
4
+ has_many :products, class_name: 'Product', foreign_key: :category_id, inverse_of: :category, dependent: :nullify
5
+
6
+ validates :name, presence: true
7
+ end
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ActiveRecord::Schema.define do
4
+ create_table :product_categories, id: :string, force: true do |t|
5
+ t.string :name, null: false
6
+
7
+ t.timestamps null: false
8
+ end
9
+
4
10
  create_table :products, force: true do |t|
11
+ t.belongs_to :category, type: :string, foreign_key: { to_table: 'product_categories' }
12
+
5
13
  t.string :title, null: false
6
14
 
7
15
  t.text :description
@@ -11,19 +11,6 @@ RSpec.describe Pursuit::SearchOptions do
11
11
  end
12
12
  end
13
13
 
14
- before do
15
- search_options.relation :variations, :title, :stock_status
16
-
17
- search_options.keyed :title
18
- search_options.keyed :title_length, &title_length_node_builder
19
- search_options.keyed :description
20
- search_options.keyed :rating
21
-
22
- search_options.unkeyed :title
23
- search_options.unkeyed :title_length, &title_length_node_builder
24
- search_options.unkeyed :description
25
- end
26
-
27
14
  describe '#record_class' do
28
15
  subject(:record_class) { search_options.record_class }
29
16
 
@@ -35,6 +22,10 @@ RSpec.describe Pursuit::SearchOptions do
35
22
  describe '#relations' do
36
23
  subject(:relations) { search_options.relations }
37
24
 
25
+ before do
26
+ search_options.relation :variations, :title, :stock_status
27
+ end
28
+
38
29
  it 'is expected to contain the correct relations' do
39
30
  expect(relations).to eq(variations: %i[title stock_status])
40
31
  end
@@ -43,12 +34,19 @@ RSpec.describe Pursuit::SearchOptions do
43
34
  describe '#keyed_attributes' do
44
35
  subject(:keyed_attributes) { search_options.keyed_attributes }
45
36
 
37
+ before do
38
+ search_options.attribute :title, keyed: false
39
+ search_options.attribute :title_length, &title_length_node_builder
40
+ search_options.attribute :description
41
+ search_options.attribute :rating, unkeyed: false
42
+ end
43
+
46
44
  it 'is expected to contain the correct keyed attributes' do
47
- expect(keyed_attributes.keys).to contain_exactly(:title, :title_length, :description, :rating)
45
+ expect(keyed_attributes.keys).to contain_exactly(:title_length, :description, :rating)
48
46
  end
49
47
 
50
48
  it 'is expected to set a default node builder for attributes declared without a block' do
51
- expect(keyed_attributes[:title].call).to eq(Product.arel_table[:title])
49
+ expect(keyed_attributes[:description].call).to eq(Product.arel_table[:description])
52
50
  end
53
51
 
54
52
  it 'is expected to set a custom node builder for attributes declared with a block' do
@@ -59,6 +57,13 @@ RSpec.describe Pursuit::SearchOptions do
59
57
  describe '#unkeyed_attributes' do
60
58
  subject(:unkeyed_attributes) { search_options.unkeyed_attributes }
61
59
 
60
+ before do
61
+ search_options.attribute :title, keyed: false
62
+ search_options.attribute :title_length, &title_length_node_builder
63
+ search_options.attribute :description
64
+ search_options.attribute :rating, unkeyed: false
65
+ end
66
+
62
67
  it 'is expected to contain the correct unkeyed attributes' do
63
68
  expect(unkeyed_attributes.keys).to contain_exactly(:title, :title_length, :description)
64
69
  end
@@ -71,4 +76,73 @@ RSpec.describe Pursuit::SearchOptions do
71
76
  expect(unkeyed_attributes[:title_length]).to eq(title_length_node_builder)
72
77
  end
73
78
  end
79
+
80
+ describe '#relation' do
81
+ subject(:relation) { search_options.relation(:variations, :title, :stock_status) }
82
+
83
+ it 'is expected to add the relation to #relations' do
84
+ expect { relation }.to change(search_options, :relations).from({}).to(variations: %i[title stock_status])
85
+ end
86
+ end
87
+
88
+ describe '#attribute' do
89
+ subject(:attribute) { search_options.attribute(:description) }
90
+
91
+ it { is_expected.to eq(nil) }
92
+
93
+ it 'is expected to add the attribute to #attributes' do
94
+ expect { attribute }.to change(search_options.attributes, :keys).from([]).to(%i[description])
95
+ end
96
+
97
+ it 'is expected to allow keyed searching by default' do
98
+ attribute
99
+ expect(search_options.attributes[:description].keyed).to eq(true)
100
+ end
101
+
102
+ it 'is expected to allow unkeyed searching by default' do
103
+ attribute
104
+ expect(search_options.attributes[:description].unkeyed).to eq(true)
105
+ end
106
+
107
+ it 'is expected to query the #term_name attribute' do
108
+ attribute
109
+ expect(search_options.attributes[:description].block.call).to eq(Product.arel_table[:description])
110
+ end
111
+
112
+ context 'when passing the attribute name to search' do
113
+ subject(:attribute) { search_options.attribute(:desc, :description) }
114
+
115
+ it 'is expected to query the #attribute_name attribute' do
116
+ attribute
117
+ expect(search_options.attributes[:desc].block.call).to eq(Product.arel_table[:description])
118
+ end
119
+ end
120
+
121
+ context 'when passing :keyed eq false' do
122
+ subject(:attribute) { search_options.attribute(:description, keyed: false) }
123
+
124
+ it 'is expected to disallow keyed searching' do
125
+ attribute
126
+ expect(search_options.attributes[:description].keyed).to eq(false)
127
+ end
128
+ end
129
+
130
+ context 'when passing :unkeyed eq false' do
131
+ subject(:attribute) { search_options.attribute(:description, unkeyed: false) }
132
+
133
+ it 'is expected to disallow unkeyed searching' do
134
+ attribute
135
+ expect(search_options.attributes[:description].unkeyed).to eq(false)
136
+ end
137
+ end
138
+
139
+ context 'when passing a block' do
140
+ subject(:attribute) { search_options.attribute(:description, &title_length_node_builder) }
141
+
142
+ it 'is expected to query the result of the passed block' do
143
+ attribute
144
+ expect(search_options.attributes[:description].block).to eq(title_length_node_builder)
145
+ end
146
+ end
147
+ end
74
148
  end
@@ -7,18 +7,16 @@ RSpec.describe Pursuit::Search do
7
7
  Pursuit::SearchOptions.new(Product) do |o|
8
8
  o.relation :variations, :title, :stock_status
9
9
 
10
- o.keyed :title
11
- o.keyed :description
12
- o.keyed :rating
13
-
14
- o.keyed :title_length do
10
+ o.attribute :title
11
+ o.attribute :description
12
+ o.attribute :rating, unkeyed: false
13
+ o.attribute :title_length, unkeyed: false do
15
14
  Arel::Nodes::NamedFunction.new('LENGTH', [
16
15
  Product.arel_table[:title]
17
16
  ])
18
17
  end
19
18
 
20
- o.unkeyed :title
21
- o.unkeyed :description
19
+ o.attribute :category, :category_id
22
20
  end
23
21
  end
24
22
 
@@ -466,5 +464,26 @@ RSpec.describe Pursuit::Search do
466
464
  expect(perform).to contain_exactly(product_a)
467
465
  end
468
466
  end
467
+
468
+ context 'when querying a custom attribute whose name matches a reflection' do
469
+ let(:query) { 'category==shirts' }
470
+
471
+ let(:shirts_category) { ProductCategory.create!(id: 'shirts', name: 'The Shirt Collection') }
472
+ let(:socks_category) { ProductCategory.create!(id: 'socks', name: 'The Sock Collection') }
473
+
474
+ let(:product_a) { Product.create!(title: 'Plain Shirt', category: shirts_category) }
475
+ let(:product_b) { Product.create!(title: 'Funky Shirt', category: shirts_category) }
476
+ let(:product_c) { Product.create!(title: 'Socks - Pack of 4', category: socks_category) }
477
+
478
+ before do
479
+ product_a
480
+ product_b
481
+ product_c
482
+ end
483
+
484
+ it 'is expected to contain the matching records' do
485
+ expect(perform).to contain_exactly(product_a, product_b)
486
+ end
487
+ end
469
488
  end
470
489
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pursuit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nialto Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-18 00:00:00.000000000 Z
11
+ date: 2022-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: 5.2.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 6.2.0
22
+ version: 7.1.0
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: 5.2.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 6.2.0
32
+ version: 7.1.0
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: 5.2.0
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: 6.2.0
42
+ version: 7.1.0
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,7 +49,7 @@ dependencies:
49
49
  version: 5.2.0
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: 6.2.0
52
+ version: 7.1.0
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: bundler
55
55
  requirement: !ruby/object:Gem::Requirement
@@ -70,14 +70,14 @@ dependencies:
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: '1.1'
73
+ version: '1.3'
74
74
  type: :development
75
75
  prerelease: false
76
76
  version_requirements: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '1.1'
80
+ version: '1.3'
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: guard-rspec
83
83
  requirement: !ruby/object:Gem::Requirement
@@ -112,56 +112,56 @@ dependencies:
112
112
  requirements:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
- version: '3.8'
115
+ version: '3.10'
116
116
  type: :development
117
117
  prerelease: false
118
118
  version_requirements: !ruby/object:Gem::Requirement
119
119
  requirements:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
- version: '3.8'
122
+ version: '3.10'
123
123
  - !ruby/object:Gem::Dependency
124
124
  name: rspec-rails
125
125
  requirement: !ruby/object:Gem::Requirement
126
126
  requirements:
127
127
  - - "~>"
128
128
  - !ruby/object:Gem::Version
129
- version: '3.8'
129
+ version: '5.0'
130
130
  type: :development
131
131
  prerelease: false
132
132
  version_requirements: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '3.8'
136
+ version: '5.0'
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: rubocop
139
139
  requirement: !ruby/object:Gem::Requirement
140
140
  requirements:
141
141
  - - "~>"
142
142
  - !ruby/object:Gem::Version
143
- version: 0.77.0
143
+ version: '1.25'
144
144
  type: :development
145
145
  prerelease: false
146
146
  version_requirements: !ruby/object:Gem::Requirement
147
147
  requirements:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: 0.77.0
150
+ version: '1.25'
151
151
  - !ruby/object:Gem::Dependency
152
152
  name: yard
153
153
  requirement: !ruby/object:Gem::Requirement
154
154
  requirements:
155
155
  - - "~>"
156
156
  - !ruby/object:Gem::Version
157
- version: 0.9.20
157
+ version: '0.9'
158
158
  type: :development
159
159
  prerelease: false
160
160
  version_requirements: !ruby/object:Gem::Requirement
161
161
  requirements:
162
162
  - - "~>"
163
163
  - !ruby/object:Gem::Version
164
- version: 0.9.20
164
+ version: '0.9'
165
165
  - !ruby/object:Gem::Dependency
166
166
  name: sqlite3
167
167
  requirement: !ruby/object:Gem::Requirement
@@ -207,6 +207,7 @@ files:
207
207
  - lib/pursuit/search_term_parser.rb
208
208
  - pursuit.gemspec
209
209
  - spec/internal/app/models/product.rb
210
+ - spec/internal/app/models/product_category.rb
210
211
  - spec/internal/app/models/product_variation.rb
211
212
  - spec/internal/config/database.yml
212
213
  - spec/internal/db/schema.rb
@@ -240,12 +241,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
240
241
  - !ruby/object:Gem::Version
241
242
  version: '0'
242
243
  requirements: []
243
- rubygems_version: 3.2.22
244
+ rubygems_version: 3.3.3
244
245
  signing_key:
245
246
  specification_version: 4
246
247
  summary: Advanced key-based searching for ActiveRecord objects.
247
248
  test_files:
248
249
  - spec/internal/app/models/product.rb
250
+ - spec/internal/app/models/product_category.rb
249
251
  - spec/internal/app/models/product_variation.rb
250
252
  - spec/internal/config/database.yml
251
253
  - spec/internal/db/schema.rb