searchlight 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://api.travis-ci.org/nathanl/searchlight.png?branch=master)](https://travis-ci.org/nathanl/searchlight)
|
8
|
+
[![Code Climate](https://codeclimate.com/github/nathanl/searchlight.png)](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
|