pursuit 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9584065901bd2b27fce1df13ee13586151c96be3e04f222b81c6ee93d8f078a4
4
- data.tar.gz: 81f233b5d39526129b8bdf7c7bba997e9a5b964b9bd5024bc3e3351f1105c927
3
+ metadata.gz: 4b1d45107d4ab3d9a2ac9d9eaf8451e6f75762f2c8ac71e51683c3afe98009c7
4
+ data.tar.gz: e40fa3f38548173ba042cd232347127a3059c4e7f4a417569cad81d16b2b3710
5
5
  SHA512:
6
- metadata.gz: 83fcec1d5657de087a5a5fdf71b131e34632fc02328cd66d2a551c438201f9125fd8d0861d239a96a4b6b36c6ac3bde342efaa4b610a1689646540229fe456bd
7
- data.tar.gz: 47d981997a463f5203b3316410d6f5e2395fc1aa448329a86a625a732c2751a3e281b4581973cb9893f3c76d68f6999f38a0fbe0020c56ecc608f99b968101b0
6
+ metadata.gz: 65009e855c743fad6e91b5939d59456c845976b2add94fd1d03a9603d34a6046d5e45e58dd3fc7ca33291600f1a17bb879b2c492f1d5c22102bc9f2124c591b3
7
+ data.tar.gz: 9662bc16e8eb17107089b5bdd7635a91d0647b71a015cff79bf43df4b517729036d8c9ff3fde2b299ca82e63e440b3abca2531eddaa35aeb306472e62a7bc708
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.3.0)
5
- activerecord (>= 5.2.0, < 6.2.0)
6
- activesupport (>= 5.2.0, < 6.2.0)
4
+ pursuit (0.4.1)
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
@@ -29,7 +29,13 @@ class Product < ActiveRecord::Base
29
29
  o.attribute :description
30
30
  o.attribute :rating, unkeyed: false
31
31
 
32
- # You can also create virtual attributes to search by passing in a block that returns an arel node.
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.
33
39
  o.attribute :title_length, unkeyed: false do
34
40
  Arel::Nodes::NamedFunction.new('LENGTH', [
35
41
  arel_table[:title]
@@ -3,5 +3,5 @@
3
3
  module Pursuit
4
4
  # @return [String] The gem's semantic version number.
5
5
  #
6
- VERSION = '0.3.0'
6
+ VERSION = '0.4.1'
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
 
@@ -89,7 +90,7 @@ module Pursuit
89
90
 
90
91
  def build_arel_for_reflection(reflection, attribute_names, operator, value)
91
92
  nodes = build_arel_for_reflection_join(reflection)
92
- count_nodes = build_arel_for_relation_count(nodes, operator, value)
93
+ count_nodes = build_arel_for_relation_count(nodes, operator, value) unless reflection.belongs_to?
93
94
  return count_nodes if count_nodes.present?
94
95
 
95
96
  match_nodes = attribute_names.reduce(nil) do |chain, attribute_name|
@@ -103,6 +104,23 @@ module Pursuit
103
104
  end
104
105
 
105
106
  def build_arel_for_reflection_join(reflection)
107
+ if reflection.belongs_to?
108
+ build_arel_for_belongs_to_reflection_join(reflection)
109
+ else
110
+ build_arel_for_has_reflection_join(reflection)
111
+ end
112
+ end
113
+
114
+ def build_arel_for_belongs_to_reflection_join(reflection)
115
+ reflection_table = reflection.klass.arel_table
116
+ reflection_table.where(
117
+ reflection_table[reflection.join_primary_key].eq(
118
+ options.record_class.arel_table[reflection.join_foreign_key]
119
+ )
120
+ )
121
+ end
122
+
123
+ def build_arel_for_has_reflection_join(reflection)
106
124
  reflection_table = reflection.klass.arel_table
107
125
  reflection_through = reflection.through_reflection
108
126
 
@@ -54,7 +54,7 @@ module Pursuit
54
54
  keys.map(&:to_s).uniq
55
55
  end
56
56
 
57
- # Add a relation to the search options.
57
+ # Add a relation to search.
58
58
  #
59
59
  # @param name [Symbol] The name of the relationship attribute.
60
60
  # @param attribute_names [Splat] The name of the attributes within the relationship to search.
@@ -64,15 +64,22 @@ module Pursuit
64
64
  nil
65
65
  end
66
66
 
67
- # Add a keyed attribute to search.
67
+ # Add an attribute to search.
68
68
  #
69
- # @param name [Symbol] The name of the attribute.
70
- # @param keyed [Boolean] `true` when the attribute should be searchable using a keyed term, `false` otherwise.
71
- # @param unkeyed [Boolean] `true` when the attribute should be searchable using an unkeyed term, `false` otherwise.
72
- # @param block [Proc] A block which returns an Arel node to query, instead of the matching table column.
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.
73
79
  #
74
- def attribute(name, keyed: true, unkeyed: true, &block)
75
- attributes[name] = AttributeOptions.new(keyed, unkeyed, 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)
76
83
  nil
77
84
  end
78
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
@@ -16,5 +18,7 @@ class Product < ActiveRecord::Base
16
18
  arel_table[:title]
17
19
  ])
