searchlight 0.9.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NGM3YWIxODBiNTM5MmM4NDJkNjk4Mzk3MmU5MGIwMzg4M2JlM2E2ZA==
5
- data.tar.gz: !binary |-
6
- Njc0ZDYxZjFkZWZmNDE2ZTJjNzY2Y2QzNjJjMTJiYjkwODZmODhiZA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- OGM2NjZmNTc2OTI3MjdkMmNlNTcwNzE2Mjk5Mzk3YmIzMzVmMjNjYTVmMGFi
10
- ODhkYjQ2ZmNiNzJlNDNlYzhlOTVjMDIxN2M5ZGE4NDU5ZGI4NmYyN2JjMzI2
11
- NWFmYWI0NDYwOTg4YjI1ZjhkOTJiNmQyNzkzNDRkMzc2ZjM2MjM=
12
- data.tar.gz: !binary |-
13
- M2JiOGE2ODJiNjhlOTM0NDBiNGVkZDBlZDE5MDNiMDRjMGRjOTQ5MzViZmM4
14
- YWZiOTVkNDFmZTdiZmRkMTM1ZjE3MGM2ZjBlMzFlMGE2ODM3NGM0OTBiN2I0
15
- NDFlOTE0NzM3MjJkOGFiYmY5ZDFkMzBmNTdkNzcyZmE5M2U5YTE=
2
+ SHA1:
3
+ metadata.gz: 65909cc6d8f99929aa4c76ba6a52e23dd8b85f59
4
+ data.tar.gz: 8ed82b1eab66fe78fcfc2b7b1630a8cbd2e2dcf3
5
+ SHA512:
6
+ metadata.gz: fd26998a2330f3b034cdad89efe07ab3a52e0125db072da03533aa0038e091240baf0af3428ddc84de5b7e7e558b9a0c5fe6b095cf9a30acee503dbd359cb27e
7
+ data.tar.gz: 62a9b2a4ae2e8e5d8b28b996199b500c210f63ef53805e1c5ce491b7273832a5aed11155ff86a83dc7c4f4d5f6d66aa8a88b1db7d524dd2ca24d622c769fa0ea
@@ -2,6 +2,13 @@
2
2
 
