tire 0.1.0
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/.gitignore +9 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +435 -0
- data/Rakefile +75 -0
- data/examples/dsl.rb +73 -0
- data/examples/rails-application-template.rb +144 -0
- data/examples/tire-dsl.rb +617 -0
- data/lib/tire.rb +35 -0
- data/lib/tire/client.rb +40 -0
- data/lib/tire/configuration.rb +29 -0
- data/lib/tire/dsl.rb +33 -0
- data/lib/tire/index.rb +209 -0
- data/lib/tire/logger.rb +60 -0
- data/lib/tire/model/callbacks.rb +23 -0
- data/lib/tire/model/import.rb +18 -0
- data/lib/tire/model/indexing.rb +50 -0
- data/lib/tire/model/naming.rb +30 -0
- data/lib/tire/model/persistence.rb +34 -0
- data/lib/tire/model/persistence/attributes.rb +60 -0
- data/lib/tire/model/persistence/finders.rb +61 -0
- data/lib/tire/model/persistence/storage.rb +75 -0
- data/lib/tire/model/search.rb +97 -0
- data/lib/tire/results/collection.rb +56 -0
- data/lib/tire/results/item.rb +39 -0
- data/lib/tire/results/pagination.rb +30 -0
- data/lib/tire/rubyext/hash.rb +3 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +117 -0
- data/lib/tire/search/facet.rb +41 -0
- data/lib/tire/search/filter.rb +28 -0
- data/lib/tire/search/highlight.rb +37 -0
- data/lib/tire/search/query.rb +42 -0
- data/lib/tire/search/sort.rb +29 -0
- data/lib/tire/tasks.rb +88 -0
- data/lib/tire/version.rb +3 -0
- data/test/fixtures/articles/1.json +1 -0
- data/test/fixtures/articles/2.json +1 -0
- data/test/fixtures/articles/3.json +1 -0
- data/test/fixtures/articles/4.json +1 -0
- data/test/fixtures/articles/5.json +1 -0
- data/test/integration/active_model_searchable_test.rb +80 -0
- data/test/integration/active_record_searchable_test.rb +193 -0
- data/test/integration/facets_test.rb +65 -0
- data/test/integration/filters_test.rb +46 -0
- data/test/integration/highlight_test.rb +52 -0
- data/test/integration/index_mapping_test.rb +44 -0
- data/test/integration/index_store_test.rb +68 -0
- data/test/integration/persistent_model_test.rb +35 -0
- data/test/integration/query_string_test.rb +43 -0
- data/test/integration/results_test.rb +28 -0
- data/test/integration/sort_test.rb +36 -0
- data/test/models/active_model_article.rb +31 -0
- data/test/models/active_model_article_with_callbacks.rb +49 -0
- data/test/models/active_model_article_with_custom_index_name.rb +5 -0
- data/test/models/active_record_article.rb +12 -0
- data/test/models/article.rb +15 -0
- data/test/models/persistent_article.rb +11 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
- data/test/models/supermodel_article.rb +22 -0
- data/test/models/validated_model.rb +11 -0
- data/test/test_helper.rb +52 -0
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/client_test.rb +43 -0
- data/test/unit/configuration_test.rb +71 -0
- data/test/unit/index_test.rb +390 -0
- data/test/unit/logger_test.rb +114 -0
- data/test/unit/model_callbacks_test.rb +90 -0
- data/test/unit/model_import_test.rb +71 -0
- data/test/unit/model_persistence_test.rb +400 -0
- data/test/unit/model_search_test.rb +289 -0
- data/test/unit/results_collection_test.rb +131 -0
- data/test/unit/results_item_test.rb +59 -0
- data/test/unit/rubyext_hash_test.rb +19 -0
- data/test/unit/search_facet_test.rb +69 -0
- data/test/unit/search_filter_test.rb +36 -0
- data/test/unit/search_highlight_test.rb +46 -0
- data/test/unit/search_query_test.rb +55 -0
- data/test/unit/search_sort_test.rb +50 -0
- data/test/unit/search_test.rb +204 -0
- data/test/unit/tire_test.rb +55 -0
- data/tire.gemspec +54 -0
- metadata +372 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
module Model
|
5
|
+
|
6
|
+
class SearchTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
context "Model::Search" do
|
9
|
+
|
10
|
+
setup do
|
11
|
+
@stub = stub('search') { stubs(:query).returns(self); stubs(:perform).returns(self); stubs(:results).returns([]) }
|
12
|
+
end
|
13
|
+
|
14
|
+
teardown do
|
15
|
+
ActiveModelArticleWithCustomIndexName.index_name 'custom-index-name'
|
16
|
+
end
|
17
|
+
|
18
|
+
should "have the search method" do
|
19
|
+
assert_respond_to Model::Search, :search
|
20
|
+
assert_respond_to ActiveModelArticle, :search
|
21
|
+
end
|
22
|
+
|
23
|
+
should "search in index named after class name by default" do
|
24
|
+
i = 'active_model_articles'
|
25
|
+
Tire::Search::Search.expects(:new).with(i, {}).returns(@stub)
|
26
|
+
|
27
|
+
ActiveModelArticle.search 'foo'
|
28
|
+
end
|
29
|
+
|
30
|
+
should "search in custom name" do
|
31
|
+
first = 'custom-index-name'
|
32
|
+
second = 'another-custom-index-name'
|
33
|
+
|
34
|
+
Tire::Search::Search.expects(:new).with(first, {}).returns(@stub)
|
35
|
+
ActiveModelArticleWithCustomIndexName.index_name 'custom-index-name'
|
36
|
+
ActiveModelArticleWithCustomIndexName.search 'foo'
|
37
|
+
|
38
|
+
Tire::Search::Search.expects(:new).with(second, {}).returns(@stub)
|
39
|
+
ActiveModelArticleWithCustomIndexName.index_name 'another-custom-index-name'
|
40
|
+
ActiveModelArticleWithCustomIndexName.search 'foo'
|
41
|
+
|
42
|
+
Tire::Search::Search.expects(:new).with(first, {}).returns(@stub)
|
43
|
+
ActiveModelArticleWithCustomIndexName.index_name 'custom-index-name'
|
44
|
+
ActiveModelArticleWithCustomIndexName.search 'foo'
|
45
|
+
end
|
46
|
+
|
47
|
+
should "allow to refresh index" do
|
48
|
+
Index.any_instance.expects(:refresh)
|
49
|
+
|
50
|
+
ActiveModelArticle.index.refresh
|
51
|
+
end
|
52
|
+
|
53
|
+
should "wrap results in proper class with ID and score and not change the original wrapper" do
|
54
|
+
response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 0.8, '_source' => { 'title' => 'Article' }}] } }
|
55
|
+
Configuration.client.expects(:post).returns(mock_response(response.to_json))
|
56
|
+
|
57
|
+
collection = ActiveModelArticle.search 'foo'
|
58
|
+
assert_instance_of Results::Collection, collection
|
59
|
+
|
60
|
+
assert_equal Results::Item, Tire::Configuration.wrapper
|
61
|
+
|
62
|
+
document = collection.first
|
63
|
+
|
64
|
+
assert_instance_of ActiveModelArticle, document
|
65
|
+
assert_not_nil document.score
|
66
|
+
assert_equal 1, document.id
|
67
|
+
assert_equal 'Article', document.title
|
68
|
+
end
|
69
|
+
|
70
|
+
context "searching with a block" do
|
71
|
+
|
72
|
+
should "pass on whatever block it received" do
|
73
|
+
Tire::Search::Search.any_instance.expects(:perform).returns(@stub)
|
74
|
+
Tire::Search::Query.any_instance.expects(:string).with('foo').returns(@stub)
|
75
|
+
|
76
|
+
ActiveModelArticle.search { query { string 'foo' } }
|
77
|
+
end
|
78
|
+
|
79
|
+
should "allow to pass block with argument to query, allowing to use local variables from outer scope" do
|
80
|
+
Tire::Search::Query.any_instance.expects(:instance_eval).never
|
81
|
+
Tire::Search::Search.any_instance.expects(:perform).returns(@stub)
|
82
|
+
Tire::Search::Query.any_instance.expects(:string).with('foo').returns(@stub)
|
83
|
+
|
84
|
+
my_query = 'foo'
|
85
|
+
ActiveModelArticle.search do
|
86
|
+
query do |query|
|
87
|
+
query.string(my_query)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
context "searching with query string" do
|
95
|
+
|
96
|
+
setup do
|
97
|
+
@q = 'foo AND bar'
|
98
|
+
|
99
|
+
Tire::Search::Query.any_instance.expects(:string).with( @q ).returns(@stub)
|
100
|
+
Tire::Search::Search.any_instance.expects(:perform).returns(@stub)
|
101
|
+
end
|
102
|
+
|
103
|
+
should "search for query string" do
|
104
|
+
ActiveModelArticle.search @q
|
105
|
+
end
|
106
|
+
|
107
|
+
should "allow to pass :order option" do
|
108
|
+
Tire::Search::Sort.any_instance.expects(:title)
|
109
|
+
|
110
|
+
ActiveModelArticle.search @q, :order => 'title'
|
111
|
+
end
|
112
|
+
|
113
|
+
should "allow to pass :sort option as :order option" do
|
114
|
+
Tire::Search::Sort.any_instance.expects(:title)
|
115
|
+
|
116
|
+
ActiveModelArticle.search @q, :sort => 'title'
|
117
|
+
end
|
118
|
+
|
119
|
+
should "allow to specify sort direction" do
|
120
|
+
Tire::Search::Sort.any_instance.expects(:title).with('DESC')
|
121
|
+
|
122
|
+
ActiveModelArticle.search @q, :order => 'title DESC'
|
123
|
+
end
|
124
|
+
|
125
|
+
should "allow to specify more fields to sort on" do
|
126
|
+
Tire::Search::Sort.any_instance.expects(:title).with('DESC')
|
127
|
+
Tire::Search::Sort.any_instance.expects(:field).with('author.name', nil)
|
128
|
+
|
129
|
+
ActiveModelArticle.search @q, :order => ['title DESC', 'author.name']
|
130
|
+
end
|
131
|
+
|
132
|
+
should "allow to specify number of results per page" do
|
133
|
+
Tire::Search::Search.any_instance.expects(:size).with(20)
|
134
|
+
|
135
|
+
ActiveModelArticle.search @q, :per_page => 20
|
136
|
+
end
|
137
|
+
|
138
|
+
should "allow to specify first page in paginated results" do
|
139
|
+
Tire::Search::Search.any_instance.expects(:size).with(10)
|
140
|
+
Tire::Search::Search.any_instance.expects(:from).with(0)
|
141
|
+
|
142
|
+
ActiveModelArticle.search @q, :per_page => 10, :page => 1
|
143
|
+
end
|
144
|
+
|
145
|
+
should "allow to specify page further in paginated results" do
|
146
|
+
Tire::Search::Search.any_instance.expects(:size).with(10)
|
147
|
+
Tire::Search::Search.any_instance.expects(:from).with(20)
|
148
|
+
|
149
|
+
ActiveModelArticle.search @q, :per_page => 10, :page => 3
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
should "not set callback when hooks are missing" do
|
155
|
+
@model = ActiveModelArticle.new
|
156
|
+
@model.expects(:update_elastic_search_index).never
|
157
|
+
|
158
|
+
@model.save
|
159
|
+
end
|
160
|
+
|
161
|
+
should "fire :after_save callbacks" do
|
162
|
+
@model = ActiveModelArticleWithCallbacks.new
|
163
|
+
@model.expects(:update_elastic_search_index)
|
164
|
+
|
165
|
+
@model.save
|
166
|
+
end
|
167
|
+
|
168
|
+
should "fire :after_destroy callbacks" do
|
169
|
+
@model = ActiveModelArticleWithCallbacks.new
|
170
|
+
@model.expects(:update_elastic_search_index)
|
171
|
+
|
172
|
+
@model.destroy
|
173
|
+
end
|
174
|
+
|
175
|
+
should "store the record in index on :update_elastic_search_index when saved" do
|
176
|
+
@model = ActiveModelArticleWithCallbacks.new
|
177
|
+
Tire::Index.any_instance.expects(:store)
|
178
|
+
|
179
|
+
@model.save
|
180
|
+
end
|
181
|
+
|
182
|
+
should "remove the record from index on :update_elastic_search_index when destroyed" do
|
183
|
+
@model = ActiveModelArticleWithCallbacks.new
|
184
|
+
i = mock('index') { expects(:remove) }
|
185
|
+
Tire::Index.expects(:new).with('active_model_article_with_callbacks').returns(i)
|
186
|
+
|
187
|
+
@model.destroy
|
188
|
+
end
|
189
|
+
|
190
|
+
context "with custom mapping" do
|
191
|
+
|
192
|
+
should "create the index with mapping" do
|
193
|
+
expected_mapping = {
|
194
|
+
:mappings => { :model_with_custom_mapping => {
|
195
|
+
:properties => { :title => { :type => 'string', :analyzer => 'snowball', :boost => 10 } }
|
196
|
+
}}
|
197
|
+
}
|
198
|
+
|
199
|
+
Tire::Index.any_instance.expects(:create).with(expected_mapping)
|
200
|
+
|
201
|
+
class ::ModelWithCustomMapping
|
202
|
+
extend ActiveModel::Naming
|
203
|
+
|
204
|
+
include Tire::Model::Search
|
205
|
+
include Tire::Model::Callbacks
|
206
|
+
|
207
|
+
mapping do
|
208
|
+
indexes :title, :type => 'string', :analyzer => 'snowball', :boost => 10
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
assert_equal 'snowball', ModelWithCustomMapping.mapping[:title][:analyzer]
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
context "serialization" do
|
219
|
+
setup { Tire::Index.any_instance.stubs(:create).returns(true) }
|
220
|
+
|
221
|
+
should "serialize itself into JSON without 'root'" do
|
222
|
+
@model = ActiveModelArticle.new 'title' => 'Test'
|
223
|
+
assert_equal({'title' => 'Test'}.to_json, @model.to_indexed_json)
|
224
|
+
end
|
225
|
+
|
226
|
+
should "serialize itself with serializable_hash when no mapping is set" do
|
227
|
+
|
228
|
+
class ::ModelWithoutMapping
|
229
|
+
extend ActiveModel::Naming
|
230
|
+
include ActiveModel::Serialization
|
231
|
+
include Tire::Model::Search
|
232
|
+
include Tire::Model::Callbacks
|
233
|
+
|
234
|
+
# Do NOT configure any mapping
|
235
|
+
|
236
|
+
attr_reader :attributes
|
237
|
+
|
238
|
+
def initialize(attributes = {}); @attributes = attributes; end
|
239
|
+
|
240
|
+
def method_missing(name, *args, &block)
|
241
|
+
attributes[name.to_sym] || attributes[name.to_s] || super
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
model = ::ModelWithoutMapping.new :one => 1, :two => 2
|
246
|
+
assert_equal( {:one => 1, :two => 2}, model.serializable_hash )
|
247
|
+
|
248
|
+
# Bot properties are returned
|
249
|
+
assert_equal( {:one => 1, :two => 2}.to_json, model.to_indexed_json )
|
250
|
+
end
|
251
|
+
|
252
|
+
should "serialize only mapped properties when mapping is set" do
|
253
|
+
|
254
|
+
class ::ModelWithMapping
|
255
|
+
extend ActiveModel::Naming
|
256
|
+
include ActiveModel::Serialization
|
257
|
+
include Tire::Model::Search
|
258
|
+
include Tire::Model::Callbacks
|
259
|
+
|
260
|
+
mapping do
|
261
|
+
# ONLY index the 'one' attribute
|
262
|
+
indexes :one, :type => 'string', :analyzer => 'keyword'
|
263
|
+
end
|
264
|
+
|
265
|
+
attr_reader :attributes
|
266
|
+
|
267
|
+
def initialize(attributes = {}); @attributes = attributes; end
|
268
|
+
|
269
|
+
def method_missing(name, *args, &block)
|
270
|
+
attributes[name.to_sym] || attributes[name.to_s] || super
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
model = ::ModelWithMapping.new :one => 1, :two => 2
|
275
|
+
assert_equal( {:one => 1, :two => 2}, model.serializable_hash )
|
276
|
+
|
277
|
+
# Only the mapped property is returned
|
278
|
+
assert_equal( {:one => 1}.to_json, model.to_indexed_json )
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class ResultsCollectionTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "Collection" do
|
8
|
+
setup do
|
9
|
+
Configuration.reset :wrapper
|
10
|
+
@default_response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 1, '_source' => {:title => 'Test'}},
|
11
|
+
{'_id' => 2},
|
12
|
+
{'_id' => 3}] } }
|
13
|
+
end
|
14
|
+
|
15
|
+
should "be iterable" do
|
16
|
+
assert_respond_to Results::Collection.new(@default_response), :each
|
17
|
+
assert_respond_to Results::Collection.new(@default_response), :size
|
18
|
+
assert_nothing_raised do
|
19
|
+
Results::Collection.new(@default_response).each { |item| item.id + 1 }
|
20
|
+
Results::Collection.new(@default_response).map { |item| item.id + 1 }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
should "have size" do
|
25
|
+
assert_equal 3, Results::Collection.new(@default_response).size
|
26
|
+
end
|
27
|
+
|
28
|
+
should "be initialized with parsed json" do
|
29
|
+
assert_nothing_raised do
|
30
|
+
collection = Results::Collection.new( @default_response )
|
31
|
+
assert_equal 3, collection.results.count
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
should "store passed options" do
|
36
|
+
collection = Results::Collection.new( @default_response, :per_page => 20, :page => 2 )
|
37
|
+
assert_equal 20, collection.options[:per_page]
|
38
|
+
assert_equal 2, collection.options[:page]
|
39
|
+
end
|
40
|
+
|
41
|
+
context "wrapping results" do
|
42
|
+
|
43
|
+
setup do
|
44
|
+
@response = { 'hits' => { 'hits' => [ { '_id' => 1, '_score' => 0.5, '_source' => { :title => 'Test', :body => 'Lorem' } } ] } }
|
45
|
+
end
|
46
|
+
|
47
|
+
should "wrap hits in Item by default" do
|
48
|
+
document = Results::Collection.new(@response).first
|
49
|
+
assert_kind_of Results::Item, document
|
50
|
+
assert_equal 'Test', document.title
|
51
|
+
end
|
52
|
+
|
53
|
+
should "NOT allow access to raw underlying Hash in Item" do
|
54
|
+
document = Results::Collection.new(@response).first
|
55
|
+
assert_nil document[:_source]
|
56
|
+
assert_nil document['_source']
|
57
|
+
end
|
58
|
+
|
59
|
+
should "allow wrapping hits in a Hash" do
|
60
|
+
Configuration.wrapper(Hash)
|
61
|
+
|
62
|
+
document = Results::Collection.new(@response).first
|
63
|
+
assert_kind_of Hash, document
|
64
|
+
assert_raise(NoMethodError) { document.title }
|
65
|
+
assert_equal 'Test', document['_source'][:title]
|
66
|
+
end
|
67
|
+
|
68
|
+
should "allow wrapping hits in custom class" do
|
69
|
+
Configuration.wrapper(Article)
|
70
|
+
|
71
|
+
article = Results::Collection.new(@response).first
|
72
|
+
assert_kind_of Article, article
|
73
|
+
assert_equal 'Test', article.title
|
74
|
+
end
|
75
|
+
|
76
|
+
should "return score" do
|
77
|
+
document = Results::Collection.new(@response).first
|
78
|
+
assert_equal 0.5, document._score
|
79
|
+
end
|
80
|
+
|
81
|
+
should "return id" do
|
82
|
+
document = Results::Collection.new(@response).first
|
83
|
+
assert_equal 1, document.id
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
context "while paginating results" do
|
89
|
+
|
90
|
+
setup do
|
91
|
+
@default_response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 1, '_source' => {:title => 'Test'}},
|
92
|
+
{'_id' => 2},
|
93
|
+
{'_id' => 3}],
|
94
|
+
'total' => 3,
|
95
|
+
'took' => 1 } }
|
96
|
+
@collection = Results::Collection.new( @default_response, :per_page => 1, :page => 2 )
|
97
|
+
end
|
98
|
+
|
99
|
+
should "return total entries" do
|
100
|
+
assert_equal 3, @collection.total
|
101
|
+
assert_equal 3, @collection.total_entries
|
102
|
+
end
|
103
|
+
|
104
|
+
should "return total pages" do
|
105
|
+
assert_equal 3, @collection.total_pages
|
106
|
+
end
|
107
|
+
|
108
|
+
should "return total pages when per_page option not set" do
|
109
|
+
collection = Results::Collection.new( @default_response, :page => 1 )
|
110
|
+
assert_equal 1, collection.total_pages
|
111
|
+
end
|
112
|
+
|
113
|
+
should "return current page" do
|
114
|
+
assert_equal 2, @collection.current_page
|
115
|
+
end
|
116
|
+
|
117
|
+
should "return previous page" do
|
118
|
+
assert_equal 1, @collection.previous_page
|
119
|
+
end
|
120
|
+
|
121
|
+
should "return next page" do
|
122
|
+
assert_equal 3, @collection.next_page
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Tire
|
4
|
+
|
5
|
+
class ResultsItemTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "Item" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@document = Results::Item.new :title => 'Test', :author => { :name => 'Kafka' }
|
11
|
+
end
|
12
|
+
|
13
|
+
should "be initialized with a Hash" do
|
14
|
+
assert_raise(ArgumentError) { Results::Item.new('FUUUUUUU') }
|
15
|
+
|
16
|
+
assert_nothing_raised do
|
17
|
+
d = Results::Item.new(:id => 1)
|
18
|
+
assert_instance_of Results::Item, d
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should "respond to :to_indexed_json" do
|
23
|
+
assert_respond_to Results::Item.new, :to_indexed_json
|
24
|
+
end
|
25
|
+
|
26
|
+
should "retrieve simple values from underlying hash" do
|
27
|
+
assert_equal 'Test', @document[:title]
|
28
|
+
end
|
29
|
+
|
30
|
+
should "retrieve hash values from underlying hash" do
|
31
|
+
assert_equal 'Kafka', @document[:author][:name]
|
32
|
+
end
|
33
|
+
|
34
|
+
should "allow to retrieve value by methods" do
|
35
|
+
assert_not_nil @document.title
|
36
|
+
assert_equal 'Test', @document.title
|
37
|
+
end
|
38
|
+
|
39
|
+
should "return nil for non-existing keys/methods" do
|
40
|
+
assert_nothing_raised { @document.whatever }
|
41
|
+
assert_nil @document.whatever
|
42
|
+
end
|
43
|
+
|
44
|
+
should "not care about symbols or strings in keys" do
|
45
|
+
@document = Results::Item.new 'title' => 'Test'
|
46
|
+
assert_not_nil @document.title
|
47
|
+
assert_equal 'Test', @document.title
|
48
|
+
end
|
49
|
+
|
50
|
+
should "allow to retrieve values from nested hashes" do
|
51
|
+
assert_not_nil @document.author.name
|
52
|
+
assert_equal 'Kafka', @document.author.name
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|