search_object 1.2.1 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +18 -3
  3. data/.travis.yml +4 -2
  4. data/CHANGELOG.md +19 -2
  5. data/Gemfile +2 -0
  6. data/README.md +5 -5
  7. data/Rakefile +2 -0
  8. data/example/Gemfile +3 -1
  9. data/example/README.md +3 -3
  10. data/example/Rakefile +3 -1
  11. data/example/app/controllers/application_controller.rb +2 -0
  12. data/example/app/controllers/posts_controller.rb +2 -0
  13. data/example/app/models/post.rb +2 -0
  14. data/example/app/models/post_search.rb +6 -4
  15. data/example/app/models/user.rb +2 -0
  16. data/example/app/views/posts/index.html.slim +1 -1
  17. data/example/bin/bundle +3 -1
  18. data/example/bin/rails +3 -1
  19. data/example/bin/rake +2 -0
  20. data/example/config.ru +2 -0
  21. data/example/db/migrate/20131102130117_create_users.rb +1 -1
  22. data/example/db/migrate/20131102130413_create_posts.rb +1 -2
  23. data/example/db/schema.rb +19 -23
  24. data/example/spec/models/post_search_spec.rb +19 -19
  25. data/example/spec/spec_helper.rb +4 -2
  26. data/lib/search_object/base.rb +27 -7
  27. data/lib/search_object/errors.rb +2 -0
  28. data/lib/search_object/helper.rb +2 -4
  29. data/lib/search_object/plugin/enum.rb +6 -10
  30. data/lib/search_object/plugin/kaminari.rb +2 -0
  31. data/lib/search_object/plugin/model.rb +2 -0
  32. data/lib/search_object/plugin/paging.rb +8 -3
  33. data/lib/search_object/plugin/sorting.rb +2 -0
  34. data/lib/search_object/plugin/will_paginate.rb +2 -0
  35. data/lib/search_object/search.rb +14 -6
  36. data/lib/search_object/version.rb +3 -1
  37. data/lib/search_object.rb +2 -0
  38. data/search_object.gemspec +3 -3
  39. data/spec/search_object/base_spec.rb +58 -0
  40. data/spec/search_object/helper_spec.rb +2 -18
  41. data/spec/search_object/plugin/enum_spec.rb +57 -31
  42. data/spec/search_object/plugin/kaminari_spec.rb +2 -0
  43. data/spec/search_object/plugin/model_spec.rb +2 -0
  44. data/spec/search_object/plugin/paging_spec.rb +2 -0
  45. data/spec/search_object/plugin/sorting_spec.rb +2 -0
  46. data/spec/search_object/plugin/will_paginate_spec.rb +2 -0
  47. data/spec/search_object/search_spec.rb +40 -21
  48. data/spec/spec_helper.rb +2 -0
  49. data/spec/spec_helper_active_record.rb +2 -0
  50. data/spec/support/paging_shared_example.rb +2 -0
  51. metadata +14 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c19b1b27bb979fcee5671d55ef380f628fb8ba624c274166ad40aa8970d8ad2f
4
- data.tar.gz: 4dc53dd30f010996cea99732d647d44d6d2d3eb07b8a39920b590dabdad5caa1
3
+ metadata.gz: c01187c2852ea65c1c340bc37e62527c9d4133691ded6a9db4ac5dea78441b1d
4
+ data.tar.gz: 177bf4f66eccf02f9195a9aa2b8563f8d1de4465be812157f9eeec1ac90121a6
5
5
  SHA512:
6
- metadata.gz: 702e7451fa287d3e98c75dc00fe0977ed4bc901fd72b4bd26fd18089c6abc200f0a03b78be6312b3217a3ecbb193b9a0346cf14834000aecd517968d41b5384b
7
- data.tar.gz: 3b2cdb6312c1ac99afcbb1ea498339c6f732ab38bd45359af4ee84fc7543b5966bc6e5b797fb2525975c478fca4e10d5719487f8da9a87884de13cbcc9509bb5
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
@@ -1,7 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2
4
- - 2.3
3
+ - 2.4
4
+ - 2.6
5
+ - 2.7
6
+ - 3.0
5
7
  script:
6
8
  - bundle exec rubocop
7
9
  - bundle exec rspec spec
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]__ Add `default:` option to `sort_by` plugin (@rstankov)
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 added (@rstankov)
36
+ * __[feature]__ Added `enum` plugin (@rstankov)
20
37
 
