mundane-search 0.0.1 → 0.0.2
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.
- data/Guardfile +11 -0
- data/README.md +61 -9
- data/Rakefile +1 -1
- data/bin/coderay +16 -0
- data/bin/erubis +16 -0
- data/bin/guard +16 -0
- data/bin/pry +16 -0
- data/bin/rackup +16 -0
- data/bin/rake +1 -1
- data/bin/sprockets +16 -0
- data/bin/thor +16 -0
- data/bin/tilt +16 -0
- data/lib/columns_hash.rb +38 -0
- data/lib/mundane-search.rb +14 -6
- data/lib/mundane-search/buildable.rb +25 -0
- data/lib/mundane-search/builder.rb +32 -18
- data/lib/mundane-search/filter_canister.rb +27 -3
- data/lib/mundane-search/filters.rb +7 -8
- data/lib/mundane-search/filters/attribute_match.rb +6 -7
- data/lib/mundane-search/filters/attribute_substring.rb +14 -0
- data/lib/mundane-search/filters/base.rb +5 -2
- data/lib/mundane-search/filters/blank_params_are_nil.rb +1 -1
- data/lib/mundane-search/filters/operator.rb +18 -0
- data/lib/mundane-search/filters/shortcuts.rb +33 -0
- data/lib/mundane-search/filters/typical.rb +28 -3
- data/lib/mundane-search/initial_stack.rb +13 -0
- data/lib/mundane-search/railtie.rb +7 -0
- data/lib/mundane-search/result.rb +23 -8
- data/lib/mundane-search/result_model.rb +65 -0
- data/lib/mundane-search/stack.rb +38 -0
- data/lib/mundane-search/version.rb +1 -1
- data/lib/mundane-search/view_helpers.rb +24 -0
- data/mundane-search.gemspec +7 -0
- data/script/console +6 -0
- data/spec/active_record_setup.rb +2 -45
- data/spec/buildable_integration_spec.rb +14 -0
- data/spec/buildable_spec.rb +19 -0
- data/spec/builder_integration_spec.rb +26 -5
- data/spec/builder_spec.rb +13 -18
- data/spec/columns_hash_spec.rb +37 -0
- data/spec/demo_data.rb +50 -0
- data/spec/filter_canister_spec.rb +46 -0
- data/spec/filters/attribute_match_integration_spec.rb +2 -2
- data/spec/filters/attribute_match_spec.rb +27 -0
- data/spec/filters/attribute_substring_spec.rb +27 -0
- data/spec/filters/base_spec.rb +39 -7
- data/spec/filters/blank_params_are_nil_spec.rb +11 -0
- data/spec/filters/operator_integration_spec.rb +20 -0
- data/spec/filters/operator_spec.rb +28 -0
- data/spec/filters/shortcuts_integration_spec.rb +16 -0
- data/spec/filters/shortcuts_spec.rb +15 -0
- data/spec/filters/typical_spec.rb +68 -0
- data/spec/form_integration_spec.rb +29 -0
- data/spec/initial_stack_spec.rb +13 -0
- data/spec/minitest_helper.rb +93 -4
- data/spec/result_integration_spec.rb +24 -0
- data/spec/result_model_spec.rb +50 -0
- data/spec/result_spec.rb +10 -28
- data/spec/search_form_for_integration_spec.rb +19 -0
- data/spec/simple_form_integration_spec.rb +36 -0
- data/spec/simple_search_form_for_integration_spec.rb +25 -0
- data/spec/stack_spec.rb +40 -0
- metadata +167 -6
- data/lib/mundane-search/filters/helpers.rb +0 -44
- data/lib/mundane-search/initial_result.rb +0 -7
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'minitest_helper'
|
2
|
+
|
3
|
+
describe MundaneSearch::Buildable do
|
4
|
+
let(:built) do
|
5
|
+
Class.new do
|
6
|
+
include MundaneSearch::Buildable
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should limit results using exact match filter" do
|
11
|
+
built.use MundaneSearch::Filters::ExactMatch, param_key: "foo"
|
12
|
+
built.call(collection, params).must_equal(['bar'])
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'minitest_helper'
|
2
|
+
|
3
|
+
describe MundaneSearch::Buildable do
|
4
|
+
let(:built) do
|
5
|
+
Class.new do
|
6
|
+
include MundaneSearch::Buildable
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have a search builder object" do
|
11
|
+
built.builder.must_be_kind_of MundaneSearch::Builder
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should add proxy methods" do
|
15
|
+
[:use, :result_for, :call, :employ].each do |method|
|
16
|
+
built.must_respond_to method
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,14 +1,35 @@
|
|
1
1
|
require_relative 'minitest_helper'
|
2
2
|
|
3
3
|
describe MundaneSearch::Builder do
|
4
|
-
|
4
|
+
let(:built) { MundaneSearch::Builder.new }
|
5
|
+
|
6
|
+
it "should return unchanged collection on call" do
|
7
|
+
built.call(collection, params).must_equal(collection)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should tollerate nil params (convert to empty hash)" do
|
11
|
+
built.call(collection, nil).must_equal(collection)
|
12
|
+
end
|
5
13
|
|
6
14
|
it "should limit results using exact match filter" do
|
15
|
+
built.use MundaneSearch::Filters::ExactMatch, param_key: "foo"
|
16
|
+
built.call(collection, params).must_equal(['bar'])
|
17
|
+
end
|
7
18
|
|
8
|
-
|
9
|
-
|
10
|
-
|
19
|
+
it "should translate symbols into MundaneSearch::Filter class (if available)" do
|
20
|
+
built.use :exact_match, param_key: "foo"
|
21
|
+
built.call(collection, params).must_equal(['bar'])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should translate strings into MundaneSearch::Filter class (if available)" do
|
25
|
+
built.use "exact_match", param_key: "foo"
|
26
|
+
built.call(collection, params).must_equal(['bar'])
|
27
|
+
end
|
11
28
|
|
29
|
+
it "should looks for Object:: level filters when translating strings" do
|
30
|
+
Object.const_set :MockFilter, Class.new(MundaneSearch::Filters::ExactMatch)
|
31
|
+
built.use :mock_filter, param_key: "foo"
|
12
32
|
built.call(collection, params).must_equal(['bar'])
|
33
|
+
Object.send(:remove_const, :MockFilter)
|
13
34
|
end
|
14
|
-
end
|
35
|
+
end
|
data/spec/builder_spec.rb
CHANGED
@@ -1,25 +1,20 @@
|
|
1
1
|
require_relative 'minitest_helper'
|
2
2
|
|
3
3
|
describe MundaneSearch::Builder do
|
4
|
-
|
4
|
+
let(:built) { MundaneSearch::Builder.new }
|
5
|
+
let(:null_filter) { Object.new }
|
6
|
+
let(:canister) { Minitest::Mock.new }
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
it "should return unchanged collection on call" do
|
13
|
-
builder.call(collection, params).must_equal(collection)
|
14
|
-
end
|
15
|
-
end
|
8
|
+
# what does a builder actually do?
|
9
|
+
# 1. Store search configuration (use, result_class, filter_canister)
|
10
|
+
# a. Can "use" (et al) be called?
|
11
|
+
# i. use can be verified
|
12
|
+
# 2. Run search (call, result_for)
|
13
|
+
# a. ... by creating a result object
|
16
14
|
|
17
15
|
it "should use middleware" do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
canister = builder.filters.first
|
22
|
-
canister.must_be_kind_of MundaneSearch::FilterCanister
|
23
|
-
canister.filter.must_equal NothingFilterForTest
|
16
|
+
expectation = ->(filter, *args) { filter.must_equal null_filter }
|
17
|
+
built.define_singleton_method :build_filter_canister, ->{ expectation }
|
18
|
+
built.use(null_filter)
|
24
19
|
end
|
25
|
-
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'minitest_helper'
|
2
|
+
|
3
|
+
describe ColumnsHash do
|
4
|
+
[:binary, :boolean, :date, :datetime, :decimal, :float, :integer, :primary_key, :string, :text, :time, :timestamp].each do |type|
|
5
|
+
it "should respond to all ActiveRecord type #{type}" do
|
6
|
+
ColumnsHash.generate({type: type}).must_be_kind_of ColumnsHash::Attribute
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have 255 limit when string" do
|
11
|
+
ColumnsHash.generate({type: :string}).limit.must_equal 255
|
12
|
+
end
|
13
|
+
|
14
|
+
[:float, :integer].each do |type|
|
15
|
+
it "should be number? true when #{type}" do
|
16
|
+
ColumnsHash.generate({type: type}).number?.must_equal true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "applied to class" do
|
21
|
+
let(:columned_class) do
|
22
|
+
Class.new do
|
23
|
+
include ColumnsHash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
let(:columned) { columned_class.new }
|
27
|
+
|
28
|
+
it "should have a columns_hash" do
|
29
|
+
columned_class.columns_hash.must_be_kind_of Hash
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should store (and retrieve) a column type" do
|
33
|
+
columned_class.attribute_column(:title, :string)
|
34
|
+
columned.column_for_attribute(:title).type.must_equal :string
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/demo_data.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
def demo_data
|
2
|
+
[
|
3
|
+
{
|
4
|
+
title: "A Tale of Two Cities",
|
5
|
+
author: "Charles Dickens",
|
6
|
+
publication_date: Date.parse('31-12-1859'),
|
7
|
+
first_purchased_at: Time.utc(1859,"jan",1,20,15,1),
|
8
|
+
sold: 200_000_000
|
9
|
+
},
|
10
|
+
{
|
11
|
+
title: "The Lord of the Rings",
|
12
|
+
author: "J. R. R. Tolkien",
|
13
|
+
publication_date: Date.parse('31-12-1954'),
|
14
|
+
first_purchased_at: Time.utc(1954,"jan",1,20,15,1),
|
15
|
+
sold: 150_000_000
|
16
|
+
},
|
17
|
+
{
|
18
|
+
title: "The Little Prince (Le Petit Prince)",
|
19
|
+
author: "Antoine de Saint-Exupery",
|
20
|
+
publication_date: Date.parse('31-12-1943'),
|
21
|
+
first_purchased_at: Time.utc(1943,"jan",1,20,15,1),
|
22
|
+
sold: 140_000_000
|
23
|
+
},
|
24
|
+
{
|
25
|
+
title: "The Hobbit",
|
26
|
+
author: "J. R. R. Tolkien",
|
27
|
+
publication_date: Date.parse('31-12-1937'),
|
28
|
+
first_purchased_at: Time.utc(1937,"jan",1,20,15,1),
|
29
|
+
sold: 100_000_000
|
30
|
+
},
|
31
|
+
{
|
32
|
+
title: "Hong lou meng (Dream of the Red Chamber/The Story of the Stone)",
|
33
|
+
author: "Cao Xueqin",
|
34
|
+
publication_date: Date.parse('31-12-1754'),
|
35
|
+
first_purchased_at: Time.utc(1754,"jan",1,20,15,1),
|
36
|
+
sold: 100_000_000
|
37
|
+
},
|
38
|
+
{
|
39
|
+
title: "And Then There Were None",
|
40
|
+
author: "Agatha Christie",
|
41
|
+
publication_date: Date.parse('31-12-1939'),
|
42
|
+
first_purchased_at: Time.utc(1939,"jan",1,20,15,1),
|
43
|
+
sold: 100_000_000
|
44
|
+
}
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
def open_struct_books
|
49
|
+
demo_data.collect {|d| OpenStruct.new(d) }
|
50
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'minitest_helper'
|
2
|
+
|
3
|
+
describe MundaneSearch::FilterCanister do
|
4
|
+
it "should build filters with options" do
|
5
|
+
filter = Minitest::Mock.new
|
6
|
+
options = {param_key: 'baz'}
|
7
|
+
result = Object.new
|
8
|
+
filter.expect(:constants, [])
|
9
|
+
filter.expect(:new, result, [collection, params, options])
|
10
|
+
filter_canister = MundaneSearch::FilterCanister.new(filter, options)
|
11
|
+
filter_canister.build(collection, params).must_equal(result)
|
12
|
+
filter.verify
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should use subconstant based on class of collection" do
|
16
|
+
object_filter = Minitest::Mock.new
|
17
|
+
collection, result, options = Object.new, Object.new, Object.new
|
18
|
+
object_filter.expect(:new, result, [collection, params, options])
|
19
|
+
|
20
|
+
base_filter = Class.new
|
21
|
+
base_filter::Object = object_filter
|
22
|
+
|
23
|
+
filter_canister = MundaneSearch::FilterCanister.new(base_filter, options)
|
24
|
+
filter_canister.build(collection, params).must_equal(result)
|
25
|
+
object_filter.verify
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should report param_key" do
|
29
|
+
param_key = Object.new
|
30
|
+
filter_canister = MundaneSearch::FilterCanister.new(nil, param_key: param_key)
|
31
|
+
filter_canister.param_key.must_equal param_key
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should report param_key_type" do
|
35
|
+
param_key_type = Object.new
|
36
|
+
filter_canister = MundaneSearch::FilterCanister.new(nil, param_key_type: param_key_type)
|
37
|
+
filter_canister.param_key_type.must_equal param_key_type
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should take param_key_type from filter, if not supplied in options" do
|
41
|
+
filter = Minitest::Mock.new
|
42
|
+
filter.expect(:param_key_type, :date)
|
43
|
+
filter_canister = MundaneSearch::FilterCanister.new(filter)
|
44
|
+
filter_canister.param_key_type.must_equal :date
|
45
|
+
end
|
46
|
+
end
|
@@ -10,8 +10,8 @@ describe MundaneSearch::Filters::AttributeMatch do
|
|
10
10
|
let(:all_books) { Book.scoped }
|
11
11
|
let(:a_tale_of_two_cities) { Book.first }
|
12
12
|
|
13
|
-
it "should
|
14
|
-
built = Builder.new do
|
13
|
+
it "should match based on param_key" do
|
14
|
+
built = MundaneSearch::Builder.new do
|
15
15
|
use MundaneSearch::Filters::AttributeMatch, param_key: "author"
|
16
16
|
end
|
17
17
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../minitest_helper'
|
2
|
+
require_relative '../demo_data'
|
3
|
+
|
4
|
+
describe MundaneSearch::Filters::AttributeMatch do
|
5
|
+
let(:books) { open_struct_books }
|
6
|
+
let(:a_tale) { books.first }
|
7
|
+
it "should match param key" do
|
8
|
+
am = MundaneSearch::Filters::AttributeMatch
|
9
|
+
filter = am.new(books, {'title' => "A Tale of Two Cities"}, {param_key: 'title'})
|
10
|
+
|
11
|
+
filter.filtered_collection.must_equal([a_tale])
|
12
|
+
end
|
13
|
+
|
14
|
+
describe MundaneSearch::Filters::AttributeMatch::ActiveRecord do
|
15
|
+
let(:am) { MundaneSearch::Filters::AttributeMatch::ActiveRecord }
|
16
|
+
it "should filter with 'where'" do
|
17
|
+
collection = Minitest::Mock.new
|
18
|
+
params = { 'title' => "A Tale of Two Cities" }
|
19
|
+
result = Object.new
|
20
|
+
|
21
|
+
filter = am.new(collection, params, param_key: 'title')
|
22
|
+
collection.expect(:where, result, [params])
|
23
|
+
filter.filtered_collection.must_equal(result)
|
24
|
+
collection.verify
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../minitest_helper'
|
2
|
+
require_relative '../demo_data'
|
3
|
+
|
4
|
+
describe MundaneSearch::Filters::AttributeSubstring do
|
5
|
+
let(:books) { open_struct_books }
|
6
|
+
let(:a_tale) { books.first }
|
7
|
+
it "should match param key" do
|
8
|
+
am = MundaneSearch::Filters::AttributeSubstring
|
9
|
+
filter = am.new(books, {'title' => "Tale of Two"}, {param_key: 'title'})
|
10
|
+
|
11
|
+
filter.filtered_collection.must_equal([a_tale])
|
12
|
+
end
|
13
|
+
|
14
|
+
describe MundaneSearch::Filters::AttributeSubstring::ActiveRecord do
|
15
|
+
let(:am) { MundaneSearch::Filters::AttributeSubstring::ActiveRecord }
|
16
|
+
it "should filter with 'where'" do
|
17
|
+
collection = Minitest::Mock.new
|
18
|
+
params = { 'title' => "Tale of Two" }
|
19
|
+
result = Object.new
|
20
|
+
|
21
|
+
filter = am.new(collection, params, param_key: 'title')
|
22
|
+
collection.expect(:where, result, [["title LIKE ?", "%Tale of Two%"]])
|
23
|
+
filter.filtered_collection.must_equal(result)
|
24
|
+
collection.verify
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/spec/filters/base_spec.rb
CHANGED
@@ -1,13 +1,45 @@
|
|
1
1
|
require_relative '../minitest_helper'
|
2
2
|
|
3
3
|
describe MundaneSearch::Filters::Base do
|
4
|
-
|
4
|
+
let(:base) { MundaneSearch::Filters::Base.new(collection, params) }
|
5
|
+
let(:standin) { Object.new }
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
describe "#call" do
|
8
|
+
it "should equal [filtered_collection, filtered_params]" do
|
9
|
+
collection, params = Object.new, Object.new
|
10
|
+
base.expects(:filtered_collection).returns(collection)
|
11
|
+
base.expects(:filtered_params).returns(params)
|
12
|
+
base.call.must_equal [collection, params]
|
13
|
+
end
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
|
15
|
+
it "should equal [collection, params] when apply? is false" do
|
16
|
+
collection, params = Object.new, Object.new
|
17
|
+
def base.filtered_params ; end
|
18
|
+
def base.filtered_collection ; end
|
19
|
+
base.expects(:apply?).returns(false)
|
20
|
+
base.expects(:collection).returns(collection)
|
21
|
+
base.expects(:params).returns(params)
|
22
|
+
base.call.must_equal [collection, params]
|
23
|
+
end
|
12
24
|
end
|
13
|
-
|
25
|
+
|
26
|
+
describe "#apply?" do
|
27
|
+
it "should be true" do
|
28
|
+
assert base.apply?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#filtered_collection" do
|
33
|
+
it "should contain collection" do
|
34
|
+
base.expects(:collection).returns(standin)
|
35
|
+
base.filtered_collection.must_equal standin
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#filtered_params" do
|
40
|
+
it "should contain params" do
|
41
|
+
base.expects(:params).returns(standin)
|
42
|
+
base.filtered_params.must_equal standin
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative '../minitest_helper'
|
2
|
+
|
3
|
+
describe MundaneSearch::Filters::BlankParamsAreNil do
|
4
|
+
it "should remove empty values from params" do
|
5
|
+
params = { empty_string: '', empty_array: [], empty_hash: {} }
|
6
|
+
bpan = MundaneSearch::Filters::BlankParamsAreNil.new([], params)
|
7
|
+
bpan.filtered_params[:empty_string].must_be_nil
|
8
|
+
bpan.filtered_params[:empty_array].must_be_nil
|
9
|
+
bpan.filtered_params[:empty_hash].must_be_nil
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative '../minitest_helper'
|
2
|
+
require_relative '../active_record_setup'
|
3
|
+
|
4
|
+
describe MundaneSearch::Filters::Operator do
|
5
|
+
before do
|
6
|
+
DatabaseCleaner.clean
|
7
|
+
populate_books!
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:all_books) { Book.scoped }
|
11
|
+
let(:a_tale_of_two_cities) { Book.first }
|
12
|
+
|
13
|
+
it "should match based on param_key" do
|
14
|
+
built = MundaneSearch::Builder.new do
|
15
|
+
use MundaneSearch::Filters::Operator, { param_key: 'sold', operator: :> }
|
16
|
+
end
|
17
|
+
|
18
|
+
built.call(all_books, {"sold" => 199_999_999}).must_equal([a_tale_of_two_cities])
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../minitest_helper'
|
2
|
+
require_relative '../demo_data'
|
3
|
+
|
4
|
+
describe MundaneSearch::Filters::Operator do
|
5
|
+
let(:books) { open_struct_books }
|
6
|
+
let(:a_tale) { books.first }
|
7
|
+
|
8
|
+
it "should match param key" do
|
9
|
+
o = MundaneSearch::Filters::Operator
|
10
|
+
filter = o.new(books, {'sold' => 199_999_999}, { param_key: 'sold', operator: :> })
|
11
|
+
|
12
|
+
filter.filtered_collection.must_equal([a_tale])
|
13
|
+
end
|
14
|
+
|
15
|
+
describe MundaneSearch::Filters::Operator::ActiveRecord do
|
16
|
+
let(:o) { MundaneSearch::Filters::Operator::ActiveRecord }
|
17
|
+
it "should filter with 'where'" do
|
18
|
+
collection = Minitest::Mock.new
|
19
|
+
params = { 'sold' => 199_999_999 }
|
20
|
+
result = Object.new
|
21
|
+
|
22
|
+
filter = o.new(collection, params, { param_key: 'sold', operator: :> })
|
23
|
+
collection.expect(:where, result, [["sold > ?", 199_999_999]])
|
24
|
+
filter.filtered_collection.must_equal(result)
|
25
|
+
collection.verify
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../minitest_helper'
|
2
|
+
require_relative '../demo_data'
|
3
|
+
|
4
|
+
describe MundaneSearch::Filters::Shortcuts do
|
5
|
+
|
6
|
+
let(:all_books) { open_struct_books }
|
7
|
+
let(:a_tale_of_two_cities) { all_books.first }
|
8
|
+
|
9
|
+
it "should match based on param_key" do
|
10
|
+
built = MundaneSearch::Builder.new do
|
11
|
+
employ :attribute_filter, param_key: 'title'
|
12
|
+
end
|
13
|
+
|
14
|
+
built.call(all_books, {"title" => "A Tale of Two Cities"}).must_equal([a_tale_of_two_cities])
|
15
|
+
end
|
16
|
+
end
|