3
3
  Searchlight does its best to use [semantic versioning](http://semver.org).
4
4
 
5
+ ## v1.0.0
6
+
7
+ - If no search target is given, search class attempts to guess based on its own name
8
+ - All errors that can be raise extend `Searchlight::Error`
9
+ - Better testing
10
+ - Still more documentation!
11
+
5
12
  ## v0.9.1
6
13
 
7
14
  Bugfix for ActiveRecord adapter
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Searchlight helps you build searches from options via Ruby methods that you write.
4
4
 
5
- Searchlight can work with any ORM or object that allows chaining search methods. It comes with modules for integrating with ActiveRecord and ActionView, but can easily be used in any Ruby program.
5
+ Searchlight can work with any ORM or object that can build a query using chained methods (eg, ActiveRecord's `.where(...).where(...).limit(...)`). It comes with modules for integrating with ActiveRecord and ActionView, but can easily be used in any Ruby program.
6
6
 
7
7
  [![Build Status](https://api.travis-ci.org/nathanl/searchlight.png?branch=master)](https://travis-ci.org/nathanl/searchlight)
8
8
  [![Code Climate](https://codeclimate.com/github/nathanl/searchlight.png)](https://codeclimate.com/github/nathanl/searchlight)
@@ -145,6 +145,9 @@ end
145
145
 
146
146
  ### Defining Defaults
147
147
 
148
+ Set defaults using plain Ruby. These can be used to prefill a form or to assume what the user didn't specify.
149
+
150
+
148
151
  ```ruby
149
152
 
150
153
  class CitySearch < Searchlight::Search
@@ -152,7 +155,7 @@ class CitySearch < Searchlight::Search
152
155
  #...
153
156
 
154
157
  def initialize(options = {})
155
- super
158
+ super
156
159
  self.continent ||= 'Asia'
157
160
  end
158
161
 
@@ -165,6 +168,38 @@ CitySearch.new(continent: 'Europe').results.to_sql
165
168
  => "SELECT `cities`.* FROM `cities` WHERE (`countries`.`continent` = 'Europe')"
166
169
  ```
167
170
 
171
+ You can define defaults for boolean attributes if you treat them as "yes/no/either" choices.
172
+
173
+ ```ruby
174
+ class AnimalSearch < Searchlight::Search
175
+
176
+ search_on Animal
177
+
178
+ searches :is_fictional
179
+
180
+ def initialize(*args)
181
+ super
182
+ self.is_fictional = :either if is_fictional.blank?
183
+ end
184
+
185
+ def search_is_fictional
186
+ case is_fictional.to_s
187
+ when 'true' then search.where(fictional: true)
188
+ when 'false' then search.where(fictional: false)
189
+ when 'either' then search # unmodified
190
+ end
191
+ end
192
+ end
193
+
194
+
195
+ AnimalSearch.new(fictional: true).results.to_sql
196
+ => "SELECT `animals`.* FROM `animals` WHERE (`fictional` = true)"
197
+ AnimalSearch.new(fictional: false).results.to_sql
198
+ => "SELECT `animals`.* FROM `animals` WHERE (`fictional` = false)"
199
+ AnimalSearch.new.results.to_sql
200
+ => "SELECT `animals`.* FROM `animals`"
201
+ ```
202
+
168
203
  ### Subclassing
169
204
 
170
205
  You can subclass an existing search class and support all the same options with a different search target. This may be useful for single table inheritance, for example.
@@ -186,9 +221,15 @@ SmallTownSearch.new(country_name_like: 'Norfolk').results.to_sql
186
221
  => "SELECT `cities`.* FROM `cities` WHERE (`cities`.`population` < 1000) AND (`countries`.`name` LIKE '%Norfolk%')"
187
222
  ```
188
223
 
224
+ ### Dependent Options
225
+
226
+ To allow search options that don't trigger searches directly, just use `attr_accessor`.
227
+
189
228
  ## Usage in Rails
190
229
 
191
- Searchlight plays nicely with Rails views.
230
+ ### Forms
231
+
232
+ Searchlight plays nicely with Rails forms. All search options and any `attr_accessor`s you define can be hooked up to form fields.
192
233
 
193
234
  ```ruby
194
235
  # app/views/cities/index.html.haml
@@ -213,13 +254,23 @@ Searchlight plays nicely with Rails views.
213
254
  = f.submit "Search"
214
255
  ```
215
256
 
257
+ ### Controllers
258
+
216
259
  As long as your form submits options your search understands, you can easily hook it up in your controller:
217
260
 
218
261
  ```ruby
219
- # app/controllers/cities_controller.rb
220
- class CitiesController < ApplicationController
221
- def search
222
- @search = CitySearch.new(params[:search])
262
+ # app/controllers/orders_controller.rb
263
+ class OrdersController < ApplicationController
264
+
265
+ def index
266
+ @orders = OrderSearch.new(search_params)
267
+ end
268
+
269
+ protected
270
+
271
+ def search_params
272
+ # Ensure the user can only browse or search their own orders
273
+ (params[:search]) || {}).merge(user_id: current_user.id)
223
274
  end
224
275
  end
225
276
  ```
@@ -3,7 +3,9 @@ module Searchlight
3
3
  extend DSL
4
4
 
5
5
  def self.search_target
6
- defined?(@search_target) ? @search_target : superclass.search_target
6
+ return @search_target if defined?(@search_target)
7
+ return superclass.search_target if superclass.respond_to?(:search_target) && superclass != Searchlight::Search
8
+ guess_search_class!
7
9
  end
8
10
 
9
11
  def initialize(options = {})
@@ -26,6 +28,19 @@ module Searchlight
26
28
 
27
29
  private
28
30
 
31
+ def self.guess_search_class!
32
+ if self.name.end_with?('Search')
33
+ @search_target = name.sub(/Search\z/, '').split('::').inject(Kernel, &:const_get)
34
+ else
35
+ raise MissingSearchTarget, "No search target provided via `search_on` and Searchlight can't guess one."
36
+ end
37
+ rescue NameError => e
38
+ if /uninitialized constant/.match(e.message)
39
+ raise MissingSearchTarget, "No search target provided via `search_on` and Searchlight's guess was wrong. Error: #{e.message}"
40
+ end
41
+ raise e
42
+ end
43
+
29
44
  def search_methods
30
45
  public_methods.map(&:to_s).select { |m| m.start_with?('search_') }
31
46
  end
@@ -49,8 +64,12 @@ module Searchlight
49
64
  (value.respond_to?(:empty?) && value.empty?) || value.nil? || value.to_s.strip == ''
50
65
  end
51
66
 
52
- class UndefinedOption < StandardError
67
+ MissingSearchTarget = Class.new(Searchlight::Error)
68
+
69
+ class UndefinedOption < Searchlight::Error
70
+
53
71
  attr_accessor :message
72
+
54
73
  def initialize(option_name, search_class)
55
74
  option_name = option_name.to_s.sub(/=\Z/, '')
56
75
  self.message = "#{search_class} doesn't search '#{option_name}'."
@@ -63,6 +82,7 @@ module Searchlight
63
82
  def to_s
64
83
  message
65
84
  end
85
+
66
86
  end
67
87
 
68
88
  end
@@ -1,3 +1,3 @@
1
1
  module Searchlight
2
- VERSION = "0.9.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  describe Searchlight::Search do
4
4
 
5
5
  let(:search_class) { Named::Class.new('ExampleSearch', described_class) }
6
- let(:options) { Hash.new }
7
- let(:search) { search_class.new(options) }
6
+ let(:options) { Hash.new }
7
+ let(:search) { search_class.new(options) }
8
8
 
9
9
  describe "initializing" do
10
10
 
@@ -23,30 +23,80 @@ describe Searchlight::Search do
23
23
  expect { search }.to raise_error( Searchlight::Search::UndefinedOption, /ExampleSearch.*genus/)
24
24
  end
25
25
 
26
+ context "if the option starts with 'search_'" do
27
+
28
+ let(:options) { {search_genus: 'Mellivora'} }
29
+
30
+ it "suggests the option name the user may have meant to provide" do
31
+ expect { search }.to raise_error( Searchlight::Search::UndefinedOption, /ExampleSearch.*genus.*Did you just mean/)
32
+ end
33
+
34
+ end
35
+
26
36
  end
27
37
 
28
38
  end
29
39
 
30
40
  describe "search_on" do
31
41
 
32
- let(:search_target) { "Bobby Fischer" }
42
+ context "when an explicit search target was provided" do
33
43
 
34
- before :each do
35
- search_class.search_on search_target
36
- end
44
+ let(:search_target) { "Bobby Fischer" }
37
45
 
38
- it "makes the object accessible via `search_target`" do
39
- expect(search_class.search_target).to eq(search_target)
40
- end
46
+ before :each do
47
+ search_class.search_on search_target
48
+ end
49
+
50
+ it "makes the object accessible via `search_target`" do
51
+ expect(search_class.search_target).to eq(search_target)
52
+ end
53
+
54
+ it "makes the search target available to its children" do
55
+ expect(SpiffyAccountSearch.search_target).to be(MockModel)
56
+ end
57
+
58
+ it "allows the children to set their own search target" do
59
+ klass = Class.new(SpiffyAccountSearch) { search_on Array }
60
+ expect(klass.search_target).to be(Array)
61
+ expect(SpiffyAccountSearch.search_target).to be(MockModel)
62
+ end
41
63
 
42
- it "makes the search target available to its children" do
43
- expect(SpiffyAccountSearch.search_target).to be(MockModel)
44
64
  end
45
65
 
46
- it "allows the children to set their own search target" do
47
- klass = Class.new(SpiffyAccountSearch) { search_on Array }
48
- expect(klass.search_target).to be(Array)
49
- expect(SpiffyAccountSearch.search_target).to be(MockModel)
66
+ context "when no explicit search target was provided" do
67
+
68
+ let(:search_class) { Named::Class.new('Namespaced::ExampleSearch', described_class) }
69
+
70
+ it "guesses the search class based on its own namespaced class name" do
71
+ expect(search_class.search_target).to eq(Namespaced::Example)
72
+ end
73
+
74
+ context "when it can't make a guess as to the search class" do
75
+
76
+ let(:search_class) { Named::Class.new('Somekinda::Searchthingy', described_class) }
77
+
78
+ it "raises an exception" do
79
+ expect{search_class.search_target}.to raise_error(
80
+ Searchlight::Search::MissingSearchTarget,
81
+ /No search target/
82
+ )
83
+ end
84
+
85
+ end
86
+
87
+ context "when it tries to guess the search class but fails" do
88
+
89
+ let(:search_class) { Named::Class.new('NonExistentObjectSearch', described_class) }
90
+
91
+ it "raises an exception" do
92
+ expect{search_class.search_target}.to raise_error(
93
+ Searchlight::Search::MissingSearchTarget,
94
+ /No search target.*uninitialized constant.*NonExistentObject/
95
+ )
96
+ end
97
+
98
+ end
99
+
50
100
  end
51
101
 
52
102
  end
@@ -35,3 +35,7 @@ class MockRelation
35
35
 
36
36
  end
37
37
 
38
+ module Namespaced
39
+ class Example
40
+ end
41
+ 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: 0.9.1
4
+ version: 1.0.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: 2013-04-30 00:00:00.000000000 Z
12
+ date: 2013-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: named
@@ -57,28 +57,28 @@ dependencies:
57
57
  name: rake
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - ! '>='
60
+ - - '>='
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - ! '>='
67
+ - - '>='
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rails
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ! '>='
74
+ - - '>='
75
75
  - !ruby/object:Gem::Version
76
76
  version: '3'
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - ! '>='
81
+ - - '>='
82
82
  - !ruby/object:Gem::Version
83
83
  version: '3'
84
84
  - !ruby/object:Gem::Dependency
@@ -154,12 +154,12 @@ require_paths:
154
154
  - lib
155
155
  required_ruby_version: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - ! '>='
157
+ - - '>='
158
158
  - !ruby/object:Gem::Version
159
159
  version: '0'
160
160
  required_rubygems_version: !ruby/object:Gem::Requirement
161
161
  requirements:
162
- - - ! '>='
162
+ - - '>='
163
163
  - !ruby/object:Gem::Version
164
164
  version: '0'
165
165
  requirements: []