searchlight 1.2.3 → 1.2.4

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 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