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.
Files changed (83) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.markdown +435 -0
  5. data/Rakefile +75 -0
  6. data/examples/dsl.rb +73 -0
  7. data/examples/rails-application-template.rb +144 -0
  8. data/examples/tire-dsl.rb +617 -0
  9. data/lib/tire.rb +35 -0
  10. data/lib/tire/client.rb +40 -0
  11. data/lib/tire/configuration.rb +29 -0
  12. data/lib/tire/dsl.rb +33 -0
  13. data/lib/tire/index.rb +209 -0
  14. data/lib/tire/logger.rb +60 -0
  15. data/lib/tire/model/callbacks.rb +23 -0
  16. data/lib/tire/model/import.rb +18 -0
  17. data/lib/tire/model/indexing.rb +50 -0
  18. data/lib/tire/model/naming.rb +30 -0
  19. data/lib/tire/model/persistence.rb +34 -0
  20. data/lib/tire/model/persistence/attributes.rb +60 -0
  21. data/lib/tire/model/persistence/finders.rb +61 -0
  22. data/lib/tire/model/persistence/storage.rb +75 -0
  23. data/lib/tire/model/search.rb +97 -0
  24. data/lib/tire/results/collection.rb +56 -0
  25. data/lib/tire/results/item.rb +39 -0
  26. data/lib/tire/results/pagination.rb +30 -0
  27. data/lib/tire/rubyext/hash.rb +3 -0
  28. data/lib/tire/rubyext/symbol.rb +11 -0
  29. data/lib/tire/search.rb +117 -0
  30. data/lib/tire/search/facet.rb +41 -0
  31. data/lib/tire/search/filter.rb +28 -0
  32. data/lib/tire/search/highlight.rb +37 -0
  33. data/lib/tire/search/query.rb +42 -0
  34. data/lib/tire/search/sort.rb +29 -0
  35. data/lib/tire/tasks.rb +88 -0
  36. data/lib/tire/version.rb +3 -0
  37. data/test/fixtures/articles/1.json +1 -0
  38. data/test/fixtures/articles/2.json +1 -0
  39. data/test/fixtures/articles/3.json +1 -0
  40. data/test/fixtures/articles/4.json +1 -0
  41. data/test/fixtures/articles/5.json +1 -0
  42. data/test/integration/active_model_searchable_test.rb +80 -0
  43. data/test/integration/active_record_searchable_test.rb +193 -0
  44. data/test/integration/facets_test.rb +65 -0
  45. data/test/integration/filters_test.rb +46 -0
  46. data/test/integration/highlight_test.rb +52 -0
  47. data/test/integration/index_mapping_test.rb +44 -0
  48. data/test/integration/index_store_test.rb +68 -0
  49. data/test/integration/persistent_model_test.rb +35 -0
  50. data/test/integration/query_string_test.rb +43 -0
  51. data/test/integration/results_test.rb +28 -0
  52. data/test/integration/sort_test.rb +36 -0
  53. data/test/models/active_model_article.rb +31 -0
  54. data/test/models/active_model_article_with_callbacks.rb +49 -0
  55. data/test/models/active_model_article_with_custom_index_name.rb +5 -0
  56. data/test/models/active_record_article.rb +12 -0
  57. data/test/models/article.rb +15 -0
  58. data/test/models/persistent_article.rb +11 -0
  59. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  60. data/test/models/supermodel_article.rb +22 -0
  61. data/test/models/validated_model.rb +11 -0
  62. data/test/test_helper.rb +52 -0
  63. data/test/unit/active_model_lint_test.rb +17 -0
  64. data/test/unit/client_test.rb +43 -0
  65. data/test/unit/configuration_test.rb +71 -0
  66. data/test/unit/index_test.rb +390 -0
  67. data/test/unit/logger_test.rb +114 -0
  68. data/test/unit/model_callbacks_test.rb +90 -0
  69. data/test/unit/model_import_test.rb +71 -0
  70. data/test/unit/model_persistence_test.rb +400 -0
  71. data/test/unit/model_search_test.rb +289 -0
  72. data/test/unit/results_collection_test.rb +131 -0
  73. data/test/unit/results_item_test.rb +59 -0
  74. data/test/unit/rubyext_hash_test.rb +19 -0
  75. data/test/unit/search_facet_test.rb +69 -0
  76. data/test/unit/search_filter_test.rb +36 -0
  77. data/test/unit/search_highlight_test.rb +46 -0
  78. data/test/unit/search_query_test.rb +55 -0
  79. data/test/unit/search_sort_test.rb +50 -0
  80. data/test/unit/search_test.rb +204 -0
  81. data/test/unit/tire_test.rb +55 -0
  82. data/tire.gemspec +54 -0
  83. metadata +372 -0
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+
5
+ class RubyCoreExtensionsTest < Test::Unit::TestCase
6
+
7
+ context "Hash" do
8
+
9
+ should "have to_indexed_json doing the same as to_json" do
10
+ [{}, { 1 => 2 }, { 3 => 4, 5 => 6 }, { nil => [7,8,9] }].each do |h|
11
+ assert_equal Yajl::Parser.parse(h.to_json), Yajl::Parser.parse(h.to_indexed_json)
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,69 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class FacetTest < Test::Unit::TestCase
6
+
7
+ context "Facet" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Facet.new('foo'), :to_json
11
+ end
12
+
13
+ context "generally" do
14
+
15
+ should "encode facets with defaults for current query" do
16
+ assert_equal( { :foo => { :terms => {:field=>'bar',:size=>10,:all_terms=>false} } }.to_json,
17
+ Facet.new('foo').terms(:bar).to_json )
18
+ end
19
+
20
+ should "encode facets as global" do
21
+ assert_equal( { :foo => { :terms => {:field=>'bar',:size=>10,:all_terms=>false}, :global => true } }.to_json,
22
+ Facet.new('foo', :global => true).terms(:bar).to_json )
23
+ end
24
+
25
+ should "encode facet options" do
26
+ assert_equal( { :foo => { :terms => {:field=>'bar',:size=>5,:all_terms=>false} } }.to_json,
27
+ Facet.new('foo').terms(:bar, :size => 5).to_json )
28
+ end
29
+
30
+ should "encode facets when passed as a block" do
31
+ f = Facet.new('foo') do
32
+ terms :bar
33
+ end
34
+ assert_equal( { :foo => { :terms => {:field=>'bar',:size=>10,:all_terms=>false} } }.to_json, f.to_json )
35
+ end
36
+
37
+ end
38
+
39
+ context "terms facet" do
40
+
41
+ should "encode the default all_terms option" do
42
+ assert_equal false, Facet.new('foo') { terms :foo }.to_hash['foo'][:terms][:all_terms]
43
+ end
44
+
45
+ should "encode the all_terms option" do
46
+ assert_equal true, Facet.new('foo') { terms :foo, :all_terms => true }.to_hash['foo'][:terms][:all_terms]
47
+ end
48
+
49
+ end
50
+
51
+ context "date histogram" do
52
+
53
+ should "encode the JSON with default values" do
54
+ f = Facet.new('date') { date :published_on, :interval => 'day' }
55
+ assert_equal({ :date => { :date_histogram => { :field => 'published_on', :interval => 'day' } } }.to_json, f.to_json)
56
+ end
57
+
58
+ should "encode the JSON with custom interval" do
59
+ f = Facet.new('date') { date :published_on, :interval => 'month' }
60
+ assert_equal({ :date => { :date_histogram => { :field => 'published_on', :interval => 'month' } } }.to_json, f.to_json)
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class FilterTest < Test::Unit::TestCase
6
+
7
+ context "Filter" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Filter.new(:terms, {}), :to_json
11
+ end
12
+
13
+ should "encode simple filter declarations as JSON" do
14
+ assert_equal( { :terms => {} }.to_json,
15
+ Filter.new('terms').to_json )
16
+
17
+ assert_equal( { :terms => { :tags => ['foo'] } }.to_json,
18
+ Filter.new('terms', :tags => ['foo']).to_json )
19
+
20
+ assert_equal( { :range => { :age => { :from => 10, :to => 20 } } }.to_json,
21
+ Filter.new('range', { :age => { :from => 10, :to => 20 } }).to_json )
22
+
23
+ assert_equal( { :geo_distance => { :distance => '12km', :location => [40, -70] } }.to_json,
24
+ Filter.new('geo_distance', { :distance => '12km', :location => [40, -70] }).to_json )
25
+ end
26
+
27
+ should "encode 'or' filter declarations as JSON" do
28
+ # See http://www.elasticsearch.org/guide/reference/query-dsl/or-filter.html
29
+ assert_equal( { :or => [ {:terms => {:tags => ['foo']}}, {:terms => {:tags => ['bar']}} ] }.to_json,
30
+ Filter.new('or', {:terms => {:tags => ['foo']}}, {:terms => {:tags => ['bar']}}).to_json )
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class HighlightTest < Test::Unit::TestCase
6
+
7
+ context "Highlight" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Highlight.new(:body), :to_json
11
+ end
12
+
13
+ should "specify highlight for single field" do
14
+ assert_equal( {:fields => { :body => {} }}.to_json,
15
+ Highlight.new(:body).to_json )
16
+ end
17
+
18
+ should "specify highlight for more fields" do
19
+ assert_equal( {:fields => { :title => {}, :body => {} }}.to_json,
20
+ Highlight.new(:title, :body).to_json )
21
+ end
22
+
23
+ should "specify highlight for more fields with options" do
24
+ assert_equal( {:fields => { :title => {}, :body => { :a => 1, :b => 2 } }}.to_json,
25
+ Highlight.new(:title, :body => { :a => 1, :b => 2 }).to_json )
26
+ end
27
+
28
+ should "specify highlight for more fields with highlight options" do
29
+ # p Highlight.new(:title, :body => {}, :options => { :tag => '<strong>' }).to_hash
30
+ assert_equal( {:fields => { :title => {}, :body => {} }, :pre_tags => ['<strong>'], :post_tags => ['</strong>'] }.to_json,
31
+ Highlight.new(:title, :body => {}, :options => { :tag => '<strong>' }).to_json )
32
+ end
33
+
34
+ context "with custom tags" do
35
+
36
+ should "properly parse tags with class" do
37
+ assert_equal( {:fields => { :title => {} }, :pre_tags => ['<strong class="highlight">'], :post_tags => ['</strong>'] }.to_json,
38
+ Highlight.new(:title, :options => { :tag => '<strong class="highlight">' }).to_json )
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class QueryTest < Test::Unit::TestCase
6
+
7
+ context "Query" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Query.new, :to_json
11
+ end
12
+
13
+ should "allow a block to be given" do
14
+ assert_equal( { :term => { :foo => 'bar' } }.to_json, Query.new do
15
+ term(:foo, 'bar')
16
+ end.to_json)
17
+ end
18
+
19
+ should "allow search for single term" do
20
+ assert_equal( { :term => { :foo => 'bar' } }, Query.new.term(:foo, 'bar') )
21
+ end
22
+
23
+ should "allow search for multiple terms" do
24
+ assert_equal( { :terms => { :foo => ['bar', 'baz'] } }, Query.new.terms(:foo, ['bar', 'baz']) )
25
+ end
26
+
27
+ should "allow set minimum match when searching for multiple terms" do
28
+ assert_equal( { :terms => { :foo => ['bar', 'baz'], :minimum_match => 2 } },
29
+ Query.new.terms(:foo, ['bar', 'baz'], :minimum_match => 2) )
30
+ end
31
+
32
+ should "allow search with a query string" do
33
+ assert_equal( { :query_string => { :query => 'title:foo' } },
34
+ Query.new.string('title:foo') )
35
+ end
36
+
37
+ should "allow set default field when searching with a query string" do
38
+ assert_equal( { :query_string => { :query => 'foo', :default_field => 'title' } },
39
+ Query.new.string('foo', :default_field => 'title') )
40
+ end
41
+
42
+ should "search for all documents" do
43
+ assert_equal( { :match_all => { } }, Query.new.all )
44
+ end
45
+
46
+ should "search for documents by IDs" do
47
+ assert_equal( { :ids => { :values => [1, 2], :type => 'foo' } },
48
+ Query.new.ids([1, 2], 'foo') )
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,50 @@
1
+ require 'test_helper'
2
+
3
+ module Tire::Search
4
+
5
+ class SortTest < Test::Unit::TestCase
6
+
7
+ context "Sort" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Sort.new, :to_json
11
+ end
12
+
13
+ should "encode simple strings" do
14
+ assert_equal [:foo].to_json, Sort.new.foo.to_json
15
+ end
16
+
17
+ should "encode method arguments" do
18
+ assert_equal [:foo => 'desc'].to_json, Sort.new.foo('desc').to_json
19
+ end
20
+
21
+ should "encode hash" do
22
+ assert_equal [ :foo => { :reverse => true } ].to_json, Sort.new.foo(:reverse => true).to_json
23
+ end
24
+
25
+ should "encode multiple sort fields" do
26
+ assert_equal [:foo, :bar].to_json, Sort.new.foo.bar.to_json
27
+ end
28
+
29
+ should "encode fields when passed as a block to constructor" do
30
+ s = Sort.new do
31
+ foo
32
+ bar 'desc'
33
+ _score
34
+ end
35
+ assert_equal [ :foo, {:bar => 'desc'}, :_score ].to_json, s.to_json
36
+ end
37
+
38
+ should "encode fields deeper in json" do
39
+ s = Sort.new { field 'author.name' }
40
+ assert_equal [ 'author.name' ].to_json, s.to_json
41
+
42
+ s = Sort.new { field 'author.name', :desc }
43
+ assert_equal [ {'author.name' => 'desc'} ].to_json, s.to_json
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,204 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+
5
+ class SearchTest < Test::Unit::TestCase
6
+
7
+ context "Search" do
8
+ setup { Configuration.reset :logger }
9
+
10
+ should "be initialized with index/indices" do
11
+ assert_raise(ArgumentError) { Search::Search.new }
12
+ end
13
+
14
+ should "have the query method" do
15
+ assert_respond_to Search::Search.new('index'), :query
16
+ end
17
+
18
+ should "allow to pass block to query" do
19
+ Search::Query.any_instance.expects(:instance_eval)
20
+
21
+ Search::Search.new('index').query { string 'foo' }
22
+ end
23
+
24
+ should "allow to pass block with argument to query, allowing to use local variables from outer scope" do
25
+ foo = 'bar'
26
+ query_block = lambda { |query| query.string foo }
27
+ Search::Query.any_instance.expects(:instance_eval).never
28
+
29
+ Search::Search.new('index').query &query_block
30
+ end
31
+
32
+ should "store indices as an array" do
33
+ s = Search::Search.new('index1') do;end
34
+ assert_equal ['index1'], s.indices
35
+
36
+ s = Search::Search.new('index1', 'index2') do;end
37
+ assert_equal ['index1', 'index2'], s.indices
38
+ end
39
+
40
+ should "return curl snippet for debugging" do
41
+ s = Search::Search.new('index') do
42
+ query { string 'title:foo' }
43
+ end
44
+ assert_equal %q|curl -X POST "http://localhost:9200/index/_search?pretty=true" -d | +
45
+ %q|'{"query":{"query_string":{"query":"title:foo"}}}'|,
46
+ s.to_curl
47
+ end
48
+
49
+ should "return curl snippet with multiple indices for debugging" do
50
+ s = Search::Search.new('index_1', 'index_2') do
51
+ query { string 'title:foo' }
52
+ end
53
+ assert_match /index_1,index_2/, s.to_curl
54
+ end
55
+
56
+ should "allow chaining" do
57
+ assert_nothing_raised do
58
+ Search::Search.new('index').query { }.sort { title 'desc' }.size(5).sort { name 'asc' }.from(1)
59
+ end
60
+ end
61
+
62
+ should "perform the search" do
63
+ response = stub(:body => '{"took":1,"hits":[]}', :code => 200)
64
+ Configuration.client.expects(:post).returns(response)
65
+ Results::Collection.expects(:new).returns([])
66
+
67
+ s = Search::Search.new('index')
68
+ s.perform
69
+ assert_not_nil s.results
70
+ assert_not_nil s.response
71
+ end
72
+
73
+ should "print debugging information on exception and re-raise it" do
74
+ Configuration.client.expects(:post).raises(RestClient::InternalServerError)
75
+ STDERR.expects(:puts)
76
+
77
+ s = Search::Search.new('index')
78
+ assert_raise(RestClient::InternalServerError) { s.perform }
79
+ end
80
+
81
+ should "log request, but not response, when logger is set" do
82
+ Configuration.logger STDERR
83
+
84
+ response = stub(:body => '{"took":1,"hits":[]}', :code => 200)
85
+ Configuration.client.expects(:post).returns(response)
86
+
87
+ Results::Collection.expects(:new).returns([])
88
+ Configuration.logger.expects(:log_request).returns(true)
89
+ Configuration.logger.expects(:log_response).with(200, 1, '')
90
+
91
+ Search::Search.new('index').perform
92
+ end
93
+
94
+ context "sort" do
95
+
96
+ should "allow sorting by multiple fields" do
97
+ s = Search::Search.new('index') do
98
+ sort do
99
+ title 'desc'
100
+ _score
101
+ end
102
+ end
103
+ hash = JSON.load( s.to_json )
104
+ assert_equal [{'title' => 'desc'}, '_score'], hash['sort']
105
+ end
106
+
107
+ end
108
+
109
+ context "facets" do
110
+
111
+ should "retrieve terms facets" do
112
+ s = Search::Search.new('index') do
113
+ facet('foo1') { terms :bar, :global => true }
114
+ facet('foo2', :global => true) { terms :bar }
115
+ facet('foo3') { terms :baz }
116
+ end
117
+ assert_equal 3, s.facets.keys.size
118
+ assert_not_nil s.facets['foo1']
119
+ assert_not_nil s.facets['foo2']
120
+ assert_not_nil s.facets['foo3']
121
+ end
122
+
123
+ should "retrieve date histogram facets" do
124
+ s = Search::Search.new('index') do
125
+ facet('date') { date :published_on }
126
+ end
127
+ assert_equal 1, s.facets.keys.size
128
+ assert_not_nil s.facets['date']
129
+ end
130
+
131
+ end
132
+
133
+ context "filter" do
134
+
135
+ should "allow to specify filter" do
136
+ s = Search::Search.new('index') do
137
+ filter :terms, :tags => ['foo']
138
+ end
139
+
140
+ assert_equal 1, s.filters.size
141
+ assert_not_nil s.filters.first
142
+ assert_not_nil s.filters.first[:terms]
143
+ end
144
+
145
+ end
146
+
147
+ context "highlight" do
148
+
149
+ should "allow to specify highlight for single field" do
150
+ s = Search::Search.new('index') do
151
+ highlight :body
152
+ end
153
+
154
+ assert_not_nil s.highlight
155
+ assert_instance_of Tire::Search::Highlight, s.highlight
156
+ end
157
+
158
+ should "allow to specify highlight for more fields" do
159
+ s = Search::Search.new('index') do
160
+ highlight :body, :title
161
+ end
162
+
163
+ assert_not_nil s.highlight
164
+ assert_instance_of Tire::Search::Highlight, s.highlight
165
+ end
166
+
167
+ should "allow to specify highlight with for more fields with options" do
168
+ s = Search::Search.new('index') do
169
+ highlight :body, :title => { :fragment_size => 150, :number_of_fragments => 3 }
170
+ end
171
+
172
+ assert_not_nil s.highlight
173
+ assert_instance_of Tire::Search::Highlight, s.highlight
174
+ end
175
+
176
+ end
177
+
178
+ context "with from/size" do
179
+
180
+ should "set the values in request" do
181
+ s = Search::Search.new('index') do
182
+ size 5
183
+ from 3
184
+ end
185
+ hash = JSON.load( s.to_json )
186
+ assert_equal 5, hash['size']
187
+ assert_equal 3, hash['from']
188
+ end
189
+
190
+ should "set the fields limit in request" do
191
+ s = Search::Search.new('index') do
192
+ fields :title
193
+ end
194
+ hash = JSON.load( s.to_json )
195
+ assert_equal 'title', hash['fields']
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ end
203
+
204
+ end