searchlight 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad62f9410f019b9c6bb09ce5e1e6b1e804ee152c
4
- data.tar.gz: 154208ad780633fff9670fd0a174ae3d46ae228a
3
+ metadata.gz: be95aae20e317b4b6be6f0124ab80790405b62e5
4
+ data.tar.gz: 370872f18bf03156a833b49052fd034019b37bf1
5
5
  SHA512:
6
- metadata.gz: 0aba3b57deac310d31274584c06ecd8075c2c709e99b8ca2190dd7fffadee25cd31a78b162ca750c5b3ea054d134b61e385b8d7f0eacd12da07f039c530dbcfd
7
- data.tar.gz: dfb54abe4eadcb4a3657a37225315ed0e8c1c79d65e035b5655f306fb329b2e9190529f50e2ca6fd3c98dd9f4b23bc2fe4c0ab91316708e174f6e8c92e012a66
6
+ metadata.gz: fddc94f601422650e35d6626041b454adb226163dbd5d9685b6864415fc408ca8cd5d918cc52fe72a37e56fe4f12a57e771e73e24a957c0b74a6b286e0cf19b0
7
+ data.tar.gz: 397ac3fc0b5b8c249eff00f332c508fb23fef38c9576f21870a01864d91d8a79433d35e34389bd9f15dea928e786ecef88e33e0f2ce77a44ed5b5e365d189f0c
@@ -4,7 +4,9 @@ Searchlight does its best to use [semantic versioning](http://semver.org), for t
4
4
 
5
5
  ## Unreleased
6
6
 
7
- Nothing
7
+ ## v4.1.0 - 2015-10-29
8
+
9
+ - Option readers now will find symbol keys as well as string keys
8
10
 
9
11
  ## v4.0.0 - 2015-10-28
10
12
 
@@ -18,6 +20,7 @@ Removed the DSL methods to simplify some things. This is a breaking change, but
18
20
  - `empty?` interprets empty arrays and hashes as empty, as well as empty or whitespace-only strings. It's used to filter the options that get passed to your search methods.
19
21
  - `explain` tells you exactly how searchlight interpreted the options a search was given. (Depending on your ORM, you might also want to call `.sql` or `.to_sql` on `search.results` for further debugging.)
20
22
  - `Searchlight::Adapters::ActionView` adapter must now be explicitly required and included.
23
+ - Within your `search_` methods, the query you chain on is now called `query`, not `search`
21
24
 
22
25
  ## v3.1.1
23
26
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ Searchlight can work with **any** ORM or object that can build a query using **c
11
11
 
12
12
  ## Getting Started
13
13
 
14
- An [introductory video](https://vimeo.com/69179161), [the demo app it uses](http://bookfinder-searchlight-demo.herokuapp.com) and [the code for that app](https://github.com/nathanl/bookfinder) are available to help you get started.
14
+ A [demo app](http://bookfinder-searchlight-demo.herokuapp.com) and [the code for that app](https://github.com/nathanl/searchlight_demo) are available to help you get started.
15
15
 
16
16
  ## Overview
17
17
 
@@ -55,15 +55,15 @@ class PersonSearch < Searchlight::Search
55
55
  # A search method.
56
56
  def search_first_name
57
57
  # If `"first_name"` was the first key in the options_hash,
58
- # `search` here will be the base query, namely, `Person.all`.
59
- search.where(first_name: options[:first_name])
58
+ # `query` here will be the base query, namely, `Person.all`.
59
+ query.where(first_name: options[:first_name])
60
60
  end
61
61
 
62
62
  # Another search method.
63
63
  def search_last_name
64
64
  # If `"last_name"` was the second key in the options_hash,
65
- # `search` here will be whatever `search_first_name` returned.
66
- search.where(last_name: last_name)
65
+ # `query` here will be whatever `search_first_name` returned.
66
+ query.where(last_name: last_name)
67
67
  end
68
68
  end
69
69
  ```
@@ -83,17 +83,17 @@ class CitySearch < Searchlight::Search
83
83
 
84
84
  # Reach into other tables
85
85
  def search_continent
86
- search.where('`countries`.`continent` = ?', continent)
86
+ query.where('`countries`.`continent` = ?', continent)
87
87
  end
88
88
 
89
89
  # Other kinds of queries
90
90
  def search_country_name_like
91
- search.where("`countries`.`name` LIKE ?", "%#{country_name_like}%")
91
+ query.where("`countries`.`name` LIKE ?", "%#{country_name_like}%")
92
92
  end
93
93
 
94
94
  # .checked? considers "false", 0 and "0" to be false
95
95
  def search_is_megacity
96
- search.where("`cities`.`population` #{checked?(is_megacity) ? '>=' : '<'} ?", 10_000_000)
96
+ query.where("`cities`.`population` #{checked?(is_megacity) ? '>=' : '<'} ?", 10_000_000)
97
97
  end
98
98
 
99
99
  end
@@ -117,9 +117,9 @@ non_megas.results.each do |city|
117
117
  end
118
118
  ```
119
119
 
120
- ### Accessors
120
+ ### Option Readers
121
121
 
122
- For each search method you define, Searchlight will define a corresponding accessor method. Eg, if you add `def search_first_name`, your search class will get a `.first_name` method that returns `options["first_name"]`. This is useful mainly when building forms.
122
+ For each search method you define, Searchlight will define a corresponding option reader method. Eg, if you add `def search_first_name`, your search class will get a `.first_name` method that returns `options["first_name"]`. This is useful mainly when building forms.
123
123
 
124
124
  **Note that this checks for string keys** - you must either use them, or use something like `ActiveSupport::HashWithIndifferentAccess`.
125
125
 
@@ -287,6 +287,8 @@ Or install it yourself as:
287
287
 
288
288
  ## Contributing
289
289
 
290
+ `rake` runs the tests; `rake mutant` runs mutation tests using [mutant](https://github.com/mbj/mutant).
291
+
290
292
  1. Fork it
291
293
  2. Create your feature branch (`git checkout -b my-new-feature`)
292
294
  3. Commit your changes (`git commit -am 'Add some feature'`)
data/Rakefile CHANGED
@@ -13,3 +13,20 @@ task :console do
13
13
  ARGV.clear
14
14
  IRB.start
15
15
  end
16
+
17
+ desc "Mutation test with mutant gem. Provide scope, eg: Searchlight::Search or Searchlight::Options#excluding_empties"
18
+ task :mutant do
19
+ scope = ENV.fetch("SCOPE") {
20
+ puts "Must set SCOPE env variable, eg `SCOPE=Searchlight::Search`"
21
+ exit
22
+ }
23
+
24
+ ARGV.clear
25
+ command = "mutant --include lib --require searchlight --use rspec #{scope}"
26
+ begin
27
+ exec(command)
28
+ rescue Errno::ENOENT
29
+ puts "Could not find mutant executable - please install gem 'mutant-rspec'"
30
+ puts "(Not included as a test dependency because it breaks CI; mutant only works with Ruby > 2.1.0"
31
+ end
32
+ end
@@ -3,7 +3,7 @@ module Searchlight::Options
3
3
  def self.empty?(value)
4
4
  return true if value.nil?
5
5
  return true if value.respond_to?(:empty?) && value.empty?
6
- return true if value.is_a?(String) && /\A[[:space:]]*\z/ === value
6
+ return true if /\A[[:space:]]*\z/ === value
7
7
  false
8
8
  end
9
9
 
@@ -14,14 +14,14 @@ module Searchlight::Options
14
14
  def self.excluding_empties(input)
15
15
  output = input.dup
16
16
  output.each do |key, value|
17
- if value.is_a?(Array)
18
- output[key] = value.reject { |v| empty?(v) }
19
- end
20
17
  if value.is_a?(Hash)
21
- output[key] = value.reject { |k, v| empty?(v) }
18
+ output[key] = value.reject { |_, v| empty?(v) }
19
+ end
20
+ if value.instance_of?(Array)
21
+ output[key] = value.reject { |v| empty?(v) }
22
22
  end
23
23
  end
24
- output.reject { |key, value| empty?(value) }
24
+ output.reject { |_, value| empty?(value) }
25
25
  end
26
26
 
27
27
  end
@@ -12,12 +12,17 @@ class Searchlight::Search
12
12
  option_name = match.captures.fetch(0)
13
13
  # accessor - eg, if method_name is #search_title, define #title
14
14
  define_method(option_name) do
15
- options[option_name]
15
+ options.key?(option_name) ? options[option_name] : options[option_name.to_sym]
16
16
  end
17
17
  end
18
18
  end
19
19
 
20
20
  def initialize(raw_options = {})
21
+ string_keys, non_string_keys = raw_options.keys.partition {|k| k.is_a?(String) }
22
+ intersection = string_keys & non_string_keys.map(&:to_s)
23
+ if intersection.any?
24
+ fail ArgumentError, "more than one key converts to these string values: #{intersection}"
25
+ end
21
26
  @raw_options = raw_options
22
27
  end
23
28
 
@@ -49,7 +54,7 @@ class Searchlight::Search
49
54
 
50
55
  def options_with_search_methods
51
56
  {}.tap do |map|
52
- options.each do |option_name, option_value|
57
+ options.each do |option_name, _|
53
58
  method_name = "search_#{option_name}"
54
59
  map[option_name] = method_name if respond_to?(method_name)
55
60
  end
@@ -1,3 +1,3 @@
1
1
  module Searchlight
2
- VERSION = "4.0.0"
2
+ VERSION = "4.1.0"
3
3
  end
@@ -52,6 +52,11 @@ describe Searchlight::Options do
52
52
  describe "excluding empties" do
53
53
 
54
54
  it "removes empty values at the top level of the hash" do
55
+ # Simulate something like HashWithIndifferentAccess
56
+ relations = FancyHash.new
57
+ relations[:uncle] = "Jimmy"
58
+ relations[:aunt] = nil
59
+
55
60
  expect(
56
61
  mod.excluding_empties(
57
62
  name: "Bob",
@@ -59,8 +64,8 @@ describe Searchlight::Options do
59
64
  likes: ["pizza", "fish"],
60
65
  dislikes: [],
61
66
  elvish: false,
62
- relations: {uncle: "Jimmy"},
63
- eh: {},
67
+ relations: relations,
68
+ enemies: {},
64
69
  )
65
70
  ).to eq(
66
71
  name: "Bob",
@@ -75,10 +80,11 @@ describe Searchlight::Options do
75
80
  it "does not remove empty values from more deeply-nested elements" do
76
81
  expect(
77
82
  mod.excluding_empties(
78
- tags: ["one", "two", "", nil, "three", ["a", "", nil, "b"], {a: ""}]
83
+ tags: ["one", "two", "", nil, "three", ["a", "", nil, "b"], {a: ""}],
84
+
79
85
  )
80
86
  ).to eq(
81
- tags: ["one", "two", "three", ["a", "", nil, "b"], {a: ""}]
87
+ tags: ["one", "two", "three", ["a", "", nil, "b"], {a: ""}],
82
88
  )
83
89
  end
84
90
 
@@ -14,6 +14,22 @@ describe Searchlight::Search do
14
14
  }
15
15
  let(:search) { BookSearch.new(raw_options) }
16
16
 
17
+ describe "initialization" do
18
+
19
+ it "doesn't require options" do
20
+ expect(BookSearch.new.results).to eq(BookSearch.new({}).results)
21
+ end
22
+
23
+ it "blows up if there is a string/symbol key conflict" do
24
+ expect {
25
+ described_class.new(a: 1, "a" => 2)
26
+ }.to raise_error(
27
+ ArgumentError, %Q{more than one key converts to these string values: ["a"]}
28
+ )
29
+ end
30
+
31
+ end
32
+
17
33
  describe "parsing options" do
18
34
 
19
35
  it "makes the raw options available" do
@@ -39,6 +55,18 @@ describe Searchlight::Search do
39
55
  end
40
56
  end
41
57
 
58
+ describe "option readers" do
59
+
60
+ it "has an option reader method for each search method, which can read strings or symbols" do
61
+ expect(search.author_name_like).to eq("Lew")
62
+ expect(search.title_like).to eq("Mere Christianity")
63
+ expect(search.board_book).to eq(nil)
64
+ expect{search.book_thickness}.to raise_error(NoMethodError)
65
+ expect{search.not_an_option}.to raise_error(NoMethodError)
66
+ end
67
+
68
+ end
69
+
42
70
  describe "querying" do
43
71
 
44
72
  it "builds results by running all methods matching its options" do
@@ -55,7 +83,19 @@ describe Searchlight::Search do
55
83
  end
56
84
 
57
85
  it "has an 'explain' method to show how it builds its query" do
58
- expect(search.explain).to be_a(String)
86
+ expect(search.explain).to eq(
87
+ %Q{
88
+ Initialized with `raw_options`: [:title_like, "author_name_like", :category_in, :tags, :book_thickness, :parts_about_lolcats]
89
+
90
+ Of those, the non-blank ones are available as `options`: [:title_like, "author_name_like", :tags, :book_thickness, :in_print]
91
+
92
+ Of those, the following have corresponding `search_` methods: [:title_like, "author_name_like", :in_print]. These would be used to build the query.
93
+
94
+ Blank options are: [:category_in, :parts_about_lolcats]
95
+
96
+ Non-blank options with no corresponding `search_` method are: [:tags, :book_thickness]
97
+ }.strip
98
+ )
59
99
  end
60
100
 
61
101
  end
@@ -3,6 +3,7 @@ require 'searchlight'
3
3
  $LOAD_PATH << '.'
4
4
  require 'support/mock_model'
5
5
  require 'support/book_search'
6
+ require 'support/fancy_hash'
6
7
 
7
8
  RSpec.configure do |config|
8
9
  config.run_all_when_everything_filtered = true
@@ -0,0 +1,3 @@
1
+ # Eg, like Rails' HashWithIndifferentAccess
2
+ class FancyHash < Hash
3
+ end
@@ -1,7 +1,7 @@
1
1
  class MockModel
2
2
 
3
3
  def self.method_missing(method, *args, &block)
4
- MockRelation.new(method)
4
+ MockRelation.new([method])
5
5
  end
6
6
 
7
7
  end
@@ -9,20 +9,16 @@ end
9
9
  class MockRelation
10
10
  attr_reader :called_methods
11
11
 
12
- def initialize(called_method)
13
- @called_methods = [called_method]
12
+ def initialize(called_methods)
13
+ @called_methods = called_methods
14
14
  end
15
15
 
16
16
  def method_missing(method, *args, &block)
17
- tap { called_methods << method }
17
+ self.class.new(called_methods + [method])
18
18
  end
19
-
20
- def is_a?(thing)
21
- thing == ::ActiveRecord::Relation ? true : super
22
- end
23
-
24
- def engine
25
- MockActiveRecord
19
+
20
+ def ==(other)
21
+ other.class == self.class && other.called_methods == called_methods
26
22
  end
27
23
 
28
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchlight
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Long
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-10-28 00:00:00.000000000 Z
12
+ date: 2015-10-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -129,6 +129,7 @@ files:
129
129
  - spec/searchlight_spec.rb
130
130
  - spec/spec_helper.rb
131
131
  - spec/support/book_search.rb
132
+ - spec/support/fancy_hash.rb
132
133
  - spec/support/mock_model.rb
133
134
  homepage: https://github.com/nathanl/searchlight
134
135
  licenses:
@@ -161,4 +162,5 @@ test_files:
161
162
  - spec/searchlight_spec.rb
162
163
  - spec/spec_helper.rb
163
164
  - spec/support/book_search.rb
165
+ - spec/support/fancy_hash.rb
164
166
  - spec/support/mock_model.rb