18
20
  end
21
+
22
+ o.attribute :category, :category_id
19
23
  end
20
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
@@ -104,11 +104,20 @@ RSpec.describe Pursuit::SearchOptions do
104
104
  expect(search_options.attributes[:description].unkeyed).to eq(true)
105
105
  end
106
106
 
107
- it 'is expected to use the matching table column node builder by default' do
107
+ it 'is expected to query the #term_name attribute' do
108
108
  attribute
109
109
  expect(search_options.attributes[:description].block.call).to eq(Product.arel_table[:description])
110
110
  end
111
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
+
112
121
  context 'when passing :keyed eq false' do
113
122
  subject(:attribute) { search_options.attribute(:description, keyed: false) }
114
123
 
@@ -130,7 +139,7 @@ RSpec.describe Pursuit::SearchOptions do
130
139
  context 'when passing a block' do
131
140
  subject(:attribute) { search_options.attribute(:description, &title_length_node_builder) }
132
141
 
133
- it 'is expected to use the custom node builder' do
142
+ it 'is expected to query the result of the passed block' do
134
143
  attribute
135
144
  expect(search_options.attributes[:description].block).to eq(title_length_node_builder)
136
145
  end
@@ -5,6 +5,7 @@ RSpec.describe Pursuit::Search do
5
5
 
6
6
  let(:search_options) do
7
7
  Pursuit::SearchOptions.new(Product) do |o|
8
+ o.relation :category, :name
8
9
  o.relation :variations, :title, :stock_status
9
10
 
10
11
  o.attribute :title
@@ -15,6 +16,8 @@ RSpec.describe Pursuit::Search do
15
16
  Product.arel_table[:title]
16
17
  ])
17
18
  end
19
+
20
+ o.attribute :cat, :category_id
18
21
  end
19
22
  end
20
23
 
@@ -251,115 +254,20 @@ RSpec.describe Pursuit::Search do
251
254
  end
252
255
  end
253
256
 
