search_object 1.2.0 → 1.2.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
- 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