searchlight 0.0.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/.travis.yml +6 -0
- data/CHANGELOG.md +9 -0
- data/README.md +206 -46
- data/TODO.md +5 -0
- data/lib/searchlight/adapters/action_view.rb +5 -1
- data/lib/searchlight/adapters/active_record.rb +14 -8
- data/lib/searchlight/dsl.rb +17 -14
- data/lib/searchlight/search.rb +37 -17
- data/lib/searchlight/version.rb +1 -1
- data/lib/searchlight.rb +1 -0
- data/searchlight.gemspec +2 -1
- data/spec/searchlight/adapters/action_view_spec.rb +5 -1
- data/spec/searchlight/adapters/active_record_spec.rb +6 -1
- data/spec/searchlight/search_spec.rb +19 -6
- data/spec/spec_helper.rb +2 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGRhMjI1Mjg1ZDRmYTA5Yjc1YTk1ZWVkZWY5M2VhNjQ2YmQ0YzRhYQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NTU2MjMxYTdmY2YwNTYxZDY4YzU0NTY2ZjczNGRjMjlmNTM3MWFiZg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
OWE1NWE0ZDc3N2MyMGJiNzZmY2MwNGYzMzg4NWVmNjY4MWQyYzY5ZGEzZmQz
|
10
|
+
YjIwYmE4ZGJkZDhkNzhkNmM0MDNhODBiMzRhMTVjODY1MjM2NjJkMzhkNzZi
|
11
|
+
ODVmZDMyMGQ2N2Q1YWEwMDVlOTI0Zjg5YjBmYjYzZjZjOTU2M2U=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YmNhN2M0M2VjMmM0MTZmMjBmOTI1ODJlMzE0NWI0YTVhZDA2ZDIzYWY0NTYx
|
14
|
+
YjFhM2QxMGZmZGY3MTFkNzg0NWY0YmMzN2MxMTViMjlmMWQyMTRiYjEzZTAy
|
15
|
+
MjMyZTY5YjRjYWM1NjI2ZTE0MjNmN2UyMWM2MDVmNzliMmMyMDU=
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -2,98 +2,253 @@
|
|
2
2
|
|
3
3
|
Searchlight helps you build searches from options via Ruby methods that you write.
|
4
4
|
|
5
|
-
Searchlight
|
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.
|
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)
|
9
9
|
|
10
10
|
## Overview
|
11
11
|
|
12
|
-
The basic idea of Searchlight is to build a search by chaining method calls that you define. It calls methods on the object you specify, based on the options you pass.
|
12
|
+
The basic idea of Searchlight is to build a search by chaining method calls that you define. It calls **public** methods on the object you specify, based on the options you pass.
|
13
13
|
|
14
|
-
For example, if you have a Searchlight search class called `
|
14
|
+
For example, if you have a Searchlight search class called `YetiSearch`, and you instantiate it like this:
|
15
15
|
|
16
16
|
```ruby
|
17
|
-
|
17
|
+
yeti_search = YetiSearch(active: true, name: 'Jimmy', location_in: %w[NY LA]) # or params[:search]
|
18
18
|
```
|
19
19
|
|
20
|
-
... calling `results`
|
20
|
+
... calling `results` on the instance will build a search by chaining calls to `search_active`, `search_name`, and `search_location`.
|
21
21
|
|
22
|
-
The `results` method will then return the return value of the last search method. If you're using ActiveRecord, this would be an `ActiveRecord::Relation
|
22
|
+
The `results` method will then return the return value of the last search method. If you're using ActiveRecord, this would be an `ActiveRecord::Relation`, and you can then call `each` to loop through the results, `to_sql` to get the generated query, etc.
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
26
|
### Search class
|
27
27
|
|
28
|
-
|
28
|
+
A search class has three main parts: a target, options, and methods. For example:
|
29
29
|
|
30
30
|
```ruby
|
31
|
-
|
32
|
-
class AccountSearch < Searchlight::Search
|
31
|
+
class PersonSearch < Searchlight::Search
|
33
32
|
|
34
|
-
#
|
35
|
-
search_on
|
33
|
+
# The search target; in this case, an ActiveRecord model.
|
34
|
+
search_on Person
|
36
35
|
|
37
|
-
# The search
|
38
|
-
searches :
|
36
|
+
# The options the search understands. Supply any combination of them to an instance.
|
37
|
+
searches :first_name, :last_name
|
39
38
|
|
40
|
-
#
|
41
|
-
def
|
42
|
-
search
|
39
|
+
# A search method.
|
40
|
+
def search_first_name
|
41
|
+
# If this is the first search method called, `search` here will be
|
42
|
+
# the search target, namely, `Person`.
|
43
|
+
# `first_name` is an automatically-defined accessor for the option value.
|
44
|
+
search.where(first_name: first_name)
|
43
45
|
end
|
44
46
|
|
45
|
-
#
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
47
|
+
# Another search method.
|
48
|
+
def search_last_name
|
49
|
+
# If this is the second search method called, `search` here will be
|
50
|
+
# whatever `search_first_name` returned.
|
51
|
+
search.where(last_name: last_name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Here's a fuller example search class.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# app/searches/city_search.rb
|
60
|
+
class CitySearch < Searchlight::Search
|
61
|
+
|
62
|
+
# `City` here is an ActiveRecord model (see notes below on the adapter)
|
63
|
+
search_on City.includes(:country)
|
64
|
+
|
65
|
+
searches :name, :continent, :country_name_like, :is_megacity
|
66
|
+
|
67
|
+
# Reach into other tables
|
68
|
+
def search_continent
|
69
|
+
search.where('`countries`.`continent` = ?', continent)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Other kinds of queries
|
73
|
+
def search_country_name_like
|
74
|
+
search.where("`countries`.`name` LIKE ?", "%#{country_name_like}%")
|
57
75
|
end
|
58
76
|
|
59
|
-
#
|
60
|
-
|
61
|
-
|
77
|
+
# For every option, we also add an accessor that coerces to a boolean,
|
78
|
+
# considering 'false', 0, and '0' to be false
|
79
|
+
def search_is_megacity
|
80
|
+
search.where("`cities`.`population` #{is_megacity? ? '>=' : '<'} ?", 10_000_000)
|
62
81
|
end
|
63
82
|
|
64
83
|
end
|
65
84
|
```
|
66
85
|
|
67
|
-
|
86
|
+
Here are some example searches.
|
68
87
|
|
69
88
|
```ruby
|
70
|
-
|
71
|
-
|
89
|
+
CitySearch.new.results.to_sql
|
90
|
+
# => "SELECT `cities`.* FROM `cities` "
|
91
|
+
CitySearch.new(name: 'Nairobi').results.to_sql
|
92
|
+
# => "SELECT `cities`.* FROM `cities` WHERE `cities`.`name` = 'Nairobi'"
|
93
|
+
|
94
|
+
CitySearch.new(country_name_like: 'aust', continent: 'Europe').results.count # => 6
|
95
|
+
|
96
|
+
non_megas = CitySearch.new(is_megacity: 'false')
|
97
|
+
non_megas.results.to_sql
|
98
|
+
# => "SELECT `cities`.* FROM `cities` WHERE (`cities`.`population` < 100000"
|
99
|
+
non_megas.results.each do |city|
|
100
|
+
# ...
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### Accessors
|
105
|
+
|
106
|
+
For each search option, Searchlight defines two accessors: one for a value, and one for a boolean.
|
107
|
+
|
108
|
+
For example, if your class `searches :awesomeness` and gets instantiated like:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
search = MySearchClass(awesomeness: 'Xtreme')
|
112
|
+
```
|
113
|
+
|
114
|
+
... your search methods can use:
|
115
|
+
|
116
|
+
- `awesomeness` to retrive the given value, `'Xtreme'`
|
117
|
+
- `awesomeness?` to get a boolean version: `true`
|
118
|
+
|
119
|
+
The boolean conversion is form-friendly, so that `0`, `'0'`, and `'false'` are considered `false`.
|
120
|
+
|
121
|
+
All accessors are defined in modules, so you can override them and use `super` to call the original methods.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
class PersonSearch < Searchlight::Search
|
125
|
+
|
126
|
+
searches :names, :awesomeness
|
127
|
+
|
128
|
+
def names
|
129
|
+
# Make sure this is an array and never search for Jimmy.
|
130
|
+
# Jimmy is a private man. An old-fashioned man. Leave him be.
|
131
|
+
Array(super).reject { |name| name == 'Jimmy' }
|
132
|
+
end
|
133
|
+
|
134
|
+
def searches_names
|
135
|
+
search.where("name IN (?)", names)
|
136
|
+
end
|
137
|
+
|
138
|
+
def awesomeness?
|
139
|
+
# Disagree about what is awesome
|
140
|
+
!super
|
141
|
+
end
|
72
142
|
|
73
|
-
def search
|
74
|
-
@search = AccountSearch.new(params[:search])
|
75
143
|
end
|
76
|
-
...
|
77
144
|
```
|
78
145
|
|
79
|
-
###
|
146
|
+
### Defining Defaults
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
|
150
|
+
class CitySearch < Searchlight::Search
|
151
|
+
|
152
|
+
#...
|
153
|
+
|
154
|
+
def initialize(options = {})
|
155
|
+
super
|
156
|
+
self.continent ||= 'Asia'
|
157
|
+
end
|
158
|
+
|
159
|
+
#...
|
160
|
+
end
|
161
|
+
|
162
|
+
CitySearch.new.results.to_sql
|
163
|
+
=> "SELECT `cities`.* FROM `cities` WHERE (`countries`.`continent` = 'Asia')"
|
164
|
+
CitySearch.new(continent: 'Europe').results.to_sql
|
165
|
+
=> "SELECT `cities`.* FROM `cities` WHERE (`countries`.`continent` = 'Europe')"
|
166
|
+
```
|
167
|
+
|
168
|
+
### Subclassing
|
169
|
+
|
170
|
+
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.
|
171
|
+
|
80
172
|
```ruby
|
81
|
-
|
173
|
+
class VillageSearch < CitySearch
|
174
|
+
search_on Village
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
You can also use `search_target` to get the superclass's `search_on` value, so you can do this:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
class SmallTownSearch < CitySearch
|
182
|
+
search_on search_target.where("`cities`.`population` < ?", 1_000)
|
183
|
+
end
|
184
|
+
|
185
|
+
SmallTownSearch.new(country_name_like: 'Norfolk').results.to_sql
|
186
|
+
=> "SELECT `cities`.* FROM `cities` WHERE (`cities`.`population` < 1000) AND (`countries`.`name` LIKE '%Norfolk%')"
|
187
|
+
```
|
188
|
+
|
189
|
+
## Usage in Rails
|
190
|
+
|
191
|
+
Searchlight plays nicely with Rails views.
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# app/views/cities/index.html.haml
|
82
195
|
...
|
83
|
-
= form_for(search, url:
|
196
|
+
= form_for(@search, url: search_cities_path) do |f|
|
197
|
+
%fieldset
|
198
|
+
= f.label :name, "Name"
|
199
|
+
= f.text_field :name
|
200
|
+
|
84
201
|
%fieldset
|
85
|
-
= f.label
|
86
|
-
= f.
|
202
|
+
= f.label :country_name_like, "Country Name Like"
|
203
|
+
= f.text_field :country_name_like
|
87
204
|
|
88
205
|
%fieldset
|
89
|
-
= f.label :
|
90
|
-
= f.select :
|
206
|
+
= f.label :is_megacity, "Megacity?"
|
207
|
+
= f.select :is_megacity, [['Yes', true], ['No', false], ['Either', '']]
|
91
208
|
|
92
209
|
%fieldset
|
93
|
-
= f.label :
|
94
|
-
= f.select :
|
210
|
+
= f.label :continent, "Continent"
|
211
|
+
= f.select :continent, ['Africa', 'Asia', 'Europe'], include_blank: true
|
212
|
+
|
213
|
+
= f.submit "Search"
|
95
214
|
```
|
96
215
|
|
216
|
+
As long as your form submits options your search understands, you can easily hook it up in your controller:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
# app/controllers/cities_controller.rb
|
220
|
+
class CitiesController < ApplicationController
|
221
|
+
def search
|
222
|
+
@search = CitySearch.new(params[:search])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
```
|
226
|
+
## Adapters
|
227
|
+
|
228
|
+
Currently, Searchlight has adapters for ActiveRecord and ActionView. We'd love to get pull requests for others. :)
|
229
|
+
|
230
|
+
### ActiveRecord
|
231
|
+
|
232
|
+
When you call `search_on` in your Searchlight class, Searchlight checks whether the search target comes from ActiveRecord, and, if so, mixes a module into your class.
|
233
|
+
|
234
|
+
For each of your search options, the module will have the simplest possible search method defined. For example, if your class `searches :name`, the module will have this method:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
def search_name
|
238
|
+
search.where(name: name)
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
Since that method is in a parent module, you can easily override it by defining your own method. You can also call `super` in the method you define.
|
243
|
+
|
244
|
+
### ActionView
|
245
|
+
|
246
|
+
Similarly, Searchlight adds ActionView-friendly methods to your classes if it sees that `ActionView` is a defined constant. See the code for details, but the upshot is that you can use a search with `form_for`.
|
247
|
+
|
248
|
+
## Compatibility
|
249
|
+
|
250
|
+
For any given version, check `.travis.yml` to see what Ruby versions we're testing for compatibility.
|
251
|
+
|
97
252
|
## Installation
|
98
253
|
|
99
254
|
Add this line to your application's Gemfile:
|
@@ -115,3 +270,8 @@ Or install it yourself as:
|
|
115
270
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
116
271
|
4. Push to the branch (`git push origin my-new-feature`)
|
117
272
|
5. Create new Pull Request
|
273
|
+
|
274
|
+
## Shout Outs
|
275
|
+
|
276
|
+
- The excellent [Mr. Adam Hunter](https://github.com/adamhunter), co-creator of Searchlight.
|
277
|
+
- [TMA](http://tma1.com) for supporting the development of Searchlight.
|
data/TODO.md
ADDED
@@ -5,7 +5,7 @@ module Searchlight
|
|
5
5
|
module ClassMethods
|
6
6
|
|
7
7
|
def model_name
|
8
|
-
ActiveModel::Name.new(self, nil, '
|
8
|
+
ActiveModel::Name.new(self, nil, 'search')
|
9
9
|
end
|
10
10
|
|
11
11
|
end
|
@@ -16,6 +16,10 @@ module Searchlight
|
|
16
16
|
[]
|
17
17
|
end
|
18
18
|
|
19
|
+
def persisted?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
19
23
|
end
|
20
24
|
|
21
25
|
end
|
@@ -4,22 +4,28 @@ module Searchlight
|
|
4
4
|
|
5
5
|
def search_on(target)
|
6
6
|
super
|
7
|
-
extend Search if target.is_a?(::ActiveRecord::Base)
|
7
|
+
extend Search if target.is_a?(::ActiveRecord::Base) || target.is_a?(::ActiveRecord::Relation)
|
8
8
|
end
|
9
9
|
|
10
10
|
module Search
|
11
11
|
def searches(*attribute_names)
|
12
12
|
super
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
14
|
+
# Ensure this class only adds one search module to the ancestors chain
|
15
|
+
if @ar_searches_module.nil?
|
16
|
+
@ar_searches_module = Named::Module.new("SearchlightActiveRecordSearches(#{self})")
|
17
|
+
include @ar_searches_module
|
20
18
|
end
|
21
19
|
|
22
|
-
attribute_names.
|
20
|
+
eval_string = attribute_names.map { |attribute_name|
|
21
|
+
<<-UNICORN_BILE
|
22
|
+
def search_#{attribute_name}
|
23
|
+
search.where('#{attribute_name}' => public_send("#{attribute_name}"))
|
24
|
+
end
|
25
|
+
UNICORN_BILE
|
26
|
+
}.join
|
27
|
+
|
28
|
+
@ar_searches_module.module_eval(eval_string, __FILE__, __LINE__)
|
23
29
|
end
|
24
30
|
end
|
25
31
|
|
data/lib/searchlight/dsl.rb
CHANGED
@@ -6,25 +6,28 @@ module Searchlight
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def searches(*attribute_names)
|
9
|
-
include_new_module "SearchlightAccessors" do
|
10
|
-
attr_accessor *attribute_names
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
# Ensure this class only adds one accessors module to the ancestors chain
|
11
|
+
if @accessors_module.nil?
|
12
|
+
@accessors_module = Named::Module.new("SearchlightAccessors(#{self})") do
|
13
|
+
private
|
14
|
+
# Treat 0 (eg, from checkboxes) as false
|
15
|
+
def truthy?(value)
|
16
|
+
!(['0', 'false', ''].include?(value.to_s.strip))
|
17
17
|
end
|
18
18
|
end
|
19
|
+
include @accessors_module
|
19
20
|
end
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
eval_string = "attr_accessor *#{attribute_names}\n"
|
23
|
+
eval_string << attribute_names.map { |attribute_name|
|
24
|
+
<<-LEPRECHAUN_JUICE
|
25
|
+
def #{attribute_name}?
|
26
|
+
truthy?(public_send("#{attribute_name}"))
|
27
|
+
end
|
28
|
+
LEPRECHAUN_JUICE
|
29
|
+
}.join
|
30
|
+
@accessors_module.module_eval(eval_string, __FILE__, __LINE__)
|
27
31
|
end
|
28
|
-
|
29
32
|
end
|
30
33
|
end
|
data/lib/searchlight/search.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
module Searchlight
|
4
2
|
class Search
|
5
3
|
extend DSL
|
@@ -8,17 +6,10 @@ module Searchlight
|
|
8
6
|
defined?(@search_target) ? @search_target : superclass.search_target
|
9
7
|
end
|
10
8
|
|
11
|
-
def self.search_methods
|
12
|
-
defined?(@search_methods) ? @search_methods : superclass.search_methods
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.method_added(name)
|
16
|
-
@search_methods ||= Set.new
|
17
|
-
search_methods << name.to_s if name.to_s.start_with?('search_')
|
18
|
-
end
|
19
|
-
|
20
9
|
def initialize(options = {})
|
21
|
-
options.each { |key, value| public_send("#{key}=", value) }
|
10
|
+
options.each { |key, value| public_send("#{key}=", value) } if options && options.any?
|
11
|
+
rescue NoMethodError => e
|
12
|
+
raise UndefinedOption.new(e.name, self.class.name)
|
22
13
|
end
|
23
14
|
|
24
15
|
def search
|
@@ -35,15 +26,44 @@ module Searchlight
|
|
35
26
|
|
36
27
|
private
|
37
28
|
|
29
|
+
def search_methods
|
30
|
+
public_methods.map(&:to_s).select { |m| m.start_with?('search_') }
|
31
|
+
end
|
32
|
+
|
38
33
|
def run
|
39
|
-
|
40
|
-
|
41
|
-
unless
|
42
|
-
self.search = public_send(method)
|
43
|
-
end
|
34
|
+
search_methods.each do |method|
|
35
|
+
new_search = run_search_method(method)
|
36
|
+
self.search = new_search unless new_search.nil?
|
44
37
|
end
|
45
38
|
search
|
46
39
|
end
|
47
40
|
|
41
|
+
def run_search_method(method_name)
|
42
|
+
option_value = instance_variable_get("@#{method_name.sub(/\Asearch_/, '')}")
|
43
|
+
option_value = option_value.reject { |item| blank_value?(item) } if option_value.respond_to?(:reject)
|
44
|
+
public_send(method_name) unless blank_value?(option_value)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Note that false is not blank
|
48
|
+
def blank_value?(value)
|
49
|
+
(value.respond_to?(:empty?) && value.empty?) || value.nil? || value.to_s.strip == ''
|
50
|
+
end
|
51
|
+
|
52
|
+
class UndefinedOption < StandardError
|
53
|
+
attr_accessor :message
|
54
|
+
def initialize(option_name, search_class)
|
55
|
+
option_name = option_name.to_s.sub(/=\Z/, '')
|
56
|
+
self.message = "#{search_class} doesn't search '#{option_name}'."
|
57
|
+
if option_name.start_with?('search_')
|
58
|
+
# Gee golly, I'm so helpful!
|
59
|
+
self.message << " Did you just mean '#{option_name.sub(/\Asearch_/, '')}'?"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
message
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
48
68
|
end
|
49
69
|
end
|
data/lib/searchlight/version.rb
CHANGED
data/lib/searchlight.rb
CHANGED
data/searchlight.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Searchlight::VERSION
|
9
9
|
spec.authors = ["Nathan Long", "Adam Hunter"]
|
10
10
|
spec.email = ["nathanmlong@gmail.com", "adamhunter@me.com"]
|
11
|
-
spec.description = %q{Searchlight helps you build searches from options via Ruby methods that you write.}
|
11
|
+
spec.description = %q{Searchlight helps you build searches from options via Ruby methods that you write. 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.}
|
12
12
|
spec.summary = %q{Searchlight helps you build searches from options via Ruby methods that you write.}
|
13
13
|
spec.homepage = "https://github.com/nathanl/searchlight"
|
14
14
|
spec.license = "MIT"
|
@@ -25,4 +25,5 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_development_dependency "rake"
|
26
26
|
spec.add_development_dependency "rails", ">= 3"
|
27
27
|
spec.add_development_dependency "capybara", "~> 2.0"
|
28
|
+
spec.add_development_dependency "simplecov", "~> 0.7"
|
28
29
|
end
|
@@ -19,7 +19,11 @@ describe 'Searchlight::Adapters::ActionView', type: :feature, adapter: true do
|
|
19
19
|
f.text_field(:paid_amount)
|
20
20
|
end
|
21
21
|
|
22
|
-
expect(form).to have_selector("form input[name='
|
22
|
+
expect(form).to have_selector("form input[name='search[paid_amount]'][value='15']")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "tells the form that it is not persisted" do
|
26
|
+
expect(search).not_to be_persisted
|
23
27
|
end
|
24
28
|
|
25
29
|
end
|
@@ -19,7 +19,7 @@ describe 'Searchlight::Adapters::ActiveRecord', adapter: true do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
it "adds search_elephants to the search_methods array" do
|
22
|
-
expect(
|
22
|
+
expect(search_instance.send(:search_methods)).to include('search_elephants')
|
23
23
|
end
|
24
24
|
|
25
25
|
it "defines search methods that call where on the search target" do
|
@@ -27,4 +27,9 @@ describe 'Searchlight::Adapters::ActiveRecord', adapter: true do
|
|
27
27
|
expect(search_instance.search.called_methods).to eq([:where])
|
28
28
|
end
|
29
29
|
|
30
|
+
it "sets arguments properly in the defined method" do
|
31
|
+
search_instance.search.should_receive(:where).with('elephants' => 'yes, please')
|
32
|
+
search_instance.search_elephants
|
33
|
+
end
|
34
|
+
|
30
35
|
end
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Searchlight::Search do
|
4
4
|
|
5
|
-
let(:search_class) { Named::Class.new('
|
5
|
+
let(:search_class) { Named::Class.new('ExampleSearch', described_class) }
|
6
6
|
let(:options) { Hash.new }
|
7
7
|
let(:search) { search_class.new(options) }
|
8
8
|
|
@@ -15,6 +15,16 @@ describe Searchlight::Search do
|
|
15
15
|
expect(search.beak_color).to eq('mauve')
|
16
16
|
end
|
17
17
|
|
18
|
+
describe "handling invalid options" do
|
19
|
+
|
20
|
+
let(:options) { {genus: 'Mellivora'} }
|
21
|
+
|
22
|
+
it "raises an error explaining that this search class doesn't search the given property" do
|
23
|
+
expect { search }.to raise_error( Searchlight::Search::UndefinedOption, /ExampleSearch.*genus/)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
18
28
|
end
|
19
29
|
|
20
30
|
describe "search_on" do
|
@@ -44,7 +54,7 @@ describe Searchlight::Search do
|
|
44
54
|
describe "search_methods" do
|
45
55
|
|
46
56
|
let(:search_class) {
|
47
|
-
Named::Class.new('
|
57
|
+
Named::Class.new('ExampleSearch', described_class) do
|
48
58
|
def search_bees
|
49
59
|
end
|
50
60
|
|
@@ -57,7 +67,7 @@ describe Searchlight::Search do
|
|
57
67
|
}
|
58
68
|
|
59
69
|
it "keeps a unique list of the search methods" do
|
60
|
-
expect(
|
70
|
+
expect(search.send(:search_methods).map(&:to_s).sort).to eq(['search_bats', 'search_bees'])
|
61
71
|
end
|
62
72
|
|
63
73
|
end
|
@@ -68,11 +78,14 @@ describe Searchlight::Search do
|
|
68
78
|
|
69
79
|
before :each do
|
70
80
|
search_class.searches :foo
|
81
|
+
search_class.searches :bar
|
82
|
+
search_class.searches :stuff
|
71
83
|
end
|
72
84
|
|
73
|
-
it "includes
|
74
|
-
|
75
|
-
expect(
|
85
|
+
it "includes exactly one SearchlightAccessors module for this class" do
|
86
|
+
accessors_modules = search_class.ancestors.select {|a| a.name =~ /\ASearchlightAccessors/ }
|
87
|
+
expect(accessors_modules.length).to eq(1)
|
88
|
+
expect(accessors_modules.first).to be_a(Named::Module)
|
76
89
|
end
|
77
90
|
|
78
91
|
it "adds a getter" do
|
data/spec/spec_helper.rb
CHANGED
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.0
|
4
|
+
version: 0.9.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-
|
12
|
+
date: 2013-04-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: named
|
@@ -95,8 +95,24 @@ dependencies:
|
|
95
95
|
- - ~>
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '2.0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: simplecov
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ~>
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0.7'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ~>
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0.7'
|
98
112
|
description: Searchlight helps you build searches from options via Ruby methods that
|
99
|
-
you write.
|
113
|
+
you write. Searchlight can work with any ORM or object that allows chaining search
|
114
|
+
methods. It comes with modules for integrating with ActiveRecord and ActionView,
|
115
|
+
but can easily be used in any Ruby program.
|
100
116
|
email:
|
101
117
|
- nathanmlong@gmail.com
|
102
118
|
- adamhunter@me.com
|
@@ -106,10 +122,13 @@ extra_rdoc_files: []
|
|
106
122
|
files:
|
107
123
|
- .gitignore
|
108
124
|
- .rspec
|
125
|
+
- .travis.yml
|
126
|
+
- CHANGELOG.md
|
109
127
|
- Gemfile
|
110
128
|
- LICENSE.txt
|
111
129
|
- README.md
|
112
130
|
- Rakefile
|
131
|
+
- TODO.md
|
113
132
|
- lib/searchlight.rb
|
114
133
|
- lib/searchlight/adapters/action_view.rb
|
115
134
|
- lib/searchlight/adapters/active_record.rb
|