254
- context 'when passed a `match` relationship search query' do
255
- let(:query) { 'variations*=green' }
256
-
257
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
258
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
259
-
260
- let(:product_variation_a) { ProductVariation.create!(product: product_a, title: 'Red') }
261
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
262
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
263
-
264
- before do
265
- product_a
266
- product_b
267
-
268
- product_variation_a
269
- product_variation_b
270
- product_variation_c
271
- end
272
-
273
- it 'is expected to contain the matching records' do
274
- expect(perform).to contain_exactly(product_b)
275
- end
276
- end
277
-
278
- context 'when passed an `equal to` relationship count query' do
279
- let(:query) { 'variations==1' }
280
-
281
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
282
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
283
- let(:product_c) { Product.create!(title: 'Socks') }
284
-
285
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
286
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
287
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
288
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
289
-
290
- before do
291
- product_a
292
- product_b
293
- product_c
294
-
295
- product_variation_a
296
- product_variation_b
297
- product_variation_c
298
- product_variation_d
299
- end
300
-
301
- it 'is expected to contain the matching records' do
302
- expect(perform).to contain_exactly(product_c)
303
- end
304
- end
305
-
306
- context 'when passed a `greater than` relationship count query' do
307
- let(:query) { 'variations>1' }
308
-
309
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
310
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
311
- let(:product_c) { Product.create!(title: 'Socks') }
312
-
313
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
314
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
315
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
316
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
317
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
318
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
319
-
320
- before do
321
- product_a
322
- product_b
323
- product_c
324
-
325
- product_variation_a
326
- product_variation_b
327
- product_variation_c
328
- product_variation_d
329
- product_variation_e
330
- product_variation_f
331
- end
332
-
333
- it 'is expected to contain the matching records' do
334
- expect(perform).to contain_exactly(product_a, product_b)
335
- end
336
- end
337
-
338
- context 'when passed a `greater than or equal to` relationship count query' do
339
- let(:query) { 'variations>=2' }
257
+ context 'when querying a custom attribute whose name matches a reflection' do
258
+ let(:query) { 'cat==shirts' }
340
259
 
341
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
342
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
343
- let(:product_c) { Product.create!(title: 'Socks') }
260
+ let(:shirts_category) { ProductCategory.create!(id: 'shirts', name: 'The Shirt Collection') }
261
+ let(:socks_category) { ProductCategory.create!(id: 'socks', name: 'The Sock Collection') }
344
262
 
345
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
346
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
347
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
348
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
349
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
350
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
263
+ let(:product_a) { Product.create!(title: 'Plain Shirt', category: shirts_category) }
264
+ let(:product_b) { Product.create!(title: 'Funky Shirt', category: shirts_category) }
265
+ let(:product_c) { Product.create!(title: 'Socks - Pack of 4', category: socks_category) }
351
266
 
352
267
  before do
353
268
  product_a
354
269
  product_b
355
270
  product_c
356
-
357
- product_variation_a
358
- product_variation_b
359
- product_variation_c
360
- product_variation_d
361
- product_variation_e
362
- product_variation_f
363
271
  end
364
272
 
365
273
  it 'is expected to contain the matching records' do
@@ -367,99 +275,243 @@ RSpec.describe Pursuit::Search do
367
275
  end
368
276
  end
369
277
 
370
- context 'when passed a `less than` relationship count query' do
371
- let(:query) { 'variations<3' }
372
-
373
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
374
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
375
- let(:product_c) { Product.create!(title: 'Socks') }
376
-
377
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
378
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
379
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
380
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
381
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
382
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
278
+ context 'when searching a #has_many relationship' do
279
+ context 'when passed a `match` relationship search query' do
280
+ let(:query) { 'variations*=green' }
383
281
 
384
- before do
385
- product_a
386
- product_b
387
- product_c
282
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
283
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
284
+
285
+ let(:product_variation_a) { ProductVariation.create!(product: product_a, title: 'Red') }
286
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
287
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
288
+
289
+ before do
290
+ product_a
291
+ product_b
292
+
293
+ product_variation_a
294
+ product_variation_b
295
+ product_variation_c
296
+ end
388
297
 
389
- product_variation_a
390
- product_variation_b
391
- product_variation_c
392
- product_variation_d
393
- product_variation_e
394
- product_variation_f
298
+ it 'is expected to contain the matching records' do
299
+ expect(perform).to contain_exactly(product_b)
300
+ end
395
301
  end
396
302
 
