search_object 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4cce323b42516f0df8bff1b13275f60dcf0439cf
4
- data.tar.gz: 35c0d32d6011eae2946e2758ebcf4222b3392f32
2
+ SHA256:
3
+ metadata.gz: c19b1b27bb979fcee5671d55ef380f628fb8ba624c274166ad40aa8970d8ad2f
4
+ data.tar.gz: 4dc53dd30f010996cea99732d647d44d6d2d3eb07b8a39920b590dabdad5caa1
5
5
  SHA512:
6
- metadata.gz: 6df95c9bff5882b28585bf3de86623d2015bdb633fabc8eac0b82e330ce11671ef09f581e7bc25a81e1157db828d0334e513a859bed0971dcfe36e1b906bbace
7
- data.tar.gz: 9ef427a40467a42bfde8499f6066de5a98481c66d92c7830b021c992386adf0172bca7df56795ab11c442659103d2902fffcf858f00e8e0608463b47460443b7
6
+ metadata.gz: 702e7451fa287d3e98c75dc00fe0977ed4bc901fd72b4bd26fd18089c6abc200f0a03b78be6312b3217a3ecbb193b9a0346cf14834000aecd517968d41b5384b
7
+ data.tar.gz: 3b2cdb6312c1ac99afcbb1ea498339c6f732ab38bd45359af4ee84fc7543b5966bc6e5b797fb2525975c478fca4e10d5719487f8da9a87884de13cbcc9509bb5
data/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## Version 1.2.0 (unreleased)
3
+ ## Version 1.2.1
4
+
5
+ * __[feature]__ Add `default:` option to `sort_by` plugin (@rstankov)
6
+
7
+ ```ruby
8
+ class ProductSearch
9
+ include SearchObject.module(:sorting)
10
+
11
+ scope { Product.all }
12
+
13
+ sort_by :name, :price, :created_at, default: 'price asc'
14
+ end
15
+ ```
16
+
17
+ ## Version 1.2.0
4
18
 
5
19
  * __[feature]__ `enum` plugin added (@rstankov)
6
20
 
data/README.md CHANGED
@@ -5,7 +5,40 @@
5
5
 
6
6
  # SearchObject
7
7
 
