pursuit 0.2.0 → 0.4.0

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: 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