searchlight 1.2.3 → 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 436f93fd15d48ee73339833c09e679d856210c44
4
- data.tar.gz: 0234dd6c0dbed8789a3214c10bc05750aa2a7917
3
+ metadata.gz: ebb954502f32317df1fe4fb490178e3c868a541c
4
+ data.tar.gz: b0d0283e388c84a917c1c0d627752ce0b625a10c
5
5
  SHA512:
6
- metadata.gz: 985d773a7359d271f74b437dcbcd81a031fc79e3177650236f69d06c4e300100b8e88ecd786b3b955fc64d2eed820fae72310f4cb6c9478fc9ba4ce34ad2f4cd
7
- data.tar.gz: e72a800936177e5eea6372c02093f04a36bea9daa532e0390e8a5d308c2e03ca774359a78fa0351f2f70c61d132093875002d9e8749a7f063c62e8345f9736dc
6
+ metadata.gz: 001d3bfe0e77b84ae6d256892310d18c63cb54ea416c2d3b7b794d5374c228d17423e32d3c82abdb59da8db6886b3a449c591b863a4ac9d578e9d34431d7b8ea
7
+ data.tar.gz: 72c116a81eb742c6d7ecf97c982d8ec99731c1321aee96d0246491c11c3725d8ab747d8eb36ca44b93c14751e24a4b275a465a28c6b77e7f254c28864c714b8a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Searchlight does its best to use [semantic versioning](http://semver.org).
4
4
 
5
+ ## v1.2.4
6
+
7
+ - `options` method only returns those that map to search methods (not `attr_accessor` type values)
8
+ - Previously, `searches :name` in a class with an `ActiveRecord` target would always trigger the `ActiveRecord` adapter to define `searches_name` as `search.where(name: name)`. Now it first checks whether `name` is a column on the model, and if not, defines the method to raise an error.
9
+
5
10
  ## v1.2.3
6
11
 
7
12
  Fix bug introduced in v1.2: setting defaults in `initialize` did not add them to the options hash, which meant they weren't used in searches.
data/TODO.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # TODO
2
2
 
3
+ - Only have `options` method return values that map to search methods; it doesn't track any `attr_accessor` you may have. This guarantees consistent contents.
3
4
  - Run rcov and mutant
4
5
  - Make nice Github page
5
6
  - Test with ActiveRecord 4
@@ -19,11 +19,23 @@ module Searchlight
19
19
  end
20
20
 
21
21
  eval_string = attribute_names.map { |attribute_name|
22
- <<-UNICORN_BILE
23
- def search_#{attribute_name}
24
- search.where('#{attribute_name}' => public_send("#{attribute_name}"))
22
+ model_class = model_class_for(search_target)
23
+ if model_class_for(search_target).columns_hash.keys.include?(attribute_name.to_s)
24
+
25
+ <<-UNICORN_BILE
26
+ def search_#{attribute_name}
27
+ search.where('#{attribute_name}' => public_send("#{attribute_name}"))
28
+ end
29
+ UNICORN_BILE
30
+ else
31
+ <<-MERMAID_TEARS
32
+ def search_#{attribute_name}
33
+ raise Searchlight::Adapters::ActiveRecord::UndefinedColumn,
34
+ "Class `#{model_class}` has no column `#{attribute_name}`; please define `search_#{attribute_name}` on `\#{self.class}` to clarify what you intend to search for"
35
+ end
36
+ MERMAID_TEARS
25
37
  end
26
- UNICORN_BILE
38
+
27
39
  }.join
28
40
 
29
41
  @ar_searches_module.module_eval(eval_string, __FILE__, __LINE__)
@@ -49,10 +61,15 @@ module Searchlight
49
61
  self.search_target = (active_record_version >= 4) ? search_target.all : search_target.scoped
50
62
  end
51
63
 
64
+ def model_class_for(target)
65
+ is_active_record_class?(target) ? target : target.engine
66
+ end
67
+
52
68
  def active_record_version
53
69
  ::ActiveRecord::VERSION::MAJOR.to_i
54
70
  end
55
71
 
72
+ UndefinedColumn = Class.new(StandardError)
56
73
  end
57
74
  end
58
75
  end
@@ -19,17 +19,9 @@ module Searchlight
19
19
  include @accessors_module
20
20
  end
21
21
 
22
- eval_string = attribute_names.map { |attribute_name|
22
+ eval_string = "attr_accessor *#{attribute_names}\n"
23
+ eval_string << attribute_names.map { |attribute_name|
23
24
  <<-LEPRECHAUN_JUICE
24
- def #{attribute_name}=(val)
25
- self.options ||= {}
26
- self.options[:#{attribute_name}] = val
27
- end
28
-
29
- def #{attribute_name}
30
- self.options[:#{attribute_name}]
31
- end
32
-
33
25
  def #{attribute_name}?
34
26
  truthy?(public_send("#{attribute_name}"))
35
27
  end
@@ -2,8 +2,6 @@ module Searchlight
2
2
  class Search
3
3
  extend DSL
4
4
 
5
- attr_accessor :options
6
-
7
5
  def self.search_target
8
6
  return @search_target if defined?(@search_target)
9
7
  return superclass.search_target if superclass.respond_to?(:search_target) && superclass != Searchlight::Search
@@ -22,12 +20,23 @@ module Searchlight
22
20
  @results ||= run
23
21
  end
24
22
 
23
+ def options
24
+ search_attributes.reduce({}) { |hash, option_name|
25
+ option_val = send(option_name)
26
+ hash.tap { |hash| hash[option_name.to_sym] = option_val unless is_blank?(option_val) }
27
+ }
28
+ end
29
+
25
30
  protected
26
31
 
27
32
  attr_writer :search
28
33
 
29
34
  private
30
35
 
36
+ def search_attributes
37
+ public_methods.map(&:to_s).select { |m| m.start_with?('search_') }.map { |m| m.sub(/\Asearch_/, '') }
38
+ end
39
+
31
40
  def self.guess_search_class!
32
41
  if self.name.end_with?('Search')
33
42
  @search_target = name.sub(/Search\z/, '').split('::').inject(Kernel, &:const_get)
@@ -44,8 +53,7 @@ module Searchlight
44
53
  end
45
54
 
46
55
  def filter_and_mass_assign(provided_options = {})
47
- provided_options = {} if provided_options.nil?
48
- self.options = provided_options.reject { |key, value| is_blank?(value) }
56
+ options = (provided_options || {}).reject { |key, value| is_blank?(value) }
49
57
  begin
50
58
  options.each { |key, value| public_send("#{key}=", value) } if options && options.any?
51
59
  rescue NoMethodError => e
@@ -55,7 +63,7 @@ module Searchlight
55
63
 
56
64
  def run
57
65
  options.each do |option_name, value|
58
- new_search = public_send("search_#{option_name}") if respond_to?("search_#{option_name}")
66
+ new_search = public_send("search_#{option_name}")
59
67
  self.search = new_search unless new_search.nil?
60
68
  end
61
69
  search
@@ -1,3 +1,3 @@
1
1
  module Searchlight
2
- VERSION = "1.2.3"
2
+ VERSION = "1.2.4"
3
3
  end
@@ -18,22 +18,46 @@ describe 'Searchlight::Adapters::ActiveRecord', adapter: true do
18
18
 
19
19
  shared_examples "search classes with an ActiveRecord target" do
20
20
 
21
- before :each do
22
- search_class.searches :elephants
23
- end
21
+ context "when the base model has a column matching the search term" do
24
22
 
25
- it "adds search methods to the search class" do
26
- expect(search_class.new).to respond_to(:search_elephants)
27
- end
23
+ before :each do
24
+ MockActiveRecord.stub(:columns_hash).and_return({'elephants' => 'column info...'})
25
+ search_class.searches :elephants
26
+ end
27
+
28
+ it "adds search methods to the search class" do
29
+ expect(search_class.new).to respond_to(:search_elephants)
30
+ end
31
+
32
+ it "defines search methods that call `where` on the search target" do
33
+ search_instance.results
34
+ expect(search_instance.search.called_methods).to include(:where)
35
+ end
36
+
37
+ it "sets arguments properly in the defined method" do
38
+ search_instance.search.should_receive(:where).with('elephants' => 'yes, please')
39
+ search_instance.search_elephants
40
+ end
28
41
 
29
- it "defines search methods that call where on the search target" do
30
- search_instance.results
31
- expect(search_instance.search.called_methods).to include(:where)
32
42
  end
33
43
 
34
- it "sets arguments properly in the defined method" do
35
- search_instance.search.should_receive(:where).with('elephants' => 'yes, please')
36
- search_instance.search_elephants
44
+ context "when the base model has no column matching the search term" do
45
+
46
+ before :each do
47
+ MockActiveRecord.stub(:columns_hash).and_return({})
48
+ search_class.searches :elephants
49
+ end
50
+
51
+ it "adds search methods to the search class" do
52
+ expect(search_class.new).to respond_to(:search_elephants)
53
+ end
54
+
55
+ it "defines search methods to raise an exception" do
56
+ expect { search_instance.results }.to raise_error(
57
+ Searchlight::Adapters::ActiveRecord::UndefinedColumn
58
+ )
59
+ end
60
+
37
61
  end
38
62
 
39
63
  end
@@ -2,7 +2,11 @@ require 'spec_helper'
2
2
 
3
3
  describe Searchlight::Search do
4
4
 
5
- let(:search_class) { Named::Class.new('ExampleSearch', described_class).tap { |klass| klass.searches *allowed_options } }
5
+ let(:search_class) { Named::Class.new('ExampleSearch', described_class).tap {|klass|
6
+ klass.searches *allowed_options
7
+ allowed_options.each { |name| klass.send(:define_method, "search_#{name}") {} }
8
+ }
9
+ }
6
10
  let(:allowed_options) { Hash.new }
7
11
  let(:provided_options) { Hash.new }
8
12
  let(:search) { search_class.new(provided_options) }
@@ -57,55 +61,74 @@ describe Searchlight::Search do
57
61
 
58
62
  end
59
63
 
64
+ context "when some options are do not map to search methods (eg, attr_accessor)" do
65
+ let(:search_class) {
66
+ Named::Class.new('ExampleSearch', described_class) do
67
+ attr_accessor :krazy_mode
68
+ def search_name; end
69
+ end.tap { |klass| klass.searches *allowed_options }
70
+ }
71
+ let(:provided_options) { {name: 'Reese Roper', krazy_mode: true} }
72
+
73
+ it "sets all the provided values" do
74
+ expect(search.name).to eq('Reese Roper')
75
+ expect(search.krazy_mode).to eq(true)
76
+ end
77
+
78
+ it "only lists options for the values corresponding to search methods" do
79
+ expect(search.options).to eq({name: 'Reese Roper'})
80
+ end
81
+
82
+ end
83
+
60
84
  end
61
85
 
62
86
  end
63
87
 
64
88
  context "when the search class has defaults" do
65
89
 
66
- context "when they're set during initialization" do
67
-
68
- let(:allowed_options) { [:name, :age] }
69
- let(:search_class) {
70
- Named::Class.new('ExampleSearch', described_class) do
90
+ let(:allowed_options) { [:name, :age] }
91
+ let(:search_class) {
92
+ Named::Class.new('ExampleSearch', described_class) do
71
93
 
72
- def initialize(options)
73
- super
74
- self.name ||= 'Dennis'
75
- self.age ||= 37
76
- end
94
+ def initialize(options)
95
+ super
96
+ self.name ||= 'Dennis'
97
+ self.age ||= 37
98
+ end
77
99
 
78
- end.tap { |klass| klass.searches *allowed_options }
79
- }
100
+ def search_name; end
101
+ def search_age; end
80
102
 
81
- context "and there were no values given" do
103
+ end.tap { |klass| klass.searches *allowed_options }
104
+ }
82
105
 
83
- let(:provided_options) { Hash.new }
106
+ context "and there were no values given" do
84
107
 
85
- it "uses the defaults for its accessors" do
86
- expect(search.name).to eq('Dennis')
87
- expect(search.age).to eq(37)
88
- end
108
+ let(:provided_options) { Hash.new }
89
109
 
90
- it "uses the defaults for its options hash" do
91
- expect(search.options).to eq({name: 'Dennis', age: 37})
92
- end
110
+ it "uses the defaults for its accessors" do
111
+ expect(search.name).to eq('Dennis')
112
+ expect(search.age).to eq(37)
113
+ end
93
114
 
115
+ it "uses the defaults for its options hash" do
116
+ expect(search.options).to eq({name: 'Dennis', age: 37})
94
117
  end
95
118
 
96
- context "and values are given" do
119
+ end
97
120
 
98
- let(:provided_options) { {name: 'Treebeard', age: 'A few thousand'} }
121
+ context "and values are given" do
99
122
 
100
- it "uses the provided values" do
101
- expect(search.name).to eq('Treebeard')
102
- expect(search.age).to eq('A few thousand')
103
- end
123
+ let(:provided_options) { {name: 'Treebeard', age: 'A few thousand'} }
104
124
 
105
- it "uses the provided values for its options hash" do
106
- expect(search.options).to eq({name: 'Treebeard', age: 'A few thousand'})
107
- end
125
+ it "uses the provided values" do
126
+ expect(search.name).to eq('Treebeard')
127
+ expect(search.age).to eq('A few thousand')
128
+ end
108
129
 
130
+ it "uses the provided values for its options hash" do
131
+ expect(search.options).to eq({name: 'Treebeard', age: 'A few thousand'})
109
132
  end
110
133
 
111
134
  end
@@ -345,13 +368,13 @@ describe Searchlight::Search do
345
368
  end
346
369
  }
347
370
 
348
- let(:search_instance) { search_class.new(bits: ' ', bats: nil, bots: false) }
371
+ let(:provided_options) { {bits: ' ', bats: nil, bots: false} }
349
372
 
350
373
  it "only runs search methods that have real values to search on" do
351
- search_instance.should_not_receive(:search_bits)
352
- search_instance.should_not_receive(:search_bats)
353
- search_instance.should_receive(:search_bots)
354
- search_instance.send(:run)
374
+ search.should_not_receive(:search_bits)
375
+ search.should_not_receive(:search_bats)
376
+ search.should_receive(:search_bots)
377
+ search.send(:run)
355
378
  end
356
379
 
357
380
  end
@@ -33,6 +33,10 @@ class MockRelation
33
33
  thing == ::ActiveRecord::Relation ? true : super
34
34
  end
35
35
 
36
+ def engine
37
+ MockActiveRecord
38
+ end
39
+
36
40
  end
37
41
 
38
42
  module Namespaced
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: 1.2.3
4
+ version: 1.2.4
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-06-19 00:00:00.000000000 Z
12
+ date: 2013-06-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: named