searchlight 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +117 -0
- data/Rakefile +7 -0
- data/lib/searchlight/adapters/action_view.rb +26 -0
- data/lib/searchlight/adapters/active_record.rb +30 -0
- data/lib/searchlight/dsl.rb +30 -0
- data/lib/searchlight/search.rb +49 -0
- data/lib/searchlight/version.rb +3 -0
- data/lib/searchlight.rb +10 -0
- data/searchlight.gemspec +28 -0
- data/spec/searchlight/adapters/action_view_spec.rb +25 -0
- data/spec/searchlight/adapters/active_record_spec.rb +30 -0
- data/spec/searchlight/search_spec.rb +185 -0
- data/spec/searchlight_spec.rb +9 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/account_search.rb +23 -0
- data/spec/support/mock_model.rb +27 -0
- data/spec/support/spiffy_account_search.rb +9 -0
- metadata +161 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
!binary "U0hBMQ==":
|
|
3
|
+
metadata.gz: !binary |-
|
|
4
|
+
OTA2YmIzYjk0NDhkNjdlNTg5MWU3MWQzOWExOTg0Mzg0Y2VmYjhkYw==
|
|
5
|
+
data.tar.gz: !binary |-
|
|
6
|
+
N2I3MmFlOGEzMjFiODcyNjUxNzA1ZWUxZjg4YmY5OTk0OWZjZGNlOQ==
|
|
7
|
+
!binary "U0hBNTEy":
|
|
8
|
+
metadata.gz: !binary |-
|
|
9
|
+
OWUxMjY2OGYxMjNmZGJiOTgyYjY3OGVkYzgxOWI4NTQ2ZmI2YzA2N2MwYTA4
|
|
10
|
+
NGQ3Y2FjYmM4MTRjZGU2OWY1YjQ3MDg3MmJjNDAxMzY1NWM2OTJjN2VlZDNh
|
|
11
|
+
MzY5YjIzZjE2MGRkYzVmZTMyMTI3ZmU3MzVkMTU0MTliZjVmNzI=
|
|
12
|
+
data.tar.gz: !binary |-
|
|
13
|
+
ODJjNGM2NmQzNWFjY2FlYjUwN2EzNGQwN2MzNjI2Njg4MjE2NWUxNzg3ZjJl
|
|
14
|
+
ZDk4NzcxYzUzNzE4NWQ3ODg5NGZmZmUwOGM0ODljOTE5MDRiYWIzMmMzMDJl
|
|
15
|
+
MTMzMWU2YmEwMWY3MjM2MzU1MjJlZTk0MDNlNjBmY2VjM2U5ZGQ=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Nathan Long
|
|
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,117 @@
|
|
|
1
|
+
# Searchlight
|
|
2
|
+
|
|
3
|
+
Searchlight helps you build searches from options via Ruby methods that you write.
|
|
4
|
+
|
|
5
|
+
Searchlight comes with ActiveRecord integration, but can call search methods on any ORM or object that allows chaining search methods.
|
|
6
|
+
|
|
7
|
+
[](https://travis-ci.org/nathanl/searchlight)
|
|
8
|
+
[](https://codeclimate.com/github/nathanl/searchlight)
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
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.
|
|
13
|
+
|
|
14
|
+
For example, if you have a Searchlight search class called `FooSearch`, and you instantiate it like this:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
foo_search = FooSearch(active: true, name: 'Jimmy', location_in: %w[NY LA]) # or params[:query]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
... calling `results` will call the instance methods `search_active`, `search_name`, and `search_location_in`. (If you omit the `active` option, `search_active` won't be called.)
|
|
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`. You can then call `each` to loop through the results, `to_sql` to get the generated query, etc.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Search class
|
|
27
|
+
|
|
28
|
+
Here's an example search class that uses ActiveRecord.
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# app/searches/account_search.rb
|
|
32
|
+
class AccountSearch < Searchlight::Search
|
|
33
|
+
|
|
34
|
+
# Defines the `search_target`
|
|
35
|
+
search_on Account
|
|
36
|
+
|
|
37
|
+
# The search options this class knows how to handle
|
|
38
|
+
searches :contract_id, :invoicing_status, :active
|
|
39
|
+
|
|
40
|
+
# If a `contract_id` option is given, this method will be called
|
|
41
|
+
def search_contract_id
|
|
42
|
+
search.where(contract_id: contract_id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# If an `invoicing` option is given, this method will be called
|
|
46
|
+
def search_invoicing
|
|
47
|
+
case invoicing_status
|
|
48
|
+
when 'partial'
|
|
49
|
+
search.partially_invoiced
|
|
50
|
+
when 'complete'
|
|
51
|
+
search.completely_invoiced
|
|
52
|
+
when 'never'
|
|
53
|
+
search.uninvoiced
|
|
54
|
+
else
|
|
55
|
+
search
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# If an `active` option is given, this method will be called
|
|
60
|
+
def search_active
|
|
61
|
+
search.where(status: active? ? 'active' : 'inactive')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Controller
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# app/controllers/accounts_controller.rb
|
|
71
|
+
class AccountsController
|
|
72
|
+
|
|
73
|
+
def search
|
|
74
|
+
@search = AccountSearch.new(params[:search])
|
|
75
|
+
end
|
|
76
|
+
...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### View
|
|
80
|
+
```ruby
|
|
81
|
+
# app/views/accounts/index.html.haml
|
|
82
|
+
...
|
|
83
|
+
= form_for(search, url: '#') do |f|
|
|
84
|
+
%fieldset
|
|
85
|
+
= f.label :contract_id, "Contract"
|
|
86
|
+
= f.select :contract_id, available_contracts_collection
|
|
87
|
+
|
|
88
|
+
%fieldset
|
|
89
|
+
= f.label :invoicing_status, "Invoicing Status"
|
|
90
|
+
= f.select :invoicing_status, invoice_statuses_collection
|
|
91
|
+
|
|
92
|
+
%fieldset
|
|
93
|
+
= f.label :active, "Active?"
|
|
94
|
+
= f.select :active, [['Active', true], ['Inactive', false], ['Either', nil]]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
Add this line to your application's Gemfile:
|
|
100
|
+
|
|
101
|
+
gem 'searchlight'
|
|
102
|
+
|
|
103
|
+
And then execute:
|
|
104
|
+
|
|
105
|
+
$ bundle
|
|
106
|
+
|
|
107
|
+
Or install it yourself as:
|
|
108
|
+
|
|
109
|
+
$ gem install searchlight
|
|
110
|
+
|
|
111
|
+
## Contributing
|
|
112
|
+
|
|
113
|
+
1. Fork it
|
|
114
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
115
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
116
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
117
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Searchlight
|
|
2
|
+
module Adapters
|
|
3
|
+
module ActionView
|
|
4
|
+
|
|
5
|
+
module ClassMethods
|
|
6
|
+
|
|
7
|
+
def model_name
|
|
8
|
+
ActiveModel::Name.new(self, nil, 'query')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module InstanceMethods
|
|
14
|
+
|
|
15
|
+
def to_key
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Searchlight::Search.send(:include, Searchlight::Adapters::ActionView::InstanceMethods)
|
|
26
|
+
Searchlight::Search.extend(Searchlight::Adapters::ActionView::ClassMethods)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Searchlight
|
|
2
|
+
module Adapters
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
|
|
5
|
+
def search_on(target)
|
|
6
|
+
super
|
|
7
|
+
extend Search if target.is_a?(::ActiveRecord::Base)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Search
|
|
11
|
+
def searches(*attribute_names)
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
include_new_module "SearchlightActiveRecordSearches" do
|
|
15
|
+
attribute_names.each do |attribute_name|
|
|
16
|
+
define_method("search_#{attribute_name}") do
|
|
17
|
+
search.where(attribute_name => public_send(attribute_name))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attribute_names.each { |attribute_name| method_added("search_#{attribute_name}") }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Searchlight::Search.extend(Searchlight::Adapters::ActiveRecord)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Searchlight
|
|
2
|
+
module DSL
|
|
3
|
+
|
|
4
|
+
def search_on(target)
|
|
5
|
+
@search_target = target
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def searches(*attribute_names)
|
|
9
|
+
include_new_module "SearchlightAccessors" do
|
|
10
|
+
attr_accessor *attribute_names
|
|
11
|
+
|
|
12
|
+
# define boolean accessors
|
|
13
|
+
attribute_names.each do |attribute_name|
|
|
14
|
+
define_method("#{attribute_name}?") do
|
|
15
|
+
# Treat 0 (eg, from checkboxes) as false
|
|
16
|
+
!['0', 'false', ''].include?(public_send(attribute_name).to_s.strip)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# So that we can allow calling `super` in submodules and the base class.
|
|
25
|
+
def include_new_module(module_name, &content)
|
|
26
|
+
include Named::Module.new(module_name, &content)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module Searchlight
|
|
4
|
+
class Search
|
|
5
|
+
extend DSL
|
|
6
|
+
|
|
7
|
+
def self.search_target
|
|
8
|
+
defined?(@search_target) ? @search_target : superclass.search_target
|
|
9
|
+
end
|
|
10
|
+
|
|
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
|
+
def initialize(options = {})
|
|
21
|
+
options.each { |key, value| public_send("#{key}=", value) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def search
|
|
25
|
+
@search ||= self.class.search_target
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def results
|
|
29
|
+
@results ||= run
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
attr_writer :search
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def run
|
|
39
|
+
self.class.search_methods.each do |method|
|
|
40
|
+
option_value = public_send(method.sub(/\Asearch_/, ''))
|
|
41
|
+
unless option_value.nil? || option_value.to_s.strip == ''
|
|
42
|
+
self.search = public_send(method)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
search
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/searchlight.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'named'
|
|
2
|
+
require 'searchlight/version'
|
|
3
|
+
|
|
4
|
+
module Searchlight
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'searchlight/dsl'
|
|
8
|
+
require 'searchlight/search'
|
|
9
|
+
require 'searchlight/adapters/active_record' if defined?(::ActiveRecord)
|
|
10
|
+
require 'searchlight/adapters/action_view' if defined?(::ActionView)
|
data/searchlight.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'searchlight/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "searchlight"
|
|
8
|
+
spec.version = Searchlight::VERSION
|
|
9
|
+
spec.authors = ["Nathan Long", "Adam Hunter"]
|
|
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.}
|
|
12
|
+
spec.summary = %q{Searchlight helps you build searches from options via Ruby methods that you write.}
|
|
13
|
+
spec.homepage = "https://github.com/nathanl/searchlight"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files`.split($/)
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_dependency "named", "~> 1.0"
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.13"
|
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
|
25
|
+
spec.add_development_dependency "rake"
|
|
26
|
+
spec.add_development_dependency "rails", ">= 3"
|
|
27
|
+
spec.add_development_dependency "capybara", "~> 2.0"
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Searchlight::Adapters::ActionView', type: :feature, adapter: true do
|
|
4
|
+
|
|
5
|
+
before :all do
|
|
6
|
+
require 'searchlight/adapters/action_view'
|
|
7
|
+
require 'action_view'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:view) { ::ActionView::Base.new }
|
|
11
|
+
let(:search) { AccountSearch.new(paid_amount: 15) }
|
|
12
|
+
|
|
13
|
+
before :each do
|
|
14
|
+
view.stub(:protect_against_forgery?).and_return(false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "it can be used to build a form" do
|
|
18
|
+
form = view.form_for(search, url: '#') do |f|
|
|
19
|
+
f.text_field(:paid_amount)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
expect(form).to have_selector("form input[name='query[paid_amount]'][value='15']")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Searchlight::Adapters::ActiveRecord', adapter: true do
|
|
4
|
+
|
|
5
|
+
before :all do
|
|
6
|
+
require 'searchlight/adapters/active_record'
|
|
7
|
+
require 'active_record'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:search_class) { Named::Class.new('SearchClass', Searchlight::Search) { search_on MockActiveRecord } }
|
|
11
|
+
let(:search_instance) { search_class.new(elephants: 'yes, please') }
|
|
12
|
+
|
|
13
|
+
before :each do
|
|
14
|
+
search_class.searches :elephants
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "adds search methods to the search class" do
|
|
18
|
+
expect(search_class.new).to respond_to(:search_elephants)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "adds search_elephants to the search_methods array" do
|
|
22
|
+
expect(search_class.search_methods).to include('search_elephants')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "defines search methods that call where on the search target" do
|
|
26
|
+
search_instance.results
|
|
27
|
+
expect(search_instance.search.called_methods).to eq([:where])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Searchlight::Search do
|
|
4
|
+
|
|
5
|
+
let(:search_class) { Named::Class.new('SearchClass', described_class) }
|
|
6
|
+
let(:options) { Hash.new }
|
|
7
|
+
let(:search) { search_class.new(options) }
|
|
8
|
+
|
|
9
|
+
describe "initializing" do
|
|
10
|
+
|
|
11
|
+
let(:options) { {beak_color: 'mauve'} }
|
|
12
|
+
|
|
13
|
+
it "mass-assigns provided options" do
|
|
14
|
+
search_class.searches :beak_color
|
|
15
|
+
expect(search.beak_color).to eq('mauve')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "search_on" do
|
|
21
|
+
|
|
22
|
+
let(:search_target) { "Bobby Fischer" }
|
|
23
|
+
|
|
24
|
+
before :each do
|
|
25
|
+
search_class.search_on search_target
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "makes the object accessible via `search_target`" do
|
|
29
|
+
expect(search_class.search_target).to eq(search_target)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "makes the search target available to its children" do
|
|
33
|
+
expect(SpiffyAccountSearch.search_target).to be(MockModel)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "allows the children to set their own search target" do
|
|
37
|
+
klass = Class.new(SpiffyAccountSearch) { search_on Array }
|
|
38
|
+
expect(klass.search_target).to be(Array)
|
|
39
|
+
expect(SpiffyAccountSearch.search_target).to be(MockModel)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "search_methods" do
|
|
45
|
+
|
|
46
|
+
let(:search_class) {
|
|
47
|
+
Named::Class.new('SearchClass', described_class) do
|
|
48
|
+
def search_bees
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def search_bats
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def search_bees
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
it "keeps a unique list of the search methods" do
|
|
60
|
+
expect(search_class.search_methods).to eq(Set.new(['search_bees', 'search_bats']))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "search options" do
|
|
66
|
+
|
|
67
|
+
describe "accessors" do
|
|
68
|
+
|
|
69
|
+
before :each do
|
|
70
|
+
search_class.searches :foo
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "includes a SearchlightAccessors module" do
|
|
74
|
+
accessors_module = search_class.ancestors.detect {|a| a.name == 'SearchlightAccessors' }
|
|
75
|
+
expect(accessors_module).to be_a(Named::Module)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "adds a getter" do
|
|
79
|
+
expect(search).to respond_to(:foo)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "adds a setter" do
|
|
83
|
+
expect(search).to respond_to(:foo=)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "adds a boolean accessor" do
|
|
87
|
+
expect(search).to respond_to(:foo?)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "accessing search options as booleans" do
|
|
93
|
+
|
|
94
|
+
let(:options) { {fishies: fishies} }
|
|
95
|
+
|
|
96
|
+
before :each do
|
|
97
|
+
search_class.searches :fishies
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
0 => false,
|
|
102
|
+
'0' => false,
|
|
103
|
+
'' => false,
|
|
104
|
+
' ' => false,
|
|
105
|
+
nil => false,
|
|
106
|
+
'false' => false,
|
|
107
|
+
1 => true,
|
|
108
|
+
'1' => true,
|
|
109
|
+
15 => true,
|
|
110
|
+
'true' => true,
|
|
111
|
+
'pie' => true
|
|
112
|
+
}.each do |input, output|
|
|
113
|
+
|
|
114
|
+
describe input.inspect do
|
|
115
|
+
|
|
116
|
+
let(:fishies) { input }
|
|
117
|
+
|
|
118
|
+
it "becomes boolean #{output}" do
|
|
119
|
+
expect(search.fishies?).to eq(output)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "search" do
|
|
131
|
+
|
|
132
|
+
let(:search) { AccountSearch.new }
|
|
133
|
+
|
|
134
|
+
it "is initialized with the search_target" do
|
|
135
|
+
expect(search.search).to eq(MockModel)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "results" do
|
|
141
|
+
|
|
142
|
+
let(:search) { AccountSearch.new(paid_amount: 50, business_name: "Rod's Meat Shack") }
|
|
143
|
+
|
|
144
|
+
it "builds a search by calling all of the methods that had values to search" do
|
|
145
|
+
search.results
|
|
146
|
+
expect(search.search.called_methods).to eq(2.times.map { :where })
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "returns the search" do
|
|
150
|
+
expect(search.results).to eq(search.search)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "only runs the search once" do
|
|
154
|
+
search.should_receive(:run).once.and_call_original
|
|
155
|
+
2.times { search.results }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
describe "run" do
|
|
161
|
+
|
|
162
|
+
let(:search_class) {
|
|
163
|
+
Named::Class.new('TinyBs', described_class) do
|
|
164
|
+
search_on Object
|
|
165
|
+
searches :bits, :bats, :bots
|
|
166
|
+
|
|
167
|
+
def search_bits; end
|
|
168
|
+
def search_bats; end
|
|
169
|
+
def search_bots; end
|
|
170
|
+
|
|
171
|
+
end
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let(:search_instance) { search_class.new(bits: ' ', bats: nil, bots: false) }
|
|
175
|
+
|
|
176
|
+
it "only runs search methods that have real values to search on" do
|
|
177
|
+
search_instance.should_not_receive(:search_bits)
|
|
178
|
+
search_instance.should_not_receive(:search_bats)
|
|
179
|
+
search_instance.should_receive(:search_bots)
|
|
180
|
+
search_instance.send(:run)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'capybara/rspec'
|
|
2
|
+
require 'searchlight'
|
|
3
|
+
$LOAD_PATH << '.'
|
|
4
|
+
require 'support/mock_model'
|
|
5
|
+
require 'support/account_search'
|
|
6
|
+
require 'support/spiffy_account_search'
|
|
7
|
+
|
|
8
|
+
RSpec.configure do |config|
|
|
9
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
|
10
|
+
config.run_all_when_everything_filtered = true
|
|
11
|
+
config.filter_run :focus
|
|
12
|
+
|
|
13
|
+
# Run specs in random order to surface order dependencies. If you find an
|
|
14
|
+
# order dependency and want to debug it, you can fix the order by providing
|
|
15
|
+
# the seed, which is printed after each run.
|
|
16
|
+
# --seed 1234
|
|
17
|
+
config.order = 'random'
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class AccountSearch < Searchlight::Search
|
|
2
|
+
|
|
3
|
+
search_on MockModel
|
|
4
|
+
|
|
5
|
+
searches :paid_amount, :business_name, :balance, :active
|
|
6
|
+
|
|
7
|
+
def search_paid_amount
|
|
8
|
+
search.where('amount > ?', paid_amount)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def search_business_name
|
|
12
|
+
search.where(business_name: business_name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def search_balance
|
|
16
|
+
search.where("owed - amount > ?", balance)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def search_active
|
|
20
|
+
search.where(active: active?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class MockModel
|
|
2
|
+
|
|
3
|
+
def self.method_missing(method, *args, &block)
|
|
4
|
+
MockRelation.new(method)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class MockActiveRecord < MockModel
|
|
10
|
+
|
|
11
|
+
def self.is_a?(thing)
|
|
12
|
+
thing == ActiveRecord::Base ? true : super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class MockRelation
|
|
18
|
+
attr_reader :called_methods
|
|
19
|
+
|
|
20
|
+
def initialize(called_method)
|
|
21
|
+
@called_methods = [called_method]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def method_missing(method, *args, &block)
|
|
25
|
+
tap { called_methods << method }
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: searchlight
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nathan Long
|
|
8
|
+
- Adam Hunter
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-04-08 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: named
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - ~>
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '1.0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ~>
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '1.0'
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: rspec
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - ~>
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '2.13'
|
|
35
|
+
type: :development
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ~>
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '2.13'
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: bundler
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ~>
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '1.3'
|
|
49
|
+
type: :development
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ~>
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '1.3'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: rake
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ! '>='
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
name: rails
|
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ! '>='
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '3'
|
|
77
|
+
type: :development
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ! '>='
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '3'
|
|
84
|
+
- !ruby/object:Gem::Dependency
|
|
85
|
+
name: capybara
|
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ~>
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '2.0'
|
|
91
|
+
type: :development
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ~>
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '2.0'
|
|
98
|
+
description: Searchlight helps you build searches from options via Ruby methods that
|
|
99
|
+
you write.
|
|
100
|
+
email:
|
|
101
|
+
- nathanmlong@gmail.com
|
|
102
|
+
- adamhunter@me.com
|
|
103
|
+
executables: []
|
|
104
|
+
extensions: []
|
|
105
|
+
extra_rdoc_files: []
|
|
106
|
+
files:
|
|
107
|
+
- .gitignore
|
|
108
|
+
- .rspec
|
|
109
|
+
- Gemfile
|
|
110
|
+
- LICENSE.txt
|
|
111
|
+
- README.md
|
|
112
|
+
- Rakefile
|
|
113
|
+
- lib/searchlight.rb
|
|
114
|
+
- lib/searchlight/adapters/action_view.rb
|
|
115
|
+
- lib/searchlight/adapters/active_record.rb
|
|
116
|
+
- lib/searchlight/dsl.rb
|
|
117
|
+
- lib/searchlight/search.rb
|
|
118
|
+
- lib/searchlight/version.rb
|
|
119
|
+
- searchlight.gemspec
|
|
120
|
+
- spec/searchlight/adapters/action_view_spec.rb
|
|
121
|
+
- spec/searchlight/adapters/active_record_spec.rb
|
|
122
|
+
- spec/searchlight/search_spec.rb
|
|
123
|
+
- spec/searchlight_spec.rb
|
|
124
|
+
- spec/spec_helper.rb
|
|
125
|
+
- spec/support/account_search.rb
|
|
126
|
+
- spec/support/mock_model.rb
|
|
127
|
+
- spec/support/spiffy_account_search.rb
|
|
128
|
+
homepage: https://github.com/nathanl/searchlight
|
|
129
|
+
licenses:
|
|
130
|
+
- MIT
|
|
131
|
+
metadata: {}
|
|
132
|
+
post_install_message:
|
|
133
|
+
rdoc_options: []
|
|
134
|
+
require_paths:
|
|
135
|
+
- lib
|
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
|
+
requirements:
|
|
138
|
+
- - ! '>='
|
|
139
|
+
- !ruby/object:Gem::Version
|
|
140
|
+
version: '0'
|
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ! '>='
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
requirements: []
|
|
147
|
+
rubyforge_project:
|
|
148
|
+
rubygems_version: 2.0.3
|
|
149
|
+
signing_key:
|
|
150
|
+
specification_version: 4
|
|
151
|
+
summary: Searchlight helps you build searches from options via Ruby methods that you
|
|
152
|
+
write.
|
|
153
|
+
test_files:
|
|
154
|
+
- spec/searchlight/adapters/action_view_spec.rb
|
|
155
|
+
- spec/searchlight/adapters/active_record_spec.rb
|
|
156
|
+
- spec/searchlight/search_spec.rb
|
|
157
|
+
- spec/searchlight_spec.rb
|
|
158
|
+
- spec/spec_helper.rb
|
|
159
|
+
- spec/support/account_search.rb
|
|
160
|
+
- spec/support/mock_model.rb
|
|
161
|
+
- spec/support/spiffy_account_search.rb
|