21
38
  ```ruby
22
39
  class ProductSearch
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in search_object.gemspec
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 filters: params[:filters]
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 25
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 filters: params[:filters], page: params[:page], per_page: params[:per_page]
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 filters: {sort: 'price desc'}
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 scope: Product.visible, filters: params[:f]
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
  require 'rubocop/rake_task'
data/example/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- gem 'rails', '5.0.1'
5
+ gem 'rails', '6.0.2.1'
4
6
 
5
7
  gem 'bootstrap-sass'
6
8
  gem 'jquery-rails'
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
- rake db:create
23
- rake db:migrate
24
- rake db:seed
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('../config/application', __FILE__)
6
+ require File.expand_path('config/application', __dir__)
5
7
 
6
8
  Example::Application.load_tasks
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationController < ActionController::Base
2
4
  protect_from_forgery with: :exception
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class PostsController < ApplicationController
2
4
  def index
3
5
  @search = PostSearch.new filters: params[:f], page: params[:page], per_page: params[:per_page]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Post < ActiveRecord::Base
2
4
  belongs_to :user
3
5
 
@@ -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)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class User < ActiveRecord::Base
2
4
  has_many :posts
3
5
 
@@ -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
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3
5
  load Gem.bin_path('bundler', 'bundle')
data/example/bin/rails CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- APP_PATH = File.expand_path('../../config/application', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
5
  require_relative '../config/boot'
4
6
  require 'rails/commands'
data/example/bin/rake CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative '../config/boot'
3
5
  require 'rake'
4
6
  Rake.application.run
data/example/config.ru CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is used by Rack-based servers to start the application.
2
4
 
3
5
  require ::File.expand_path('../config/environment', __FILE__)
@@ -1,4 +1,4 @@
1
- class CreateUsers < ActiveRecord::Migration
1
+ class CreateUsers < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table :users do |t|
4
4
  t.string :name, null: false
@@ -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
- # Note that this schema.rb definition is the authoritative source for your
7
- # database schema. If you need to create the application database on another
8
- # system, you should be using db:schema:load, not running all the migrations
9
- # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
- # you'll amass, the slower it'll run and the greater likelihood for issues).
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: 20131102130413) do
13
+ ActiveRecord::Schema.define(version: 2013_11_02_130413) do
15
14
 
16
- create_table "posts", force: true do |t|
17
- t.integer "user_id", null: false
18
- t.string "title", null: false
19
- t.string "body", null: false
20
- t.string "category_name", null: false
21
- t.integer "views_count", default: 0, null: false
22
- t.integer "likes_count", default: 0, null: false
23
- t.integer "comments_count", default: 0, null: false
24
- t.boolean "published", default: false, null: false
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
- add_index "posts", ["user_id"], name: "index_posts_on_user_id"
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
- # rubocop:disable Lint/UselessAssignment:%s
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: user,
15
- title: 'Title',
16
- body: 'Body',
17
- category_name: 'Tech',
18
- published: true
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 = create category_name: 'Personal'
28
- other = create category_name: 'Other'
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 = create user: create_user
35
- other = create user: create_user
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 = create title: 'Title'
42
- other = create title: 'Other'
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 = create published: true
49
- other = create published: false
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
- other = create
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 = create created_at: 1.month.ago
64
- other = create created_at: 3.month.ago
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 = create created_at: 3.month.ago
71
- other = create created_at: 1.month.ago
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
@@ -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('../../config/environment', __FILE__)
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.
@@ -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
- actions: {},
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(scope, params, actions)
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[:actions][name] = Helper.normalize_search_handler(handler, name)
90
+ config[:options][name] = Helper.normalize_search_handler(handler, name)
71
91
 
72
92
  define_method(name) { @search.param name }
73
93
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
4
  class MissingScopeError < ArgumentError
3
5
  def initialize(message = 'No scope provided. Scope can be defined on a class level or passed as an option.')
@@ -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
- raise BlockIgnoredError if block
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
- catch_all = 'handle_invalid_enum'
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 Kaminari
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
4
  module Plugin
3
5
  module Model
@@ -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 > 0
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 > 0
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 > 0
43
+ raise InvalidNumberError.new('Max per page', number) unless number.positive?
44
+
40
45
  config[:max_per_page] = number
41
46
  end
42
47
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
4
  module Plugin
3
5
  module Sorting
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
4
  module Plugin
3
5
  module WillPaginate
@@ -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, params, actions)
7
- @scope = scope
8
- @actions = actions
9
- @params = params
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, &@actions[name]
26
+ new_scope = context.instance_exec scope, value, &@options[name]
19
27
  new_scope || scope
20
28
  end
21
29
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SearchObject
2
- VERSION = '1.2.1'.freeze
4
+ VERSION = '1.2.5'
3
5
  end
data/lib/search_object.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'search_object/version'
2
4
  require 'search_object/errors'
3
5
  require 'search_object/helper'
@@ -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", "~> 1.13"
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.51.0'
33
- spec.add_development_dependency 'rubocop-rspec', '1.20.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
- class TestSearch
8
- include SearchObject.module(:enum)
9
+ let(:test_search) do
10
+ Class.new do
11
+ include SearchObject.module(:enum)
9
12
 
10
- scope { [1, 2, 3, 4, 5] }
13
+ scope { [1, 2, 3, 4, 5] }
11
14
 
12
- option :filter, enum: %w[odd even]
15
+ option :filter, enum: %w[odd even]
16
+ option :camelCase, enum: %w[someValue]
13
17
 
14
- private
18
+ private
15
19
 
16
- def apply_filter_with_odd(scope)
17
- scope.select(&:odd?)
18
- end
20
+ def apply_filter_with_odd(scope)
21
+ scope.select(&:odd?)
22
+ end
19
23
 
20
- def apply_filter_with_even(scope)
21
- scope.select(&:even?)
22
- end
24
+ def apply_filter_with_even(scope)
25
+ scope.select(&:even?)
26
+ end
23
27
 
24
- def handle_invalid_filter(_scope, value)
25
- "invalid filter - #{value}"
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(TestSearch.results(filters: { filter: 'odd' })).to eq [1, 3, 5]
31
- expect(TestSearch.results(filters: { filter: 'even' })).to eq [2, 4]
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(TestSearch.results(filters: { filter: nil })).to eq [1, 2, 3, 4, 5]
36
- expect(TestSearch.results(filters: { filter: '' })).to eq [1, 2, 3, 4, 5]
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(TestSearch.results(filters: { filter: 'foo' })).to eq 'invalid filter - foo'
54
+ expect(test_search.results(filters: { filter: 'foo' })).to eq 'invalid filter - foo'
41
55
  end
42
56
 
43
- it 'raises when block is passed with enum option' do
44
- expect do
45
- Class.new do
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
- option(:filter, enum: %w[a b]) { |_scope, _value| nil }
49
- end
50
- end.to raise_error Enum::BlockIgnoredError
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 'raises when :with is passed with enum option' do
54
- expect do
55
- Class.new do
56
- include SearchObject.module(:enum)
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
- option :filter, enum: %w[a b], with: :method_name
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.to raise_error Enum::WithIgnoredError
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper_active_record'
2
4
  require 'kaminari'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  require 'active_support/core_ext/object/blank'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper_active_record'
2
4
 
3
5
  module SearchObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper_active_record'
2
4
 
3
5
  module SearchObject
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper_active_record'
2
4
 
3
5
  require 'will_paginate'
@@ -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 'returns the passed params' do
8
- search = Search.new('scope', 'params', {})
9
- expect(search.params).to eq 'params'
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 = Search.new('scope', { name: 'value' }, {})
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
- actions = {
23
- min: ->(scope, min) { scope.select { |v| v > min } }
41
+ options = {
42
+ 'min' => ->(scope, min) { scope.select { |v| v > min } }
24
43
  }
25
44
 
26
- search = Search.new [1, 2, 3], { min: 2 }, actions
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 actions to params' do
31
- actions = {
32
- min: ->(scope, min) { scope.select { |v| v > min } },
33
- max: ->(scope, max) { scope.select { |v| v < 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 = Search.new [1, 2, 3, 4, 5], { min: 2, max: 5 }, actions
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
- actions = {
42
- odd: ->(scope, odd) { scope.select(&:odd?) if odd }
60
+ options = {
61
+ 'odd' => ->(scope, odd) { scope.select(&:odd?) if odd }
43
62
  }
44
63
 
45
- search = Search.new [1, 2, 3, 4, 5], { odd: false }, actions
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
- actions = {
51
- search: ->(scope, _) { scope.select { |v| v == target_value } }
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 = Search.new [1, 2, 3, 4, 5], { search: true }, actions
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
- actions = {
64
- value: ->(scope, value) { scope.select { |v| v == value } }
82
+ options = {
83
+ 'value' => ->(scope, value) { scope.select { |v| v == value } }
65
84
  }
66
85
 
67
- search = Search.new [1, 2, 3], { value: 2 }, actions
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/setup'
2
4
 
3
5
  if ENV['TRAVIS']
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'spec_helper'
2
4
  require_relative 'support/paging_shared_example'
3
5
  require 'active_record'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  shared_examples_for 'a paging plugin' do
2
4
  after do
3
5
  Product.delete_all
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.1
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: 2018-05-20 00:00:00.000000000 Z
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: '1.13'
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: '1.13'
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.51.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.51.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.20.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.20.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
- rubyforge_project:
297
- rubygems_version: 2.7.6
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: