query_string_search 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +18 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +97 -0
  9. data/Rakefile +12 -0
  10. data/lib/query_string_search/abstract_matcher.rb +43 -0
  11. data/lib/query_string_search/matcher_factory.rb +11 -0
  12. data/lib/query_string_search/matchers/match_all.rb +9 -0
  13. data/lib/query_string_search/matchers/match_attribute.rb +13 -0
  14. data/lib/query_string_search/matchers/match_attribute_value.rb +11 -0
  15. data/lib/query_string_search/matchers/match_no_attribute.rb +13 -0
  16. data/lib/query_string_search/search_options.rb +32 -0
  17. data/lib/query_string_search/search_parameters.rb +22 -0
  18. data/lib/query_string_search/version.rb +3 -0
  19. data/lib/query_string_search.rb +30 -0
  20. data/query_string_search.gemspec +24 -0
  21. data/spec/features/find_all_data_spec.rb +11 -0
  22. data/spec/features/find_records_that_match_multiple_parameters_spec.rb +31 -0
  23. data/spec/features/find_records_with_matching_attribute_values_spec.rb +49 -0
  24. data/spec/features/find_records_with_nil_attributes_spec.rb +29 -0
  25. data/spec/features/find_records_with_nonnil_attributes_spec.rb +29 -0
  26. data/spec/features/find_seen_and_unseen_movies_spec.rb +26 -0
  27. data/spec/fixtures/movie.rb +39 -0
  28. data/spec/lib/query_string_search/abstract_matcher_spec.rb +27 -0
  29. data/spec/lib/query_string_search/matcher_factory_spec.rb +31 -0
  30. data/spec/lib/query_string_search/matchers/match_all_spec.rb +26 -0
  31. data/spec/lib/query_string_search/matchers/match_attribute_spec.rb +68 -0
  32. data/spec/lib/query_string_search/matchers/match_attribute_value_spec.rb +61 -0
  33. data/spec/lib/query_string_search/matchers/match_no_attribute_spec.rb +68 -0
  34. data/spec/lib/query_string_search/search_options_spec.rb +53 -0
  35. data/spec/lib/query_string_search/search_parameters_spec.rb +27 -0
  36. data/spec/spec_helper.rb +85 -0
  37. metadata +151 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5750a3574b4f201d0e9274a96fa4e1a10aff5593
4
+ data.tar.gz: 6bddcc98d4a95cd33a88ecd0d7d5adb49cc787ea
5
+ SHA512:
6
+ metadata.gz: 97feeae7f780d9513c10b1861cee25a72297d4e6af9fddd2e341e922349d627fa11bd25c4b785a5323cbe805846d5dbdec514474c6a30e912a9312d8479c51fd
7
+ data.tar.gz: 1f275d60e2f5b2075214b60e58546edbe261fc40db111734bb56584daac3088da502281314695fd3415a0c2ffae5c597ba1f8bbaaac30c4b4a9a19a2e4f5363e
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ vendor/bundle
16
+ *gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,18 @@
1
+ AllCops:
2
+ Exclude:
3
+ - '**/*.gemspec'
4
+ - 'bin/**/*'
5
+ - 'spec/spec_helper.rb'
6
+ - 'vendor/bundle/**/*'
7
+
8
+ Documentation:
9
+ Enabled: false
10
+
11
+ Style/IfUnlessModifier:
12
+ MaxLineLength: 0
13
+
14
+ Style/StringLiterals:
15
+ EnforcedStyle: double_quotes
16
+
17
+ Metrics/LineLength:
18
+ Max: 800
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in query_string_search.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Ian Whitney
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # Query String Search
2
+
3
+ Provides an easy way to implement searching in your API endpoints
4
+
5
+ ## Searches it supports
6
+
7
+ Say you have a `movies` endpoint and people want to be able to search your huge collection of movie data. The API Search gem will give you the following search functionality:
8
+
9
+ ### Return all data
10
+
11
+ `movies` will return every movie in the data set.
12
+
13
+ ### Return all data with non-null attribute value
14
+
15
+ `movies?q=rating=all`
16
+
17
+ Returns every movie with a non-nil rating.
18
+
19
+ ### Return all data with null attribute value
20
+
21
+ `movies?q=rating=none`
22
+
23
+ Returns every movie without ratings.
24
+
25
+ ### Return all data with an attribute value that matches
26
+
27
+ `movies?q=year=1994`
28
+
29
+ Returns every movie with a year of 1994
30
+
31
+ ### Combining Searches
32
+
33
+ Search criteria can be separated with commas
34
+
35
+ `movie?q=year=1994,country=US,rated=none`
36
+
37
+ Records that match **all** the criteria will be returned. All un-rated movies made in the US in 1994.
38
+
39
+ ## Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ ```ruby
44
+ gem 'query_string_search', git: https://github.umn.edu/asrweb/query_string_search.git
45
+ ```
46
+
47
+ And then execute:
48
+
49
+ $ bundle
50
+
51
+ ## Usage
52
+
53
+ First, create a collection of data. With ActiveRecord or other ORMs this is straightforward:
54
+
55
+ ```ruby
56
+ Movie.all
57
+ ```
58
+
59
+ Or something similar. As long as it returns a collection of objects, you should be good.
60
+
61
+ The objects must respond to the attributes you want to search on. Say you want to allow a search string like this:
62
+
63
+ ```
64
+ `movies?q=year=1994`
65
+ ```
66
+
67
+ Then every object in your data collection needs to respond to `year`.
68
+
69
+ Again, with ActiveRecord this is pretty straightforward. But if you're building your data source from raw SQL then you're going to have to convert that data into objects that respond to the attributes you want to search on.
70
+
71
+ Second, search! In Rails you can do something like this in a Controller method.
72
+
73
+ ```ruby
74
+ QueryStringSearch.new(data, query_string).results
75
+ ```
76
+
77
+ This returns a collection of the objects that matched the search criteria.
78
+
79
+ Or you can do it not in the controller. This will work:
80
+
81
+ ```ruby
82
+ test_query = "country=us"
83
+ QueryStringSearch.new(Movie.all, test_query).results
84
+ ```
85
+
86
+ You get the idea. Pass in a data set and a query-stringish string and you'll get results back.
87
+
88
+
89
+ ## Contributing
90
+
91
+ - Fork, branch, commit & pull.
92
+ - Tests are required.
93
+ - Don't go against our Rubocop style guidelines.
94
+
95
+ ## License
96
+
97
+ © Regents of the University of Minnesota. All rights reserved.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "rspec/core/rake_task"
2
+ require "bundler/gem_tasks"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ desc "Run rubocop"
7
+ task :rubocop do
8
+ require "rubocop/rake_task"
9
+ RuboCop::RakeTask.new
10
+ end
11
+
12
+ task default: [:spec, :rubocop]
@@ -0,0 +1,43 @@
1
+ module QueryStringSearch
2
+ class AbstractMatcher
3
+ attr_accessor :attribute, :value
4
+
5
+ def self.matchers
6
+ descendants.push(self)
7
+ end
8
+
9
+ def self.descendants
10
+ ObjectSpace.each_object(Class).select { |klass| klass < AbstractMatcher }
11
+ end
12
+
13
+ def self.all_reserved_words
14
+ descendants.each_with_object([]) { |d, ret| ret << d.reserved_words }.flatten
15
+ end
16
+
17
+ def initialize(attribute = nil, value = nil)
18
+ self.attribute = attribute
19
+ self.value = value
20
+ end
21
+
22
+ def match?(_)
23
+ false
24
+ end
25
+
26
+ def self.reserved_words
27
+ []
28
+ end
29
+
30
+ def self.build_me?(_, _)
31
+ true
32
+ end
33
+
34
+ private
35
+
36
+ def match_with_contingency
37
+ yield
38
+ rescue
39
+ false
40
+ end
41
+ end
42
+ end
43
+ Dir.glob(File.join(File.dirname(__FILE__), "matchers", "*.rb")) { |file| require file }
@@ -0,0 +1,11 @@
1
+ module QueryStringSearch
2
+ class MatcherFactory
3
+ def self.build(query_option, build_options)
4
+ constructor = build_options.detect { |m| m.build_me?(query_option.search_type, query_option.search_param) }
5
+
6
+ if constructor
7
+ constructor.new(query_option.search_type, query_option.search_param)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class MatchAll < QueryStringSearch::AbstractMatcher
2
+ def match?(_)
3
+ true
4
+ end
5
+
6
+ def self.build_me?(search_type, search_param)
7
+ search_type.nil? && search_param.nil?
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class MatchAttribute < QueryStringSearch::AbstractMatcher
2
+ def match?(target)
3
+ match_with_contingency { target.public_send(attribute) }
4
+ end
5
+
6
+ def self.reserved_words
7
+ %w(true all)
8
+ end
9
+
10
+ def self.build_me?(_, search_param)
11
+ reserved_words.include?(search_param)
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ class MatchAttributeValue < QueryStringSearch::AbstractMatcher
2
+ def match?(target)
3
+ match_with_contingency do
4
+ target.public_send(attribute).to_s.upcase == value.to_s.upcase
5
+ end
6
+ end
7
+
8
+ def self.build_me?(search_type, search_param)
9
+ search_param && search_type && !all_reserved_words.include?(search_param)
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class MatchNoAttribute < QueryStringSearch::AbstractMatcher
2
+ def match?(target)
3
+ match_with_contingency { !target.public_send(attribute) }
4
+ end
5
+
6
+ def self.reserved_words
7
+ %w(false none)
8
+ end
9
+
10
+ def self.build_me?(_, search_param)
11
+ reserved_words.include?(search_param)
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module QueryStringSearch
2
+ class SearchOptions
3
+ attr_reader :search_type, :search_param
4
+
5
+ def self.parse(query_string)
6
+ if query_string
7
+ search_params = query_string.split(",")
8
+ search_params.each_with_object([]) do |p, ret|
9
+ ret << new(p)
10
+ end
11
+ else
12
+ [new(nil)]
13
+ end
14
+ end
15
+
16
+ def initialize(raw_query)
17
+ self.search_type, self.search_param = raw_query.to_s.split("=")
18
+ end
19
+
20
+ def search_type
21
+ @search_type ? @search_type.to_sym : nil
22
+ end
23
+
24
+ def search_param
25
+ @search_param ? CGI.unescape(@search_param) : @search_param
26
+ end
27
+
28
+ private
29
+
30
+ attr_writer :search_type, :search_param
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ module QueryStringSearch
2
+ class SearchParameters
3
+ extend Forwardable
4
+
5
+ def_delegators :@collection, :each
6
+
7
+ def self.build_from_querystring(query_string, factory = QueryStringSearch::MatcherFactory, matchers = QueryStringSearch::AbstractMatcher.matchers)
8
+ parameters = QueryStringSearch::SearchOptions.parse(query_string)
9
+ new(parameters, factory, matchers)
10
+ end
11
+
12
+ def initialize(parameters, factory, matchers)
13
+ parameters.each do |param|
14
+ collection << factory.build(param, matchers)
15
+ end
16
+ end
17
+
18
+ def collection
19
+ @collection ||= []
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module QueryStringSearch
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,30 @@
1
+ module QueryStringSearch
2
+ def self.new(data_source, query_string)
3
+ QueryStringSearch::Search.new(data_source, query_string)
4
+ end
5
+
6
+ class Search
7
+ attr_accessor :data_source, :query_string
8
+
9
+ def initialize(data_source, query_string)
10
+ self.data_source = data_source
11
+ self.query_string = query_string
12
+ end
13
+
14
+ def results
15
+ @results = data_source
16
+ QueryStringSearch::SearchParameters.build_from_querystring(query_string).each do |param|
17
+ @results = filter_by_param(@results, param)
18
+ end
19
+ @results
20
+ end
21
+
22
+ private
23
+
24
+ def filter_by_param(data, param)
25
+ data.select { |c| param.match?(c) }
26
+ end
27
+ end
28
+ end
29
+
30
+ Dir.glob(File.join(File.dirname(__FILE__), "query_string_search", "*.rb")) { |file| require file }
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'query_string_search/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "query_string_search"
8
+ spec.version = QueryStringSearch::VERSION
9
+ spec.authors = ["Ian Whitney"]
10
+ spec.email = ["whit0694@umn.edu"]
11
+ spec.summary = %q{Provides a standard way to do searches using query strings in an API endpoint}
12
+ spec.homepage = "https://github.com/umn-asr/query_string_search"
13
+ spec.license = ""
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3"
23
+ spec.add_development_dependency "rubocop"
24
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "../../lib/query_string_search"
2
+ require_relative "../fixtures/movie"
3
+
4
+ RSpec.describe "Searching for all data" do
5
+ let(:data_set) { Movie.random_collection }
6
+
7
+ it "Uses no query string " do
8
+ results = QueryStringSearch.new(data_set, nil).results
9
+ expect(results).to eq(data_set)
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "../../lib/query_string_search"
2
+ require_relative "../fixtures/movie"
3
+
4
+ RSpec.describe "Finding data that match multiple parameters" do
5
+ let(:data_set) { Movie.random_collection }
6
+ let(:random_movie) { data_set.sample }
7
+
8
+ let(:movies_with_country) { data_set.select { |d| d.country == random_movie.country } }
9
+ let(:movies_with_year) { data_set.select { |d| d.year == random_movie.year } }
10
+ let(:movies_with_rating) { data_set.select { |d| d.rating == random_movie.rating } }
11
+
12
+ let(:expected) { movies_with_country & movies_with_year & movies_with_rating }
13
+
14
+ it "Returns records that match the requested value" do
15
+ query_string = "country=#{random_movie.country},year=#{random_movie.year},rating=#{random_movie.rating || 'none'}"
16
+ results = QueryStringSearch.new(data_set, query_string).results
17
+
18
+ expect(expected).to eq(results)
19
+ end
20
+
21
+ describe "when the objects do not respond one of the attributes" do
22
+ it "returns an empty collection" do
23
+ query_string = "country=#{random_movie.country},year=#{random_movie.year},rating=#{random_movie.rating},monkey=golden"
24
+ results = QueryStringSearch.new(data_set, query_string).results
25
+
26
+ expect { data_set.sample.monkey }.to raise_error(NoMethodError)
27
+
28
+ expect(results).to eq([])
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,49 @@
1
+ require_relative "../../lib/query_string_search"
2
+ require_relative "../fixtures/movie"
3
+ require "erb"
4
+ include ERB::Util
5
+
6
+ RSpec.describe "Finding data with matching attribute values" do
7
+ let(:data_set) { Movie.random_collection }
8
+ let(:movies_with_us_country) { data_set.select { |d| d.country == "US" } }
9
+
10
+ it "Returns records that match the requested value" do
11
+ results = QueryStringSearch.new(data_set, "country=US").results
12
+
13
+ expect(results).to eq(movies_with_us_country)
14
+ end
15
+
16
+ it "is case-insensitive" do
17
+ results = QueryStringSearch.new(data_set, "country=us").results
18
+
19
+ expect(results).to eq(movies_with_us_country)
20
+ end
21
+
22
+ describe "when the values have spaces" do
23
+ let(:random_movie) { data_set.sample }
24
+ # there's a chance that more than one movie has the same title
25
+ let(:movies_with_title) { data_set.select { |d| d.title == random_movie.title } }
26
+
27
+ it "matches if the query-string is not escaped" do
28
+ results = QueryStringSearch.new(data_set, "title=#{random_movie.title}").results
29
+
30
+ expect(results).to eq(movies_with_title)
31
+ end
32
+
33
+ it "matches if the query-string is escaped" do
34
+ results = QueryStringSearch.new(data_set, "title=#{url_encode(random_movie.title)}").results
35
+
36
+ expect(results).to eq(movies_with_title)
37
+ end
38
+ end
39
+
40
+ describe "when the objects do not respond to the attribute" do
41
+ it "returns an empty result set" do
42
+ results = QueryStringSearch.new(data_set, "monkey=golden").results
43
+
44
+ expect { data_set.sample.monkey }.to raise_error(NoMethodError)
45
+
46
+ expect(results).to eq([])
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "../../lib/query_string_search"
2
+ require_relative "../fixtures/movie"
3
+
4
+ RSpec.describe "Finding data with nil attributes" do
5
+ let(:data_set) { Movie.random_collection }
6
+ let(:movies_with_nil_ratings) { data_set.select { |d| d.rating.nil? } }
7
+
8
+ it "Returns nil-attribute records with a query-string value of false" do
9
+ results = QueryStringSearch.new(data_set, "rating=false").results
10
+
11
+ expect(results).to eq(movies_with_nil_ratings)
12
+ end
13
+
14
+ it "Returns nil-attribute records with a query-string value of none" do
15
+ results = QueryStringSearch.new(data_set, "rating=none").results
16
+
17
+ expect(results).to eq(movies_with_nil_ratings)
18
+ end
19
+
20
+ describe "when the objects do not respond to the attribute" do
21
+ it "returns an empty collection" do
22
+ results = QueryStringSearch.new(data_set, "monkey=false").results
23
+
24
+ expect { data_set.sample.monkey }.to raise_error(NoMethodError)
25
+
26
+ expect(results).to be_empty
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "../../lib/query_string_search"
2
+ require_relative "../fixtures/movie"
3
+
4
+ RSpec.describe "Finding data with non-nil attributes" do
5
+ let(:data_set) { Movie.random_collection }
6
+ let(:movies_with_non_nil_ratings) { data_set.select { |d| !d.rating.nil? } }
7
+
8
+ it "Returns non-nil-attribute records with a query-string value of true" do
9
+ results = QueryStringSearch.new(data_set, "rating=true").results
10
+
11
+ expect(results).to eq(movies_with_non_nil_ratings)
12
+ end
13
+
14
+ it "Returns non-nil-attribute records with a query-string value of all" do
15
+ results = QueryStringSearch.new(data_set, "rating=all").results
16
+
17
+ expect(results).to eq(movies_with_non_nil_ratings)
18
+ end
19
+
20
+ describe "when the objects do not respond to the attribute" do
21
+ it "returns an empty collection" do
22
+ results = QueryStringSearch.new(data_set, "monkey=true").results
23
+
24
+ expect { data_set.sample.monkey }.to raise_error(NoMethodError)
25
+
26
+ expect(results).to be_empty
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ require_relative "../../lib/query_string_search"
2
+ require_relative "../fixtures/movie"
3
+
4
+ RSpec.describe "Finding movies" do
5
+ let(:data_set) { Movie.random_collection }
6
+
7
+ describe "that have been seen" do
8
+ let(:seen_movies) { data_set.select(&:seen?) }
9
+
10
+ it "uses a query-string of 'seen?=true'" do
11
+ results = QueryStringSearch.new(data_set, "seen?=true").results
12
+
13
+ expect(results).to eq(seen_movies)
14
+ end
15
+ end
16
+
17
+ describe "that have not been seen" do
18
+ let(:unseen_movies) { data_set.select { |d| !d.seen? } }
19
+
20
+ it "uses a query-string of 'seen?=false'" do
21
+ results = QueryStringSearch.new(data_set, "seen?=false").results
22
+
23
+ expect(results).to eq(unseen_movies)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ class Movie
2
+ attr_accessor :title, :rating, :year, :country, :seen
3
+
4
+ def self.random_collection(count = 1_000)
5
+ (1...count).inject([]) do |collection, _|
6
+ collection << Movie.new(random_title, random_rating, random_year, random_country)
7
+ end
8
+ end
9
+
10
+ def initialize(title, rating, year, country)
11
+ self.title = title
12
+ self.rating = rating
13
+ self.year = year
14
+ self.country = country
15
+ self.seen = [true, false].sample
16
+ end
17
+
18
+ def seen?
19
+ seen
20
+ end
21
+
22
+ def self.random_title
23
+ "Random Movie #{rand(10_000)}"
24
+ end
25
+
26
+ def self.random_rating
27
+ ["X", "NC-17", "R", "PG-13", "PG", "G", nil].sample
28
+ end
29
+
30
+ def self.random_year
31
+ (1990..2014).to_a.sample.to_s
32
+ end
33
+
34
+ def self.random_country
35
+ %w(
36
+ US CAN UK HK BR RUS FR IN
37
+ ).sample
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ require "ostruct"
2
+ require_relative "../../../lib/query_string_search/abstract_matcher"
3
+
4
+ RSpec.describe QueryStringSearch::AbstractMatcher do
5
+ describe "match?" do
6
+ it "is false" do
7
+ value = rand
8
+ target = OpenStruct.new(other: value)
9
+ it = QueryStringSearch::AbstractMatcher.new(:other, value)
10
+ expect(it.match?(target)).to be_falsey
11
+ end
12
+ end
13
+
14
+ describe "build_me?" do
15
+ it "is true" do
16
+ it = QueryStringSearch::AbstractMatcher.build_me?(rand, rand)
17
+ expect(it).to be_truthy
18
+ end
19
+ end
20
+
21
+ describe "matchers" do
22
+ it "is has self as last element " do
23
+ it = QueryStringSearch::AbstractMatcher.matchers
24
+ expect(it.last).to eq(QueryStringSearch::AbstractMatcher)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ require_relative "../../../lib/query_string_search/matcher_factory"
2
+ require_relative "../../../lib/query_string_search/search_options"
3
+
4
+ RSpec.describe QueryStringSearch::MatcherFactory do
5
+ let(:matcher_double) { class_double("MatchAttribute") }
6
+ let(:param_double) { double("QueryStringSearch::SearchOptions") }
7
+ let(:build_candidates) { [matcher_double] }
8
+
9
+ before do
10
+ allow(param_double).to receive(:search_param).and_return("test_search_param")
11
+ allow(param_double).to receive(:search_type).and_return("test_search_type")
12
+ end
13
+
14
+ describe "build" do
15
+ describe "finds a matcher to build" do
16
+ it "builds that matcher and returns it" do
17
+ test_return = Object.new
18
+ expect(matcher_double).to receive(:build_me?).with(param_double.search_type, param_double.search_param).and_return(true)
19
+ expect(matcher_double).to receive(:new).with(param_double.search_type, param_double.search_param).and_return(test_return)
20
+ expect(QueryStringSearch::MatcherFactory.build(param_double, build_candidates)).to eq(test_return)
21
+ end
22
+ end
23
+
24
+ describe "does not find a matcher to build" do
25
+ it "returns nil" do
26
+ expect(matcher_double).to receive(:build_me?).and_return(false)
27
+ expect(QueryStringSearch::MatcherFactory.build(param_double, build_candidates)).to be_nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ require_relative "../../../../lib/query_string_search/abstract_matcher"
2
+
3
+ RSpec.describe MatchAll do
4
+ describe "match?" do
5
+ it "is true" do
6
+ it = MatchAll.new(:other, rand)
7
+ expect(it.match?(rand)).to be_truthy
8
+ end
9
+ end
10
+
11
+ describe "build_me?" do
12
+ describe "given a nil search_type and search_param" do
13
+ it "is true" do
14
+ expect(MatchAll.build_me?(nil, nil)).to be_truthy
15
+ end
16
+ end
17
+
18
+ describe "given a non-nil search_type or search_param" do
19
+ it "is false" do
20
+ expect(MatchAll.build_me?(rand, nil)).to be_falsey
21
+ expect(MatchAll.build_me?(nil, rand)).to be_falsey
22
+ expect(MatchAll.build_me?(rand, rand)).to be_falsey
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,68 @@
1
+ require_relative "../../../../lib/query_string_search/abstract_matcher"
2
+
3
+ RSpec.describe MatchAttribute do
4
+ describe "match?" do
5
+ describe "if the target's attribute is not nil" do
6
+ let(:target) { Target.new("search_value") }
7
+ let(:subject) { MatchAttribute.new(:search_attr) }
8
+
9
+ it "is true" do
10
+ expect(subject.match?(target)).to be_truthy
11
+ end
12
+ end
13
+
14
+ describe "if the target's attribute is true" do
15
+ let(:target) { Target.new(true) }
16
+ let(:subject) { MatchAttribute.new(:search_attr) }
17
+
18
+ it "is true" do
19
+ expect(subject.match?(target)).to be_truthy
20
+ end
21
+ end
22
+
23
+ describe "if the target's attribute is nil" do
24
+ let(:target) { Target.new(nil) }
25
+ let(:subject) { MatchAttribute.new(:search_attr) }
26
+
27
+ it "is false" do
28
+ expect(subject.match?(target)).to be_falsey
29
+ end
30
+ end
31
+
32
+ describe "if the target's attribute is false" do
33
+ let(:target) { Target.new(false) }
34
+ let(:subject) { MatchAttribute.new(:search_attr) }
35
+
36
+ it "is true" do
37
+ expect(subject.match?(target)).to be_falsey
38
+ end
39
+ end
40
+
41
+ describe "if the target doesn't have the attribute" do
42
+ let(:target) { Target.new(rand) }
43
+ let(:subject) { MatchAttribute.new(:bad_attr) }
44
+
45
+ it "is false" do
46
+ expect(subject.match?(target)).to be_falsey
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "build_me?" do
52
+ describe "given a search param of 'all' or 'true'" do
53
+ it "is true" do
54
+ %w(all true).each do |search_param|
55
+ expect(MatchAttribute.build_me?(rand, search_param)).to be_truthy
56
+ end
57
+ end
58
+ end
59
+
60
+ describe "given a search param that is not 'all' or 'true'" do
61
+ it "is false" do
62
+ %w(1all rue).each do |search_param|
63
+ expect(MatchAttribute.build_me?(rand, search_param)).to be_falsey
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,61 @@
1
+ require_relative "../../../../lib/query_string_search/abstract_matcher"
2
+
3
+ RSpec.describe MatchAttributeValue do
4
+ describe "match?" do
5
+ describe "given a target with an attribute that matches the Parameter's attribute" do
6
+ let(:target) { Target.new("search_value") }
7
+ let(:value) { "search_value" }
8
+ let(:subject) { MatchAttributeValue.new(:search_attr, value) }
9
+
10
+ it "returns true" do
11
+ expect(subject.match?(target)).to be_truthy
12
+ end
13
+ end
14
+
15
+ describe "given a value with spaces" do
16
+ let(:target) { Target.new("search value") }
17
+ let(:value) { "search value" }
18
+ let(:subject) { MatchAttributeValue.new(:search_attr, value) }
19
+
20
+ it "returns true" do
21
+ expect(subject.match?(target)).to be_truthy
22
+ end
23
+ end
24
+
25
+ describe "given a target with an attribute that does not match the Parameter's attribute" do
26
+ let(:target) { Target.new("other_value") }
27
+ let(:value) { "search_value" }
28
+ let(:subject) { MatchAttributeValue.new(:search_attr, value) }
29
+
30
+ it "returns false" do
31
+ expect(subject.match?(target)).to be_falsey
32
+ end
33
+ end
34
+
35
+ describe "if the target doesn't have the attribute" do
36
+ let(:value) { "search_value" }
37
+ let(:target) { Target.new(value) }
38
+ let(:subject) { MatchAttribute.new(:bad_attr, value) }
39
+
40
+ it "is false" do
41
+ expect(subject.match?(target)).to be_falsey
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "build_me?" do
47
+ describe "given a non-nil search_type and search_param" do
48
+ it "is true" do
49
+ expect(MatchAttributeValue.build_me?(rand, rand)).to be_truthy
50
+ end
51
+ end
52
+
53
+ describe "given a nil search_type or search_param" do
54
+ it "is false" do
55
+ expect(MatchAttributeValue.build_me?(rand, nil)).to be_falsey
56
+ expect(MatchAttributeValue.build_me?(nil, rand)).to be_falsey
57
+ expect(MatchAttributeValue.build_me?(nil, nil)).to be_falsey
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,68 @@
1
+ require_relative "../../../../lib/query_string_search/abstract_matcher"
2
+
3
+ RSpec.describe MatchNoAttribute do
4
+ describe "match?" do
5
+ describe "if the target's attribute is not nil" do
6
+ let(:target) { Target.new("search_value") }
7
+ let(:subject) { MatchNoAttribute.new(:search_attr) }
8
+
9
+ it "is false" do
10
+ expect(subject.match?(target)).to be_falsey
11
+ end
12
+ end
13
+
14
+ describe "if the target's attribute is true" do
15
+ let(:target) { Target.new(true) }
16
+ let(:subject) { MatchNoAttribute.new(:search_attr) }
17
+
18
+ it "is true" do
19
+ expect(subject.match?(target)).to be_falsey
20
+ end
21
+ end
22
+
23
+ describe "if the target's attribute is false" do
24
+ let(:target) { Target.new(false) }
25
+ let(:subject) { MatchNoAttribute.new(:search_attr) }
26
+
27
+ it "is true" do
28
+ expect(subject.match?(target)).to be_truthy
29
+ end
30
+ end
31
+
32
+ describe "if the target's attribute is nil" do
33
+ let(:target) { Target.new(nil) }
34
+ let(:subject) { MatchNoAttribute.new(:search_attr) }
35
+
36
+ it "is true" do
37
+ expect(subject.match?(target)).to be_truthy
38
+ end
39
+ end
40
+
41
+ describe "if the target doesn't have the attribute" do
42
+ let(:target) { Target.new(rand) }
43
+ let(:subject) { MatchNoAttribute.new(:bad_attr) }
44
+
45
+ it "is false" do
46
+ expect(subject.match?(target)).to be_falsey
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "build_me?" do
52
+ describe "given a search param of 'none' or 'false'" do
53
+ it "is true" do
54
+ %w(none false).each do |search_param|
55
+ expect(MatchNoAttribute.build_me?(rand, search_param)).to be_truthy
56
+ end
57
+ end
58
+ end
59
+
60
+ describe "given a search param that is not 'none' or 'false'" do
61
+ it "is false" do
62
+ %w(one falsey).each do |search_param|
63
+ expect(MatchNoAttribute.build_me?(rand, search_param)).to be_falsey
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "../../../lib/query_string_search/search_options"
2
+
3
+ RSpec.describe QueryStringSearch::SearchOptions do
4
+ describe "parse" do
5
+ describe "with a single-element and multi-element query_string" do
6
+ let(:single_query_string) { "test=filter" }
7
+ let(:multi_query_string) { "test=filter,test2=test%20attribute" }
8
+ let(:single_element) { QueryStringSearch::SearchOptions.parse(single_query_string) }
9
+ let(:multi_element) { QueryStringSearch::SearchOptions.parse(multi_query_string) }
10
+
11
+ describe "returns a collection" do
12
+ it "that is enumerable" do
13
+ [single_element, multi_element].each do |it|
14
+ expect(it).to respond_to(:each)
15
+ end
16
+ end
17
+
18
+ describe "whose contents" do
19
+ it "have search_type that are symbols" do
20
+ expect(single_element.collect(&:search_type)).to eq([:test])
21
+ expect(multi_element.collect(&:search_type)).to eq([:test, :test2])
22
+ end
23
+
24
+ it "have search_param that are unescaped strings" do
25
+ expect(single_element.collect(&:search_param)).to eq(["filter"])
26
+ expect(multi_element.collect(&:search_param)).to eq(["filter", "test attribute"])
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "with a nil" do
33
+ let(:nil_element) { QueryStringSearch::SearchOptions.parse(nil) }
34
+ describe "returns a collection" do
35
+ it "that is enumerable" do
36
+ expect(nil_element).to respond_to(:each)
37
+ end
38
+
39
+ it "with one element" do
40
+ expect(nil_element.count).to eq(1)
41
+ end
42
+
43
+ it "with nil search_type" do
44
+ expect(nil_element.first.search_type).to be_nil
45
+ end
46
+
47
+ it "with nil search_param" do
48
+ expect(nil_element.first.search_param).to be_nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "../../../lib/query_string_search/search_parameters"
2
+ require_relative "../../../lib/query_string_search/abstract_matcher"
3
+ require_relative "../../../lib/query_string_search/search_options"
4
+
5
+ RSpec.describe QueryStringSearch::SearchParameters do
6
+ let(:factory) { class_double("QueryStringSearch::MatcherFactory") }
7
+ let(:matchers) { QueryStringSearch::AbstractMatcher.matchers }
8
+ let(:options_double) { double("search_options") }
9
+
10
+ describe "build_from_querystring" do
11
+ it "parses the query_string and creates an instance of SearchParameters" do
12
+ expect(QueryStringSearch::SearchOptions).to receive(:parse).with("test=filter").and_return(options_double)
13
+ expect(QueryStringSearch::SearchParameters).to receive(:new).with(options_double, factory, matchers)
14
+ QueryStringSearch::SearchParameters.build_from_querystring("test=filter", factory, matchers)
15
+ end
16
+ end
17
+
18
+ describe "new" do
19
+ it "uses the factory to build each parameter" do
20
+ params = QueryStringSearch::SearchOptions.parse("test=filter")
21
+ params.each do |p|
22
+ expect(factory).to receive(:build).with(p, matchers)
23
+ end
24
+ QueryStringSearch::SearchParameters.new(params, factory, matchers)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,85 @@
1
+ # This file was generated by the `rails generate rspec:install` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # This option will default to `true` in RSpec 4. It makes the `description`
23
+ # and `failure_message` of custom matchers include text for helper methods
24
+ # defined using `chain`, e.g.:
25
+ # be_bigger_than(2).and_smaller_than(4).description
26
+ # # => "be bigger than 2 and smaller than 4"
27
+ # ...rather than:
28
+ # # => "be bigger than 2"
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ # rspec-mocks config goes here. You can use an alternate test double
33
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
34
+ config.mock_with :rspec do |mocks|
35
+ # Prevents you from mocking or stubbing a method that does not exist on
36
+ # a real object. This is generally recommended, and will default to
37
+ # `true` in RSpec 4.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # The settings below are suggested to provide a good initial experience
42
+ # with RSpec, but feel free to customize to your heart's content.
43
+ # These two settings work together to allow you to limit a spec run
44
+ # to individual examples or groups you care about by tagging them with
45
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
46
+ # get run.
47
+ config.filter_run :focus
48
+ config.run_all_when_everything_filtered = true
49
+
50
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
51
+ # For more details, see:
52
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
53
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
54
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
55
+ config.disable_monkey_patching!
56
+
57
+ # Many RSpec users commonly either run the entire suite or an individual
58
+ # file, and it's useful to allow more verbose output when running an
59
+ # individual spec file.
60
+ if config.files_to_run.one?
61
+ # Use the documentation formatter for detailed output,
62
+ # unless a formatter has already been configured
63
+ # (e.g. via a command-line flag).
64
+ config.default_formatter = 'doc'
65
+ end
66
+
67
+ # Print the 10 slowest examples and example groups at the
68
+ # end of the spec run, to help surface which specs are running
69
+ # particularly slow.
70
+ config.profile_examples = 10
71
+
72
+ # Run specs in random order to surface order dependencies. If you find an
73
+ # order dependency and want to debug it, you can fix the order by providing
74
+ # the seed, which is printed after each run.
75
+ # --seed 1234
76
+ config.order = :random
77
+
78
+ # Seed global randomization in this process using the `--seed` CLI option.
79
+ # Setting this allows you to use `--seed` to deterministically reproduce
80
+ # test failures related to randomization by passing the same `--seed` value
81
+ # as the one that triggered the failure.
82
+ Kernel.srand config.seed
83
+ end
84
+
85
+ Target = Struct.new(:search_attr)
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: query_string_search
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Ian Whitney
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - whit0694@umn.edu
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".rubocop.yml"
79
+ - ".ruby-version"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - lib/query_string_search.rb
85
+ - lib/query_string_search/abstract_matcher.rb
86
+ - lib/query_string_search/matcher_factory.rb
87
+ - lib/query_string_search/matchers/match_all.rb
88
+ - lib/query_string_search/matchers/match_attribute.rb
89
+ - lib/query_string_search/matchers/match_attribute_value.rb
90
+ - lib/query_string_search/matchers/match_no_attribute.rb
91
+ - lib/query_string_search/search_options.rb
92
+ - lib/query_string_search/search_parameters.rb
93
+ - lib/query_string_search/version.rb
94
+ - query_string_search.gemspec
95
+ - spec/features/find_all_data_spec.rb
96
+ - spec/features/find_records_that_match_multiple_parameters_spec.rb
97
+ - spec/features/find_records_with_matching_attribute_values_spec.rb
98
+ - spec/features/find_records_with_nil_attributes_spec.rb
99
+ - spec/features/find_records_with_nonnil_attributes_spec.rb
100
+ - spec/features/find_seen_and_unseen_movies_spec.rb
101
+ - spec/fixtures/movie.rb
102
+ - spec/lib/query_string_search/abstract_matcher_spec.rb
103
+ - spec/lib/query_string_search/matcher_factory_spec.rb
104
+ - spec/lib/query_string_search/matchers/match_all_spec.rb
105
+ - spec/lib/query_string_search/matchers/match_attribute_spec.rb
106
+ - spec/lib/query_string_search/matchers/match_attribute_value_spec.rb
107
+ - spec/lib/query_string_search/matchers/match_no_attribute_spec.rb
108
+ - spec/lib/query_string_search/search_options_spec.rb
109
+ - spec/lib/query_string_search/search_parameters_spec.rb
110
+ - spec/spec_helper.rb
111
+ homepage: https://github.com/umn-asr/query_string_search
112
+ licenses:
113
+ - ''
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.2.2
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Provides a standard way to do searches using query strings in an API endpoint
135
+ test_files:
136
+ - spec/features/find_all_data_spec.rb
137
+ - spec/features/find_records_that_match_multiple_parameters_spec.rb
138
+ - spec/features/find_records_with_matching_attribute_values_spec.rb
139
+ - spec/features/find_records_with_nil_attributes_spec.rb
140
+ - spec/features/find_records_with_nonnil_attributes_spec.rb
141
+ - spec/features/find_seen_and_unseen_movies_spec.rb
142
+ - spec/fixtures/movie.rb
143
+ - spec/lib/query_string_search/abstract_matcher_spec.rb
144
+ - spec/lib/query_string_search/matcher_factory_spec.rb
145
+ - spec/lib/query_string_search/matchers/match_all_spec.rb
146
+ - spec/lib/query_string_search/matchers/match_attribute_spec.rb
147
+ - spec/lib/query_string_search/matchers/match_attribute_value_spec.rb
148
+ - spec/lib/query_string_search/matchers/match_no_attribute_spec.rb
149
+ - spec/lib/query_string_search/search_options_spec.rb
150
+ - spec/lib/query_string_search/search_parameters_spec.rb
151
+ - spec/spec_helper.rb