397
- it 'is expected to contain the matching records' do
398
- expect(perform).to contain_exactly(product_a, product_c)
303
+ context 'when passed an `equal to` relationship count query' do
304
+ let(:query) { 'variations==1' }
305
+
306
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
307
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
308
+ let(:product_c) { Product.create!(title: 'Socks') }
309
+
310
+ let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
311
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
312
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
313
+ let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
314
+
315
+ before do
316
+ product_a
317
+ product_b
318
+ product_c
319
+
320
+ product_variation_a
321
+ product_variation_b
322
+ product_variation_c
323
+ product_variation_d
324
+ end
325
+
326
+ it 'is expected to contain the matching records' do
327
+ expect(perform).to contain_exactly(product_c)
328
+ end
329
+ end
330
+
331
+ context 'when passed a `greater than` relationship count query' do
332
+ let(:query) { 'variations>1' }
333
+
334
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
335
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
336
+ let(:product_c) { Product.create!(title: 'Socks') }
337
+
338
+ let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
339
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
340
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
341
+ let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
342
+ let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
343
+ let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
344
+
345
+ before do
346
+ product_a
347
+ product_b
348
+ product_c
349
+
350
+ product_variation_a
351
+ product_variation_b
352
+ product_variation_c
353
+ product_variation_d
354
+ product_variation_e
355
+ product_variation_f
356
+ end
357
+
358
+ it 'is expected to contain the matching records' do
359
+ expect(perform).to contain_exactly(product_a, product_b)
360
+ end
361
+ end
362
+
363
+ context 'when passed a `greater than or equal to` relationship count query' do
364
+ let(:query) { 'variations>=2' }
365
+
366
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
367
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
368
+ let(:product_c) { Product.create!(title: 'Socks') }
369
+
370
+ let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
371
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
372
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
373
+ let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
374
+ let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
375
+ let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
376
+
377
+ before do
378
+ product_a
379
+ product_b
380
+ product_c
381
+
382
+ product_variation_a
383
+ product_variation_b
384
+ product_variation_c
385
+ product_variation_d
386
+ product_variation_e
387
+ product_variation_f
388
+ end
389
+
390
+ it 'is expected to contain the matching records' do
391
+ expect(perform).to contain_exactly(product_a, product_b)
392
+ end
393
+ end
394
+
395
+ context 'when passed a `less than` relationship count query' do
396
+ let(:query) { 'variations<3' }
397
+
398
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
399
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
400
+ let(:product_c) { Product.create!(title: 'Socks') }
401
+
402
+ let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
403
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
404
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
405
+ let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
406
+ let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
407
+ let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
408
+
409
+ before do
410
+ product_a
411
+ product_b
412
+ product_c
413
+
414
+ product_variation_a
415
+ product_variation_b
416
+ product_variation_c
417
+ product_variation_d
418
+ product_variation_e
419
+ product_variation_f
420
+ end
421
+
422
+ it 'is expected to contain the matching records' do
423
+ expect(perform).to contain_exactly(product_a, product_c)
424
+ end
425
+ end
426
+
427
+ context 'when passed a `less than or equal to` relationship count query' do
428
+ let(:query) { 'variations<=2' }
429
+
430
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
431
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
432
+ let(:product_c) { Product.create!(title: 'Socks') }
433
+
434
+ let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
435
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
436
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
437
+ let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
438
+ let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
439
+ let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
440
+
441
+ before do
442
+ product_a
443
+ product_b
444
+ product_c
445
+
446
+ product_variation_a
447
+ product_variation_b
448
+ product_variation_c
449
+ product_variation_d
450
+ product_variation_e
451
+ product_variation_f
452
+ end
453
+
454
+ it 'is expected to contain the matching records' do
455
+ expect(perform).to contain_exactly(product_a, product_c)
456
+ end
457
+ end
458
+
459
+ context 'when passed a `greater than` and `less than` relationship count query' do
460
+ let(:query) { 'variations>1 variations<3' }
461
+
462
+ let(:product_a) { Product.create!(title: 'Plain Shirt') }
463
+ let(:product_b) { Product.create!(title: 'Funky Shirt') }
464
+ let(:product_c) { Product.create!(title: 'Socks') }
465
+
466
+ let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
467
+ let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
468
+ let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
469
+ let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
470
+ let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
471
+ let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
472
+
473
+ before do
474
+ product_a
475
+ product_b
476
+ product_c
477
+
478
+ product_variation_a
479
+ product_variation_b
480
+ product_variation_c
481
+ product_variation_d
482
+ product_variation_e
483
+ product_variation_f
484
+ end
485
+
486
+ it 'is expected to contain the matching records' do
487
+ expect(perform).to contain_exactly(product_a)
488
+ end
399
489
  end