8
- In many of my projects I needed an object that performs several fairly complicated queries. Or, just some multi-field search forms. Most times I hand-coded them, but they would get complicated over time when other concerns were added like sorting, pagination and so on. So I decided to abstract this away and created ```SearchObject```, a DSL for creating such objects.
8
+ In many of my projects I needed an object that performs several fairly complicated queries. Most times I hand-coded them, but they would get complicated over time when other concerns like sorting, pagination and so are being added. So I decided to abstract this away and created ```SearchObject```, a DSL for creating such objects.
9
+
10
+ It is useful for:
11
+
12
+ - complicated search forms
13
+ - api endpoints with multiple filter conditions
14
+ - [GraphQL](https://rmosolgo.github.io/graphql-ruby/) resolvers
15
+ - ... search objects 😀
16
+
17
+ ## Table of Contents
18
+
19
+ * [Installation](#installation)
20
+ * [Dependencies](#dependencies)
21
+ * [Usage](#usage)
22
+ * [Example](#example)
23
+ * [Plugins](#plugins)
24
+ * [Paginate Plugin](#paginate-plugin)
25
+ * [Enum Plugin](#enum-plugin)
26
+ * [Model Plugin](#model-plugin)
27
+ * [GraphQL Plugin](#graphql-plugin)
28
+ * [Sorting Plugin](#sorting-plugin)
29
+ * [Tips & Tricks](#tips--tricks)
30
+ * [Results Shortcut](#results-shortcut)
31
+ * [Passing Scope as Argument](#passing-scope-as-argument)
32
+ * [Handling Nil Options](#handling-nil-options)
33
+ * [Default Option Block](#default-option-block)
34
+ * [Using Instance Method in Option Blocks](#using-instance-method-in-option-blocks)
35
+ * [Using Instance Method for Straight Dispatch](#using-instance-method-for-straight-dispatch)
36
+ * [Active Record Is Not Required](#active-record-is-not-required)
37
+ * [Overwriting Methods](#overwriting-methods)
38
+ * [Extracting Basic Module](#extracting-basic-module#)
39
+ * [Contributing](#contributing)
40
+ * [License](#license)
41
+
9
42
 
10
43
  ## Installation
11
44
 
@@ -59,7 +92,7 @@ search.params # => option values
59
92
  search.params opened: false # => overwrites the 'opened' option
60
93
  ```
61
94
 
62
- ## Example
95
+ ### Example
63
96
 
64
97
  You can find example of most important features and plugins - [here](https://github.com/RStankov/SearchObject/tree/master/example).
65
98
 
@@ -69,7 +102,7 @@ You can find example of most important features and plugins - [here](https://git
69
102
 
70
103
  Plugins are just plain Ruby modules, which are included with ```SearchObject.module```. They are located under ```SearchObject::Plugin``` module.
71
104
 
72
- ### Paginate plugin
105
+ ### Paginate Plugin
73
106
 
74
107
  Really simple paginate plugin, which uses the plain ```.limit``` and ```.offset``` methods.
75
108
 
@@ -104,7 +137,7 @@ include SearchObject.module(:will_paginate)
104
137
  include SearchObject.module(:kaminari)
105
138
  ```
106
139
 
107
- ### Enum plugin
140
+ ### Enum Plugin
108
141
 
109
142
  Gives you filter with pre-defined options.
110
143
 
@@ -135,7 +168,7 @@ class ProductSearch
135
168
  end
136
169
  ```
137
170
 
138
- ### Model plugin
171
+ ### Model Plugin
139
172
 
140
173
  Extends your search object with ```ActiveModel```, so you can use it in Rails forms.
141
174
 
@@ -161,7 +194,28 @@ end
161
194
  <% end %>
162
195
  ```
163
196
 
164
- ### Sorting plugin
197
+ ### GraphQL Plugin
198
+
199
+ Installed as separate [gem](https://github.com/RStankov/SearchObjectGraphQL), it is designed to work with GraphQL:
200
+
201
+ ```
202
+ gem 'search_object_graphql'
203
+ ```
204
+
205
+ ```ruby
206
+ class PostResolver
207
+ include SearchObject.module(:graphql)
208
+
209
+ type PostType
210
+
211
+ scope { Post.all }
212
+
213
+ option(:name, type: types.String) { |scope, value| scope.where name: value }
214
+ option(:published, type: types.Boolean) { |scope, value| value ? scope.published : scope.unpublished }
215
+ end
216
+ ```
217
+
218
+ ### Sorting Plugin
165
219
 
166
220
  Fixing the pain of dealing with sorting attributes and directions.
167
221
 
@@ -197,7 +251,7 @@ search.sort_params_for('name')
197
251
 
198
252
  ## Tips & Tricks
199
253
 
200
- ### Results shortcut
254
+ ### Results Shortcut
201
255
 
202
256
  Very often you will just need results of search:
203
257
 
@@ -205,7 +259,7 @@ Very often you will just need results of search:
205
259
  ProductSearch.new(params).results == ProductSearch.results(params)
206
260
  ```
207
261
 
208
- ### Passing scope as argument
262
+ ### Passing Scope as Argument
209
263
 
210
264
  ``` ruby
211
265
  class ProductSearch
@@ -217,8 +271,7 @@ search = ProductSearch.new scope: Product.visible, filters: params[:f]
217
271
  search.results # => includes only visible products
218
272
  ```
219
273
 
220
-
221
- ### Handling nil options
274
+ ### Handling Nil Options
222
275
 
223
276
  ```ruby
224
277
  class ProductSearch
@@ -231,7 +284,7 @@ class ProductSearch
231
284
  end
232
285
  ```
233
286
 
234
- ### Default option block
287
+ ### Default Option Block
235
288
 
236
289
  ```ruby
237
290
  class ProductSearch
@@ -243,7 +296,7 @@ class ProductSearch
243
296
  end
244
297
  ```
245
298
 
246
- ### Using instance method in option blocks
299
+ ### Using Instance Method in Option Blocks
247
300
 
248
301
  ```ruby
249
302
  class ProductSearch
@@ -261,7 +314,7 @@ class ProductSearch
261
314
  end
262
315
  ```
263
316
 
264
- ### Using instance method for straight dispatch
317
+ ### Using Instance Method for Straight Dispatch
265
318
 
266
319
  ```ruby
267
320
  class ProductSearch
@@ -279,7 +332,7 @@ class ProductSearch
279
332
  end
280
333
  ```
281
334
 
282
- ### Active Record is not required at all
335
+ ### Active Record Is Not Required
283
336
 
284
337
  ```ruby
285
338
  class ProductSearch
@@ -292,7 +345,7 @@ class ProductSearch
292
345
  end
293
346
  ```
294
347
 
295
- ### Overwriting methods
348
+ ### Overwriting Methods
296
349
 
297
350
  You can have fine grained scope, by overwriting ```initialize``` method:
298
351
 
@@ -333,7 +386,7 @@ class ProductSearch
333
386
  end
334
387
  ```
335
388
 
336
- ### Extracting basic module
389
+ ### Extracting Basic Module
337
390
 
338
391
  You can extarct a basic search class for your application.
339
392
 
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require 'rubocop/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
  RuboCop::RakeTask.new(:rubocop)
7
7
 
8
- task default: [:rubocop, :spec]
8
+ task default: %i[rubocop spec]
data/example/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # SearchObject example Rails application
1
+ # SearchObject Example Rails Application
2
2
 
3
3
  This is example application showing, one of the possible usages of ```SearchObject```. It showcases the following features:
4
4
 
5
5
  * Basic search object functionality
6
6
  * Default options
7
7
  * Using private method helpers in options
8
- * Plugins: model, sorting, will_paginate
8
+ * Plugins: model, sorting, will_paginate, enum
9
9
 
10
10
  ## Interesting files:
11
11
 
@@ -15,7 +15,7 @@ class PostSearch
15
15
 
16
16
  option :term, with: :apply_term
17
17
 
18
- option :rating, enum: %i(low high)
18
+ option :rating, enum: %i[low high]
19
19
 
20
20
  option :title do |scope, value|
21
21
  scope.where 'title LIKE ?', escape_search_term(value)
@@ -51,7 +51,7 @@ class PostSearch
51
51
 
52
52
  def parse_date(value)
53
53
  Date.parse(value).strftime('%Y-%m-%d')
54
- rescue
54
+ rescue ArgumentError
55
55
  nil
56
56
  end
57
57
 
@@ -19,7 +19,7 @@ module SearchObject
19
19
  end
20
20
 
21
21
  def sort_direction
22
- @sort_direction ||= Helper.ensure_included sort.to_s.split(' ', 2).last, %w(desc asc)
22
+ @sort_direction ||= Helper.ensure_included sort.to_s.split(' ', 2).last, %w[desc asc]
23
23
  end
24
24
 
25
25
  def sort_direction_for(attribute)
@@ -40,9 +40,9 @@ module SearchObject
40
40
  end
41
41
 
42
42
  module ClassMethods
43
- def sort_by(*attributes)
43
+ def sort_by(*attributes, default: nil)
44
44
  config[:sort_attributes] = attributes.map(&:to_s)
45
- config[:defaults]['sort'] = "#{config[:sort_attributes].first} desc"
45
+ config[:defaults]['sort'] = default || "#{config[:sort_attributes].first} desc"
46
46
  end
47
47
 
48
48
  def sort_attributes
@@ -1,3 +1,3 @@
1
1
  module SearchObject
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.2.1'.freeze
3
3
  end
@@ -29,6 +29,6 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'will_paginate'
30
30
  spec.add_development_dependency 'kaminari'
31
31
  spec.add_development_dependency 'kaminari-activerecord'
32
- spec.add_development_dependency 'rubocop', '0.46.0'
33
- spec.add_development_dependency 'rubocop-rspec', '1.8.0'
32
+ spec.add_development_dependency 'rubocop', '0.51.0'
33
+ spec.add_development_dependency 'rubocop-rspec', '1.20.1'
34
34
  end
@@ -77,7 +77,7 @@ module SearchObject
77
77
  expect(inequality_search.new(filters: { value: 1 }).results).to eq [2, 3]
78
78
  end
79
79
 
80
- context 'scope' do
80
+ describe 'scope' do
81
81
  def search_class
82
82
  define_search_class do
83
83
  option :name do
@@ -258,11 +258,11 @@ module SearchObject
258
258
 
259
259
  describe '#results?' do
260
260
  it 'returns true if there are results' do
261
- expect(new_search([1, 2, 3], value: 1).results?).to be_truthy
261
+ expect(new_search([1, 2, 3], value: 1)).to be_results
262
262
  end
263
263
 
264
264
  it 'returns false if there are no results' do
265
- expect(new_search([1, 2, 3], value: 4).results?).to be_falsey
265
+ expect(new_search([1, 2, 3], value: 4)).not_to be_results
266
266
  end
267
267
  end
268
268
 
@@ -18,12 +18,12 @@ module SearchObject
18
18
 
19
19
  describe '.slice_keys' do
20
20
  it 'selects only given keys' do
21
- hash = described_class.slice_keys({ a: 1, b: 2, c: 3 }, [:a, :b])
21
+ hash = described_class.slice_keys({ a: 1, b: 2, c: 3 }, %i[a b])
22
22
  expect(hash).to eq a: 1, b: 2
23
23
  end
24
24
 
25
25
  it 'ignores not existing keys' do
26
- hash = described_class.slice_keys({}, [:a, :b])
26
+ hash = described_class.slice_keys({}, %i[a b])
27
27
  expect(hash).to eq({})
28
28
  end
29
29
  end
@@ -43,15 +43,15 @@ module SearchObject
43
43
 
44
44
  describe '.normalize_filters' do
45
45
  it 'combines defaults and filters' do
46
- expect(described_class.normalize_params({ 'a' => 1, 'b' => 2 }, { a: 2 }, %w(a b))).to eq 'a' => 2, 'b' => 2
46
+ expect(described_class.normalize_params({ 'a' => 1, 'b' => 2 }, { a: 2 }, %w[a b])).to eq 'a' => 2, 'b' => 2
47
47
  end
48
48
 
49
49
  it 'excludes non specified keys' do
50
- expect(described_class.normalize_params({ 'a' => 1 }, { b: 2 }, %w(a))).to eq 'a' => 1
50
+ expect(described_class.normalize_params({ 'a' => 1 }, { b: 2 }, %w[a])).to eq 'a' => 1
51
51
  end
52
52
 
53
53
  it 'handles missing defaults' do
54
- expect(described_class.normalize_params(nil, { a: 1 }, %w(a))).to eq 'a' => 1
54
+ expect(described_class.normalize_params(nil, { a: 1 }, %w[a])).to eq 'a' => 1
55
55
  end
56
56
 
57
57
  it 'handles missing filters' do
@@ -9,7 +9,7 @@ module SearchObject
9
9
 
10
10
  scope { [1, 2, 3, 4, 5] }
11
11
 
12
- option :filter, enum: %w(odd even)
12
+ option :filter, enum: %w[odd even]
13
13
 
14
14
  private
15
15
 
@@ -45,7 +45,7 @@ module SearchObject
45
45
  Class.new do
46
46
  include SearchObject.module(:enum)
47
47
 
48
- option(:filter, enum: %w(a b)) { |_scope, _value| nil }
48
+ option(:filter, enum: %w[a b]) { |_scope, _value| nil }
49
49
  end
50
50
  end.to raise_error Enum::BlockIgnoredError
51
51
  end
@@ -55,7 +55,7 @@ module SearchObject
55
55
  Class.new do
56
56
  include SearchObject.module(:enum)
57
57
 
58
- option :filter, enum: %w(a b), with: :method_name
58
+ option :filter, enum: %w[a b], with: :method_name
59
59
  end
60
60
  end.to raise_error Enum::WithIgnoredError
61
61
  end
@@ -91,10 +91,10 @@ module SearchObject
91
91
  end
92
92
  end
93
93
 
94
- scope = %w(name age location)
94
+ scope = %w[name age location]
95
95
 
96
- expect(call(object: object, option: 'select', scope: scope, value: 'name')).to eq %w(name)
97
- expect(call(object: object, option: 'select', scope: scope, value: 'age')).to eq %w(age)
96
+ expect(call(object: object, option: 'select', scope: scope, value: 'name')).to eq %w[name]
97
+ expect(call(object: object, option: 'select', scope: scope, value: 'age')).to eq %w[age]
98
98
  end
99
99
 
100
100
  it 'raises NoMethodError when object can not handle enum method' do
@@ -7,7 +7,7 @@ module SearchObject
7
7
  it_behaves_like 'a paging plugin' do
8
8
  it 'uses kaminari gem' do
9
9
  search = search_with_page
10
- expect(search.results.respond_to?(:total_pages)).to be_truthy
10
+ expect(search.results).to be_respond_to :total_pages
11
11
  end
12
12
  end
13
13
  end
@@ -39,6 +39,10 @@ module SearchObject
39
39
  expect(value).to eq(expected_value), message
40
40
  end
41
41
 
42
+ def assert_respond_to(object, method, message = nil)
43
+ expect(object).to respond_to(method), message
44
+ end
45
+
42
46
  ActiveModel::Lint::Tests.public_instance_methods.map(&:to_s).grep(/^test/).each do |method|
43
47
  example(method.tr('_', ' ')) { send method }
44
48
  end
@@ -39,10 +39,24 @@ module SearchObject
39
39
  end
40
40
 
41
41
  it 'defaults to first sort by option' do
42
+ search_class = Class.new do
43
+ include SearchObject.module(:sorting)
44
+
45
+ scope { Product.all }
46
+
47
+ sort_by :name, :price, :created_at, default: :price
48
+ end
49
+
50
+ 5.times { |i| Product.create! price: i }
51
+
52
+ expect(search_class.new.results.map(&:price)).to eq [4, 3, 2, 1, 0]
53
+ end
54
+
55
+ it 'accepts default sorting' do
42
56
  5.times { |i| Product.create! name: "Name#{i}" }
43
57
 
44
58
  search = search_with_sort
45
- expect(search.results.map(&:name)).to eq %w(Name4 Name3 Name2 Name1 Name0)
59
+ expect(search.results.map(&:name)).to eq %w[Name4 Name3 Name2 Name1 Name0]
46
60
  end
47
61
 
48
62
  it 'ignores invalid sort values' do
@@ -67,22 +81,22 @@ module SearchObject
67
81
  it 'matches the sort option' do
68
82
  search = search_with_sort 'price desc'
69
83
 
70
- expect(search.sort?(:price)).to be_truthy
71
- expect(search.sort?(:name)).to be_falsey
84
+ expect(search).to be_sort :price
85
+ expect(search).not_to be_sort :name
72
86
  end
73
87
 
74
88
  it 'matches string also' do
75
89
  search = search_with_sort 'price desc'
76
90
 
77
- expect(search.sort?('price')).to be_truthy
78
- expect(search.sort?('name')).to be_falsey
91
+ expect(search).to be_sort 'price'
92
+ expect(search).not_to be_sort 'name'
79
93
  end
80
94
 
81
95
  it 'matches exact strings' do
82
96
  search = search_with_sort 'price desc'
83
97
 
84
- expect(search.sort?('price desc')).to be_truthy
85
- expect(search.sort?('price asc')).to be_falsey
98
+ expect(search).to be_sort 'price desc'
99
+ expect(search).not_to be_sort 'price asc'
86
100
  end
87
101
  end
88
102
 
@@ -9,7 +9,7 @@ module SearchObject
9
9
  it_behaves_like 'a paging plugin' do
10
10
  it 'uses will_paginate gem' do
11
11
  search = search_with_page
12
- expect(search.results.respond_to?(:total_entries)).to be_truthy
12
+ expect(search.results).to be_respond_to :total_entries
13
13
  end
14
14
  end
15
15
  end
@@ -37,7 +37,7 @@ shared_examples_for 'a paging plugin' do
37
37
  6.times { |i| Product.create name: "product_#{i}" }
38
38
  search = search_with_page 2, 2
39
39
 
40
- expect(search.results.map(&:name)).to eq %w(product_2 product_3)
40
+ expect(search.results.map(&:name)).to eq %w[product_2 product_3]
41
41
  end
42
42
  end
43
43
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radoslav Stankov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-27 00:00:00.000000000 Z
11
+ date: 2018-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -170,28 +170,28 @@ dependencies:
170
170
  requirements:
171
171
  - - '='
172
172
  - !ruby/object:Gem::Version
173
- version: 0.46.0
173
+ version: 0.51.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - '='
179
179
  - !ruby/object:Gem::Version
180
- version: 0.46.0
180
+ version: 0.51.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: rubocop-rspec
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - '='
186
186
  - !ruby/object:Gem::Version
187
- version: 1.8.0
187
+ version: 1.20.1
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - '='
193
193
  - !ruby/object:Gem::Version
194
- version: 1.8.0
194
+ version: 1.20.1
195
195
  description: Search object DSL
196
196
  email:
197
197
  - rstankov@gmail.com
@@ -294,7 +294,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
294
294
  version: '0'
295
295
  requirements: []
296
296
  rubyforge_project:
297
- rubygems_version: 2.4.5
297
+ rubygems_version: 2.7.6
298
298
  signing_key:
299
299
  specification_version: 4
300
300
  summary: Provides DSL for creating search objects