tire 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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