400
490
  end
401
491
 
402
- context 'when passed a `less than or equal to` relationship count query' do
403
- let(:query) { 'variations<=2' }
492
+ context 'when searching a #belongs_to relationship' do
493
+ context 'when passed a `match` relationship search query' do
494
+ let(:query) { 'category*=shirt' }
404
495
 
405
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
406
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
407
- let(:product_c) { Product.create!(title: 'Socks') }
496
+ let(:product_category_a) { ProductCategory.create!(id: 'one', name: 'Shirts') }
497
+ let(:product_category_b) { ProductCategory.create!(id: 'two', name: 'Socks') }
408
498
 
409
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
410
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
411
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
412
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
413
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
414
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
499
+ let(:product_a) { Product.create!(category: product_category_a, title: 'Plain Shirt') }
500
+ let(:product_b) { Product.create!(category: product_category_a, title: 'Funky Shirt') }
501
+ let(:product_c) { Product.create!(category: product_category_b, title: 'White Socks') }
415
502
 
416
- before do
417
- product_a
418
- product_b
419
- product_c
420
-
421
- product_variation_a
422
- product_variation_b
423
- product_variation_c
424
- product_variation_d
425
- product_variation_e
426
- product_variation_f
427
- end
428
-
429
- it 'is expected to contain the matching records' do
430
- expect(perform).to contain_exactly(product_a, product_c)
431
- end
432
- end
503
+ before do
504
+ product_category_a
505
+ product_category_b
433
506
 
434
- context 'when passed a `greater than` and `less than` relationship count query' do
435
- let(:query) { 'variations>1 variations<3' }
507
+ product_a
508
+ product_b
509
+ product_c
510
+ end
436
511
 
437
- let(:product_a) { Product.create!(title: 'Plain Shirt') }
438
- let(:product_b) { Product.create!(title: 'Funky Shirt') }
439
- let(:product_c) { Product.create!(title: 'Socks') }
440
-
441
- let(:product_variation_a) { ProductVariation.create!(product: product_b, title: 'Red') }
442
- let(:product_variation_b) { ProductVariation.create!(product: product_b, title: 'Green') }
443
- let(:product_variation_c) { ProductVariation.create!(product: product_b, title: 'Blue') }
444
- let(:product_variation_d) { ProductVariation.create!(product: product_c, title: 'White') }
445
- let(:product_variation_e) { ProductVariation.create!(product: product_a, title: 'Black') }
446
- let(:product_variation_f) { ProductVariation.create!(product: product_a, title: 'Gray') }
447
-
448
- before do
449
- product_a
450
- product_b
451
- product_c
452
-
453
- product_variation_a
454
- product_variation_b
455
- product_variation_c
456
- product_variation_d
457
- product_variation_e
458
- product_variation_f
459
- end
460
-
461
- it 'is expected to contain the matching records' do
462
- expect(perform).to contain_exactly(product_a)
512
+ it 'is expected to contain the matching records' do
513
+ expect(perform).to contain_exactly(product_a, product_b)
514
+ end
463
515
  end
464
516
  end
465
517
  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.3.0
4
+ version: 0.4.1
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-19 00:00:00.000000000 Z
11
+ date: 2022-01-31 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