search_object 1.2.1 → 1.2.5
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 +4 -4
- data/.rubocop.yml +18 -3
- data/.travis.yml +4 -2
- data/CHANGELOG.md +19 -2
- data/Gemfile +2 -0
- data/README.md +5 -5
- data/Rakefile +2 -0
- data/example/Gemfile +3 -1
- data/example/README.md +3 -3
- data/example/Rakefile +3 -1
- data/example/app/controllers/application_controller.rb +2 -0
- data/example/app/controllers/posts_controller.rb +2 -0
- data/example/app/models/post.rb +2 -0
- data/example/app/models/post_search.rb +6 -4
- data/example/app/models/user.rb +2 -0
- data/example/app/views/posts/index.html.slim +1 -1
- data/example/bin/bundle +3 -1
- data/example/bin/rails +3 -1
- data/example/bin/rake +2 -0
- data/example/config.ru +2 -0
- data/example/db/migrate/20131102130117_create_users.rb +1 -1
- data/example/db/migrate/20131102130413_create_posts.rb +1 -2
- data/example/db/schema.rb +19 -23
- data/example/spec/models/post_search_spec.rb +19 -19
- data/example/spec/spec_helper.rb +4 -2
- data/lib/search_object/base.rb +27 -7
- data/lib/search_object/errors.rb +2 -0
- data/lib/search_object/helper.rb +2 -4
- data/lib/search_object/plugin/enum.rb +6 -10
- data/lib/search_object/plugin/kaminari.rb +2 -0
- data/lib/search_object/plugin/model.rb +2 -0
- data/lib/search_object/plugin/paging.rb +8 -3
- data/lib/search_object/plugin/sorting.rb +2 -0
- data/lib/search_object/plugin/will_paginate.rb +2 -0
- data/lib/search_object/search.rb +14 -6
- data/lib/search_object/version.rb +3 -1
- data/lib/search_object.rb +2 -0
- data/search_object.gemspec +3 -3
- data/spec/search_object/base_spec.rb +58 -0
- data/spec/search_object/helper_spec.rb +2 -18
- data/spec/search_object/plugin/enum_spec.rb +57 -31
- data/spec/search_object/plugin/kaminari_spec.rb +2 -0
- data/spec/search_object/plugin/model_spec.rb +2 -0
- data/spec/search_object/plugin/paging_spec.rb +2 -0
- data/spec/search_object/plugin/sorting_spec.rb +2 -0
- data/spec/search_object/plugin/will_paginate_spec.rb +2 -0
- data/spec/search_object/search_spec.rb +40 -21
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_helper_active_record.rb +2 -0
- data/spec/support/paging_shared_example.rb +2 -0
- metadata +14 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c01187c2852ea65c1c340bc37e62527c9d4133691ded6a9db4ac5dea78441b1d
|
4
|
+
data.tar.gz: 177bf4f66eccf02f9195a9aa2b8563f8d1de4465be812157f9eeec1ac90121a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cd78261ad83342fec752d1e54714afe7971841ae5afa90270bd90a8a1713cbb2aa06fdbad3489be78127195666015918f9f356ec1f4c9809b201f5631fc0795
|
7
|
+
data.tar.gz: 575474f4d79ff650bccb1cb9e9d33cb540d4b85dfc18a23487e1239e6a6833bf4da7dc52193829d2c116a51c9089736720eb6ac5db5868b814558fe15675fc34
|
data/.rubocop.yml
CHANGED
@@ -7,15 +7,15 @@ AllCops:
|
|
7
7
|
- search_object.gemspec
|
8
8
|
|
9
9
|
# Disables "Line is too long"
|
10
|
-
LineLength:
|
10
|
+
Layout/LineLength:
|
11
11
|
Enabled: false
|
12
12
|
|
13
13
|
# Disables Module has too many lines
|
14
|
-
ModuleLength:
|
14
|
+
Metrics/ModuleLength:
|
15
15
|
Enabled: false
|
16
16
|
|
17
17
|
# Disables "Missing top-level class documentation comment"
|
18
|
-
Documentation:
|
18
|
+
Style/Documentation:
|
19
19
|
Enabled: false
|
20
20
|
|
21
21
|
# Disables "Use each_with_object instead of inject"
|
@@ -37,3 +37,18 @@ RSpec/ExampleLength:
|
|
37
37
|
# Disables "Too many expectations."
|
38
38
|
RSpec/MultipleExpectations:
|
39
39
|
Enabled: false
|
40
|
+
|
41
|
+
Lint/RaiseException:
|
42
|
+
Enabled: true
|
43
|
+
|
44
|
+
Lint/StructNewOverride:
|
45
|
+
Enabled: true
|
46
|
+
|
47
|
+
Style/HashEachMethods:
|
48
|
+
Enabled: true
|
49
|
+
|
50
|
+
Style/HashTransformKeys:
|
51
|
+
Enabled: true
|
52
|
+
|
53
|
+
Style/HashTransformValues:
|
54
|
+
Enabled: true
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 1.2.5
|
4
|
+
|
5
|
+
* __[feature]__ Added `param?` method to `Search::Base` (@rstankov)
|
6
|
+
|
7
|
+
## Version 1.2.4
|
8
|
+
|
9
|
+
* __[feature]__ Added `:with` and block support to enum (@hschne)
|
10
|
+
|
11
|
+
## Version 1.2.3
|
12
|
+
|
13
|
+
* __[fix]__ convert enum values to strings (@Postmodum37)
|
14
|
+
|
15
|
+
## Version 1.2.2
|
16
|
+
|
17
|
+
* __[feature]__ Added `SearchObject::Base#params=` method, to reset search results (@rstankov)
|
18
|
+
* __[change]__ `option :orderBy, enum: %(price date)`, now expects a method `apply_order_by_x`, instead of `apply_orderBy_with_` (__backward incompatible__) (@rstankov)
|
19
|
+
|
3
20
|
## Version 1.2.1
|
4
21
|
|
5
|
-
* __[feature]__
|
22
|
+
* __[feature]__ Added `default:` option to `sort_by` plugin (@rstankov)
|
6
23
|
|
7
24
|
```ruby
|
8
25
|
class ProductSearch
|
@@ -16,7 +33,7 @@ end
|
|
16
33
|
|
17
34
|
## Version 1.2.0
|
18
35
|
|
19
|
-
* __[feature]__ `enum` plugin
|
36
|
+
* __[feature]__ Added `enum` plugin (@rstankov)
|
20
37
|
|
21
38
|
```ruby
|
22
39
|
class ProductSearch
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -76,7 +76,7 @@ end
|
|
76
76
|
Then you can just search the given scope:
|
77
77
|
|
78
78
|
```ruby
|
79
|
-
search = PostSearch.new
|
79
|
+
search = PostSearch.new(filters: params[:filters])
|
80
80
|
|
81
81
|
# accessing search options
|
82
82
|
search.name # => name option
|
@@ -115,7 +115,7 @@ class ProductSearch
|
|
115
115
|
option :name
|
116
116
|
option :category_name
|
117
117
|
|
118
|
-
# per page defaults to
|
118
|
+
# per page defaults to 10
|
119
119
|
per_page 10
|
120
120
|
|
121
121
|
# range of values is also possible
|
@@ -123,7 +123,7 @@ class ProductSearch
|
|
123
123
|
max_per_page 100
|
124
124
|
end
|
125
125
|
|
126
|
-
search = ProductSearch.new
|
126
|
+
search = ProductSearch.new(filters: params[:filters], page: params[:page], per_page: params[:per_page])
|
127
127
|
|
128
128
|
search.page # => page number
|
129
129
|
search.per_page # => per page (10)
|
@@ -228,7 +228,7 @@ class ProductSearch
|
|
228
228
|
sort_by :name, :price
|
229
229
|
end
|
230
230
|
|
231
|
-
search = ProductSearch.new
|
231
|
+
search = ProductSearch.new(filters: {sort: 'price desc'})
|
232
232
|
|
233
233
|
search.results # => Product sorted my price DESC
|
234
234
|
search.sort_attribute # => 'price'
|
@@ -267,7 +267,7 @@ class ProductSearch
|
|
267
267
|
end
|
268
268
|
|
269
269
|
# first arguments is treated as scope (if no scope option is provided)
|
270
|
-
search = ProductSearch.new
|
270
|
+
search = ProductSearch.new(scope: Product.visible, filters: params[:f])
|
271
271
|
search.results # => includes only visible products
|
272
272
|
```
|
273
273
|
|
data/Rakefile
CHANGED
data/example/Gemfile
CHANGED
data/example/README.md
CHANGED
@@ -19,9 +19,9 @@ This is example application showing, one of the possible usages of ```SearchObje
|
|
19
19
|
```
|
20
20
|
gem install bundler
|
21
21
|
bundle install
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
rails db:create
|
23
|
+
rails db:migrate
|
24
|
+
rails db:seed
|
25
25
|
|
26
26
|
rails server
|
27
27
|
```
|
data/example/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
4
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('config/application', __dir__)
|
5
7
|
|
6
8
|
Example::Application.load_tasks
|
data/example/app/models/post.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class PostSearch
|
2
4
|
include SearchObject.module(:model, :sorting, :will_paginate, :enum)
|
3
5
|
|
4
|
-
scope { Post.all }
|
6
|
+
scope { Post.includes(:user).all }
|
5
7
|
|
6
8
|
sort_by :id, :created_at, :views_count, :likes_count, :comments_count
|
7
9
|
|
@@ -18,11 +20,11 @@ class PostSearch
|
|
18
20
|
option :rating, enum: %i[low high]
|
19
21
|
|
20
22
|
option :title do |scope, value|
|
21
|
-
scope.where 'title LIKE ?', escape_search_term(value)
|
23
|
+
scope.where 'title LIKE ?', escape_search_term(value) if value.present?
|
22
24
|
end
|
23
25
|
|
24
26
|
option :published do |scope, value|
|
25
|
-
scope.where published: true if value.present?
|
27
|
+
scope.where published: true if value.present? && value != '0'
|
26
28
|
end
|
27
29
|
|
28
30
|
option :created_after do |scope, value|
|
@@ -38,7 +40,7 @@ class PostSearch
|
|
38
40
|
private
|
39
41
|
|
40
42
|
def apply_term(scope, value)
|
41
|
-
scope.where 'title LIKE :term OR body LIKE :term', term: escape_search_term(value)
|
43
|
+
scope.where 'title LIKE :term OR body LIKE :term', term: escape_search_term(value) if value.present?
|
42
44
|
end
|
43
45
|
|
44
46
|
def apply_rating_with_low(scope)
|
data/example/app/models/user.rb
CHANGED
@@ -32,7 +32,7 @@
|
|
32
32
|
th
|
33
33
|
th = form.text_field :title
|
34
34
|
th = form.select :user_id, User.all.map { |u| [u.name, u.id] }, include_blank: true
|
35
|
-
th = form.select :category_name, Post.pluck('DISTINCT category_name'), include_blank: true
|
35
|
+
th = form.select :category_name, Post.pluck(Arel.sql('DISTINCT category_name')), include_blank: true
|
36
36
|
th
|
37
37
|
th
|
38
38
|
th
|
data/example/bin/bundle
CHANGED
data/example/bin/rails
CHANGED
data/example/bin/rake
CHANGED
data/example/config.ru
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class CreatePosts < ActiveRecord::Migration
|
1
|
+
class CreatePosts < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :posts do |t|
|
4
4
|
t.integer :user_id, null: false
|
@@ -9,7 +9,6 @@ class CreatePosts < ActiveRecord::Migration
|
|
9
9
|
t.integer :likes_count, null: false, default: 0
|
10
10
|
t.integer :comments_count, null: false, default: 0
|
11
11
|
t.boolean :published, null: false, default: false
|
12
|
-
t.datetime :published_at
|
13
12
|
t.timestamps
|
14
13
|
end
|
15
14
|
|
data/example/db/schema.rb
CHANGED
@@ -1,40 +1,36 @@
|
|
1
|
-
# encoding: UTF-8
|
2
1
|
# This file is auto-generated from the current state of the database. Instead
|
3
2
|
# of editing this file, please use the migrations feature of Active Record to
|
4
3
|
# incrementally modify your database, and then regenerate this schema definition.
|
5
4
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# from scratch.
|
10
|
-
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `rails
|
6
|
+
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
11
10
|
#
|
12
11
|
# It's strongly recommended that you check this file into your version control system.
|
13
12
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 2013_11_02_130413) do
|
15
14
|
|
16
|
-
create_table "posts", force:
|
17
|
-
t.integer
|
18
|
-
t.string
|
19
|
-
t.string
|
20
|
-
t.string
|
21
|
-
t.integer
|
22
|
-
t.integer
|
23
|
-
t.integer
|
24
|
-
t.boolean
|
25
|
-
t.datetime "published_at"
|
15
|
+
create_table "posts", force: :cascade do |t|
|
16
|
+
t.integer "user_id", null: false
|
17
|
+
t.string "title", null: false
|
18
|
+
t.string "body", null: false
|
19
|
+
t.string "category_name", null: false
|
20
|
+
t.integer "views_count", default: 0, null: false
|
21
|
+
t.integer "likes_count", default: 0, null: false
|
22
|
+
t.integer "comments_count", default: 0, null: false
|
23
|
+
t.boolean "published", default: false, null: false
|
26
24
|
t.datetime "created_at"
|
27
25
|
t.datetime "updated_at"
|
26
|
+
t.index ["user_id"], name: "index_posts_on_user_id"
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
create_table "users", force: true do |t|
|
33
|
-
t.string "name", null: false
|
29
|
+
create_table "users", force: :cascade do |t|
|
30
|
+
t.string "name", limit: 255, null: false
|
34
31
|
t.datetime "created_at"
|
35
32
|
t.datetime "updated_at"
|
33
|
+
t.index ["name"], name: "index_users_on_name", unique: true
|
36
34
|
end
|
37
35
|
|
38
|
-
add_index "users", ["name"], name: "index_users_on_name", unique: true
|
39
|
-
|
40
36
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
@@ -11,11 +11,11 @@ describe PostSearch do
|
|
11
11
|
|
12
12
|
def create(attributes = {})
|
13
13
|
Post.create! attributes.reverse_merge(
|
14
|
-
user:
|
15
|
-
title:
|
16
|
-
body:
|
17
|
-
category_name:
|
18
|
-
published:
|
14
|
+
user: user,
|
15
|
+
title: 'Title',
|
16
|
+
body: 'Body',
|
17
|
+
category_name: 'Tech',
|
18
|
+
published: true
|
19
19
|
)
|
20
20
|
end
|
21
21
|
|
@@ -24,29 +24,29 @@ describe PostSearch do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'can search by category name' do
|
27
|
-
post
|
28
|
-
|
27
|
+
post = create category_name: 'Personal'
|
28
|
+
_other = create category_name: 'Other'
|
29
29
|
|
30
30
|
expect_search(category_name: 'Personal').to eq [post]
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'can search by user_id' do
|
34
|
-
post
|
35
|
-
|
34
|
+
post = create user: create_user
|
35
|
+
_other = create user: create_user
|
36
36
|
|
37
37
|
expect_search(user_id: post.user_id).to eq [post]
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'can search by title' do
|
41
|
-
post
|
42
|
-
|
41
|
+
post = create title: 'Title'
|
42
|
+
_other = create title: 'Other'
|
43
43
|
|
44
44
|
expect_search(title: 'itl').to eq [post]
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'can search by published' do
|
48
|
-
post
|
49
|
-
|
48
|
+
post = create published: true
|
49
|
+
_other = create published: false
|
50
50
|
|
51
51
|
expect_search(published: true).to eq [post]
|
52
52
|
end
|
@@ -54,21 +54,21 @@ describe PostSearch do
|
|
54
54
|
it 'can search by term' do
|
55
55
|
post_with_body = create body: 'pattern'
|
56
56
|
post_with_title = create title: 'pattern'
|
57
|
-
|
57
|
+
_other = create
|
58
58
|
|
59
59
|
expect_search(term: 'pattern').to eq [post_with_title, post_with_body]
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'can search by created after' do
|
63
|
-
post
|
64
|
-
|
63
|
+
post = create created_at: 1.month.ago
|
64
|
+
_other = create created_at: 3.month.ago
|
65
65
|
|
66
66
|
expect_search(created_after: 2.month.ago.strftime('%Y-%m-%d')).to eq [post]
|
67
67
|
end
|
68
68
|
|
69
69
|
it 'can search by created before' do
|
70
|
-
post
|
71
|
-
|
70
|
+
post = create created_at: 3.month.ago
|
71
|
+
_other = create created_at: 1.month.ago
|
72
72
|
|
73
73
|
expect_search(created_before: 2.month.ago.strftime('%Y-%m-%d')).to eq [post]
|
74
74
|
end
|
data/example/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
4
|
ENV['RAILS_ENV'] ||= 'test'
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('../config/environment', __dir__)
|
5
7
|
require 'rspec/rails'
|
6
8
|
|
7
9
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
8
10
|
# in spec/support/ and its subdirectories.
|
9
|
-
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
|
11
|
+
Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
|
10
12
|
|
11
13
|
# Checks for pending migrations before tests are run.
|
12
14
|
# If you are not using ActiveRecord, you can remove this line.
|
data/lib/search_object/base.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
module Base
|
3
5
|
def self.included(base)
|
4
6
|
base.extend ClassMethods
|
5
7
|
base.instance_eval do
|
6
8
|
@config = {
|
7
|
-
defaults:
|
8
|
-
|
9
|
-
scope: nil
|
9
|
+
defaults: {},
|
10
|
+
options: {}
|
10
11
|
}
|
11
12
|
end
|
12
13
|
end
|
@@ -14,12 +15,15 @@ module SearchObject
|
|
14
15
|
def initialize(options = {})
|
15
16
|
config = self.class.config
|
16
17
|
scope = options[:scope] || (config[:scope] && instance_eval(&config[:scope]))
|
17
|
-
actions = config[:actions] || {}
|
18
|
-
params = Helper.normalize_params(config[:defaults], options[:filters], actions.keys)
|
19
18
|
|
20
19
|
raise MissingScopeError unless scope
|
21
20
|
|
22
|
-
@search = Search.new(
|
21
|
+
@search = Search.new(
|
22
|
+
scope: scope,
|
23
|
+
options: config[:options],
|
24
|
+
defaults: config[:defaults],
|
25
|
+
params: options[:filters]
|
26
|
+
)
|
23
27
|
end
|
24
28
|
|
25
29
|
def results
|
@@ -34,6 +38,12 @@ module SearchObject
|
|
34
38
|
@count ||= @search.count self
|
35
39
|
end
|
36
40
|
|
41
|
+
def params=(params)
|
42
|
+
@count = nil
|
43
|
+
@results = nil
|
44
|
+
@search.params = params
|
45
|
+
end
|
46
|
+
|
37
47
|
def params(additions = {})
|
38
48
|
if additions.empty?
|
39
49
|
@search.params
|
@@ -42,6 +52,16 @@ module SearchObject
|
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
55
|
+
def param?(*args)
|
56
|
+
if args.size == 1
|
57
|
+
params.key?(args[0].to_s)
|
58
|
+
elsif args.size == 2
|
59
|
+
params[args[0].to_s] == args[1]
|
60
|
+
else
|
61
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1 or 2)"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
45
65
|
private
|
46
66
|
|
47
67
|
def fetch_results
|
@@ -67,7 +87,7 @@ module SearchObject
|
|
67
87
|
handler = options[:with] || block
|
68
88
|
|
69
89
|
config[:defaults][name] = default unless default.nil?
|
70
|
-
config[:
|
90
|
+
config[:options][name] = Helper.normalize_search_handler(handler, name)
|
71
91
|
|
72
92
|
define_method(name) { @search.param name }
|
73
93
|
end
|
data/lib/search_object/errors.rb
CHANGED
data/lib/search_object/helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
# :api: private
|
3
5
|
module Helper
|
@@ -54,10 +56,6 @@ module SearchObject
|
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
|
-
def normalize_params(defaults, filters, keys)
|
58
|
-
(defaults || {}).merge(slice_keys(stringify_keys(filters || {}), keys || []))
|
59
|
-
end
|
60
|
-
|
61
59
|
def deep_copy(object) # rubocop:disable Metrics/MethodLength
|
62
60
|
case object
|
63
61
|
when Array
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
module Plugin
|
3
5
|
module Enum
|
@@ -9,10 +11,7 @@ module SearchObject
|
|
9
11
|
def option(name, options = nil, &block)
|
10
12
|
return super unless options.is_a?(Hash) && options[:enum]
|
11
13
|
|
12
|
-
|
13
|
-
raise WithIgnoredError if options[:with]
|
14
|
-
|
15
|
-
handler = Handler.build(name, options[:enum])
|
14
|
+
handler = options[:with] || block || Handler.build(name, options[:enum])
|
16
15
|
|
17
16
|
super(name, options, &handler)
|
18
17
|
end
|
@@ -30,19 +29,16 @@ module SearchObject
|
|
30
29
|
def apply_filter(object:, option:, enums:, scope:, value:)
|
31
30
|
return if value.nil? || value == ''
|
32
31
|
|
33
|
-
unless enums.include? value
|
34
|
-
return handle_invalid_value(object: object, option: option, enums: enums, scope: scope, value: value)
|
35
|
-
end
|
32
|
+
return handle_invalid_value(object: object, option: option, enums: enums, scope: scope, value: value) unless enums.include? value.to_s
|
36
33
|
|
37
|
-
object.send("apply_#{option}_with_#{Helper.underscore(value)}", scope)
|
34
|
+
object.send("apply_#{Helper.underscore(option)}_with_#{Helper.underscore(value)}", scope)
|
38
35
|
end
|
39
36
|
|
40
37
|
def handle_invalid_value(object:, option:, enums:, scope:, value:)
|
41
38
|
specific = "handle_invalid_#{option}"
|
42
39
|
return object.send(specific, scope, value) if object.respond_to? specific, true
|
43
40
|
|
44
|
-
|
45
|
-
return object.send(catch_all, option, scope, value) if object.respond_to? catch_all, true
|
41
|
+
return object.handle_invalid_enum(option, scope, value) if object.respond_to? :handle_invalid_enum, true
|
46
42
|
|
47
43
|
raise InvalidEnumValueError.new(option, enums, value)
|
48
44
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
module Plugin
|
3
5
|
module Paging
|
@@ -26,17 +28,20 @@ module SearchObject
|
|
26
28
|
|
27
29
|
module ClassMethods
|
28
30
|
def per_page(number)
|
29
|
-
raise InvalidNumberError.new('Per page', number) unless number
|
31
|
+
raise InvalidNumberError.new('Per page', number) unless number.positive?
|
32
|
+
|
30
33
|
config[:per_page] = number
|
31
34
|
end
|
32
35
|
|
33
36
|
def min_per_page(number)
|
34
|
-
raise InvalidNumberError.new('Min per page', number) unless number
|
37
|
+
raise InvalidNumberError.new('Min per page', number) unless number.positive?
|
38
|
+
|
35
39
|
config[:min_per_page] = number
|
36
40
|
end
|
37
41
|
|
38
42
|
def max_per_page(number)
|
39
|
-
raise InvalidNumberError.new('Max per page', number) unless number
|
43
|
+
raise InvalidNumberError.new('Max per page', number) unless number.positive?
|
44
|
+
|
40
45
|
config[:max_per_page] = number
|
41
46
|
end
|
42
47
|
|
data/lib/search_object/search.rb
CHANGED
@@ -1,21 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
# :api: private
|
3
5
|
class Search
|
4
6
|
attr_reader :params
|
5
7
|
|
6
|
-
def initialize(scope,
|
7
|
-
@scope
|
8
|
-
@
|
9
|
-
@
|
8
|
+
def initialize(scope:, options: nil, defaults: nil, params: nil)
|
9
|
+
@scope = scope
|
10
|
+
@options = options || {}
|
11
|
+
@defaults = defaults || {}
|
12
|
+
|
13
|
+
self.params = params
|
14
|
+
end
|
15
|
+
|
16
|
+
def params=(params)
|
17
|
+
@params = @defaults.merge(Helper.slice_keys(Helper.stringify_keys(params || {}), @options.keys))
|
10
18
|
end
|
11
19
|
|
12
20
|
def param(name)
|
13
|
-
@params[name]
|
21
|
+
@params[name.to_s]
|
14
22
|
end
|
15
23
|
|
16
24
|
def query(context)
|
17
25
|
@params.inject(@scope) do |scope, (name, value)|
|
18
|
-
new_scope = context.instance_exec scope, value, &@
|
26
|
+
new_scope = context.instance_exec scope, value, &@options[name]
|
19
27
|
new_scope || scope
|
20
28
|
end
|
21
29
|
end
|
data/lib/search_object.rb
CHANGED
data/search_object.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler"
|
21
|
+
spec.add_development_dependency "bundler"
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency 'rspec', '~> 3.5'
|
24
24
|
spec.add_development_dependency 'rspec-mocks', '~> 3.5'
|
@@ -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.
|
33
|
-
spec.add_development_dependency 'rubocop-rspec', '1.
|
32
|
+
spec.add_development_dependency 'rubocop', '0.81.0'
|
33
|
+
spec.add_development_dependency 'rubocop-rspec', '1.38.1'
|
34
34
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'active_support/core_ext/object/blank'
|
3
5
|
|
@@ -282,6 +284,23 @@ module SearchObject
|
|
282
284
|
end
|
283
285
|
end
|
284
286
|
|
287
|
+
describe '#params=' do
|
288
|
+
it 'resets search' do
|
289
|
+
search = new_search [1, 2, 3], value: 1 do
|
290
|
+
option :value do |scope, value|
|
291
|
+
scope.select { |v| v > value }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
expect(search.results).to eq [2, 3]
|
296
|
+
expect(search.count).to eq 2
|
297
|
+
|
298
|
+
search.params = { value: 2 }
|
299
|
+
expect(search.results).to eq [3]
|
300
|
+
expect(search.count).to eq 1
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
285
304
|
describe '#params' do
|
286
305
|
it 'exports options as params' do
|
287
306
|
search = new_search [], value: 1
|
@@ -310,5 +329,44 @@ module SearchObject
|
|
310
329
|
expect(search.params).to eq 'value' => 1
|
311
330
|
end
|
312
331
|
end
|
332
|
+
|
333
|
+
describe '#param?' do
|
334
|
+
context 'one argument' do
|
335
|
+
it 'returns true when params is set' do
|
336
|
+
search = new_search([], value: 2) do
|
337
|
+
option :value
|
338
|
+
end
|
339
|
+
|
340
|
+
expect(search.param?(:value)).to eq true
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'returns false when params isnt set' do
|
344
|
+
search = new_search do
|
345
|
+
option :value
|
346
|
+
end
|
347
|
+
|
348
|
+
expect(search.param?(:value)).to eq false
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'two arguments' do
|
353
|
+
it 'returns true when param matches value' do
|
354
|
+
search = new_search([], value: 2) do
|
355
|
+
option :value
|
356
|
+
end
|
357
|
+
|
358
|
+
expect(search.param?(:value, 2)).to eq true
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'returns false when param matches value' do
|
362
|
+
search = new_search([], value: 2) do
|
363
|
+
option :value
|
364
|
+
end
|
365
|
+
|
366
|
+
expect(search.param?(:value, 3)).to eq false
|
367
|
+
expect(search.param?(:other, 3)).to eq false
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
313
371
|
end
|
314
372
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'action_controller'
|
3
5
|
|
@@ -41,24 +43,6 @@ module SearchObject
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
describe '.normalize_filters' do
|
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
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'excludes non specified keys' do
|
50
|
-
expect(described_class.normalize_params({ 'a' => 1 }, { b: 2 }, %w[a])).to eq 'a' => 1
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'handles missing defaults' do
|
54
|
-
expect(described_class.normalize_params(nil, { a: 1 }, %w[a])).to eq 'a' => 1
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'handles missing filters' do
|
58
|
-
expect(described_class.normalize_params(nil, nil, ['a'])).to eq({})
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
46
|
describe 'deep_copy' do
|
63
47
|
it 'returns a deep copy on the given object' do
|
64
48
|
original = {
|
@@ -1,63 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
4
6
|
module SearchObject
|
5
7
|
module Plugin
|
6
8
|
describe Enum do
|
7
|
-
|
8
|
-
|
9
|
+
let(:test_search) do
|
10
|
+
Class.new do
|
11
|
+
include SearchObject.module(:enum)
|
9
12
|
|
10
|
-
|
13
|
+
scope { [1, 2, 3, 4, 5] }
|
11
14
|
|
12
|
-
|
15
|
+
option :filter, enum: %w[odd even]
|
16
|
+
option :camelCase, enum: %w[someValue]
|
13
17
|
|
14
|
-
|
18
|
+
private
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
def apply_filter_with_odd(scope)
|
21
|
+
scope.select(&:odd?)
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
24
|
+
def apply_filter_with_even(scope)
|
25
|
+
scope.select(&:even?)
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
|
28
|
+
def apply_camel_case_with_some_value(_scope)
|
29
|
+
[1]
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle_invalid_filter(_scope, value)
|
33
|
+
"invalid filter - #{value}"
|
34
|
+
end
|
26
35
|
end
|
27
36
|
end
|
28
37
|
|
29
38
|
it 'can filter by enum values' do
|
30
|
-
expect(
|
31
|
-
expect(
|
39
|
+
expect(test_search.results(filters: { filter: 'odd' })).to eq [1, 3, 5]
|
40
|
+
expect(test_search.results(filters: { filter: 'even' })).to eq [2, 4]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'converts input to string' do
|
44
|
+
expect(test_search.results(filters: { filter: :odd })).to eq [1, 3, 5]
|
45
|
+
expect(test_search.results(filters: { filter: :even })).to eq [2, 4]
|
32
46
|
end
|
33
47
|
|
34
48
|
it 'ignores blank values' do
|
35
|
-
expect(
|
36
|
-
expect(
|
49
|
+
expect(test_search.results(filters: { filter: nil })).to eq [1, 2, 3, 4, 5]
|
50
|
+
expect(test_search.results(filters: { filter: '' })).to eq [1, 2, 3, 4, 5]
|
37
51
|
end
|
38
52
|
|
39
53
|
it 'handles wrong enum values' do
|
40
|
-
expect(
|
54
|
+
expect(test_search.results(filters: { filter: 'foo' })).to eq 'invalid filter - foo'
|
41
55
|
end
|
42
56
|
|
43
|
-
it '
|
44
|
-
expect
|
45
|
-
|
46
|
-
include SearchObject.module(:enum)
|
57
|
+
it 'underscores method and enum values' do
|
58
|
+
expect(test_search.results(filters: { camelCase: 'someValue' })).to eq [1]
|
59
|
+
end
|
47
60
|
|
48
|
-
|
49
|
-
|
50
|
-
|
61
|
+
it 'can filter by passed block' do
|
62
|
+
block_search = Class.new do
|
63
|
+
include SearchObject.module(:enum)
|
64
|
+
|
65
|
+
scope { [1, 2, 3, 4, 5] }
|
66
|
+
|
67
|
+
option(:filter, enum: %w[odd even]) { |scope, value| scope.select(&:"#{value}?".to_sym) }
|
68
|
+
end
|
69
|
+
expect(block_search.results(filters: { filter: :odd })).to eq [1, 3, 5]
|
70
|
+
expect(block_search.results(filters: { filter: :even })).to eq [2, 4]
|
51
71
|
end
|
52
72
|
|
53
|
-
it '
|
54
|
-
|
55
|
-
|
56
|
-
|
73
|
+
it 'can filter by with option' do
|
74
|
+
with_search = Class.new do
|
75
|
+
include SearchObject.module(:enum)
|
76
|
+
|
77
|
+
scope { [1, 2, 3, 4, 5] }
|
57
78
|
|
58
|
-
|
79
|
+
option(:filter, enum: %w[odd even], with: :filter)
|
80
|
+
|
81
|
+
def filter(scope, value)
|
82
|
+
scope.select(&:"#{value}?".to_sym)
|
59
83
|
end
|
60
|
-
end
|
84
|
+
end
|
85
|
+
expect(with_search.results(filters: { filter: :odd })).to eq [1, 3, 5]
|
86
|
+
expect(with_search.results(filters: { filter: :even })).to eq [2, 4]
|
61
87
|
end
|
62
88
|
end
|
63
89
|
|
@@ -1,70 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
4
6
|
module SearchObject
|
5
7
|
describe Search do
|
6
8
|
describe '.params' do
|
7
|
-
it '
|
8
|
-
search =
|
9
|
-
expect(search.params).to eq '
|
9
|
+
it 'stringify param keys' do
|
10
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' }, options: { 'name' => nil })
|
11
|
+
expect(search.params).to eq 'name' => 'value'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'filters invalid params' do
|
15
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' })
|
16
|
+
expect(search.params).to eq({})
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'supports default values' do
|
20
|
+
search = described_class.new(scope: 'scope', params: {}, defaults: { 'name' => 'value' })
|
21
|
+
expect(search.params).to eq 'name' => 'value'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can be updated' do
|
25
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' }, options: { 'name' => nil })
|
26
|
+
search.params = { name: 'updated', fake: 'value' }
|
27
|
+
|
28
|
+
expect(search.params).to eq 'name' => 'updated'
|
10
29
|
end
|
11
30
|
end
|
12
31
|
|
13
32
|
describe '.param' do
|
14
33
|
it 'returns the param value' do
|
15
|
-
search =
|
34
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' }, options: { 'name' => nil })
|
16
35
|
expect(search.param(:name)).to eq 'value'
|
17
36
|
end
|
18
37
|
end
|
19
38
|
|
20
39
|
describe '.query' do
|
21
40
|
it 'returns filtered result' do
|
22
|
-
|
23
|
-
min
|
41
|
+
options = {
|
42
|
+
'min' => ->(scope, min) { scope.select { |v| v > min } }
|
24
43
|
}
|
25
44
|
|
26
|
-
search =
|
45
|
+
search = described_class.new(scope: [1, 2, 3], params: { min: 2 }, options: options)
|
27
46
|
expect(search.query(Object.new)).to eq [3]
|
28
47
|
end
|
29
48
|
|
30
|
-
it 'applies
|
31
|
-
|
32
|
-
min
|
33
|
-
max
|
49
|
+
it 'applies options to params' do
|
50
|
+
options = {
|
51
|
+
'min' => ->(scope, min) { scope.select { |v| v > min } },
|
52
|
+
'max' => ->(scope, max) { scope.select { |v| v < max } }
|
34
53
|
}
|
35
54
|
|
36
|
-
search =
|
55
|
+
search = described_class.new(scope: [1, 2, 3, 4, 5], params: { min: 2, max: 5 }, options: options)
|
37
56
|
expect(search.query(Object.new)).to eq [3, 4]
|
38
57
|
end
|
39
58
|
|
40
59
|
it 'handles nil returned from action' do
|
41
|
-
|
42
|
-
odd
|
60
|
+
options = {
|
61
|
+
'odd' => ->(scope, odd) { scope.select(&:odd?) if odd }
|
43
62
|
}
|
44
63
|
|
45
|
-
search =
|
64
|
+
search = described_class.new(scope: [1, 2, 3, 4, 5], params: { odd: false }, options: options)
|
46
65
|
expect(search.query(Object.new)).to eq [1, 2, 3, 4, 5]
|
47
66
|
end
|
48
67
|
|
49
68
|
it 'executes action in the passed context' do
|
50
|
-
|
51
|
-
search
|
69
|
+
options = {
|
70
|
+
'search' => ->(scope, _) { scope.select { |v| v == target_value } }
|
52
71
|
}
|
53
72
|
|
54
73
|
context = OpenStruct.new target_value: 2
|
55
74
|
|
56
|
-
search =
|
75
|
+
search = described_class.new(scope: [1, 2, 3, 4, 5], params: { search: true }, options: options)
|
57
76
|
expect(search.query(context)).to eq [2]
|
58
77
|
end
|
59
78
|
end
|
60
79
|
|
61
80
|
describe '.count' do
|
62
81
|
it 'counts the results of the query' do
|
63
|
-
|
64
|
-
value
|
82
|
+
options = {
|
83
|
+
'value' => ->(scope, value) { scope.select { |v| v == value } }
|
65
84
|
}
|
66
85
|
|
67
|
-
search =
|
86
|
+
search = described_class.new(scope: [1, 2, 3], params: { value: 2 }, options: options)
|
68
87
|
expect(search.count(Object.new)).to eq 1
|
69
88
|
end
|
70
89
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: search_object
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Radoslav Stankov
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,28 +170,28 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - '='
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.
|
173
|
+
version: 0.81.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.
|
180
|
+
version: 0.81.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.
|
187
|
+
version: 1.38.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.
|
194
|
+
version: 1.38.1
|
195
195
|
description: Search object DSL
|
196
196
|
email:
|
197
197
|
- rstankov@gmail.com
|
@@ -278,7 +278,7 @@ homepage: https://github.com/RStankov/SearchObject
|
|
278
278
|
licenses:
|
279
279
|
- MIT
|
280
280
|
metadata: {}
|
281
|
-
post_install_message:
|
281
|
+
post_install_message:
|
282
282
|
rdoc_options: []
|
283
283
|
require_paths:
|
284
284
|
- lib
|
@@ -293,9 +293,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
293
293
|
- !ruby/object:Gem::Version
|
294
294
|
version: '0'
|
295
295
|
requirements: []
|
296
|
-
|
297
|
-
|
298
|
-
signing_key:
|
296
|
+
rubygems_version: 3.1.4
|
297
|
+
signing_key:
|
299
298
|
specification_version: 4
|
300
299
|
summary: Provides DSL for creating search objects
|
301
300
|
test_files:
|