tire 0.1.14 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -557,4 +557,4 @@ You can send feedback via [e-mail](mailto:karmi@karmi.cz) or via [Github Issues]
557
557
 
558
558
  -----
559
559
 
560
- [Karel Minarik](http://karmi.cz)
560
+ [Karel Minarik](http://karmi.cz) and [contributors](http://github.com/karmi/tire/contributors)
data/examples/tire-dsl.rb CHANGED
@@ -61,14 +61,56 @@ Tire.index 'articles' do
61
61
  create
62
62
 
63
63
  # We want to store and index some articles with `title`, `tags` and `published_on` properties.
64
- # Simple Hashes are OK.
64
+ # Simple Hashes are OK. The default type is „document”.
65
65
  #
66
66
  store :title => 'One', :tags => ['ruby'], :published_on => '2011-01-01'
67
67
  store :title => 'Two', :tags => ['ruby', 'python'], :published_on => '2011-01-02'
68
- store :title => 'Three', :tags => ['java'], :published_on => '2011-01-02'
69
- store :title => 'Four', :tags => ['ruby', 'php'], :published_on => '2011-01-03'
70
68
 
71
- # We force refreshing the index, so we can query it immediately.
69
+ # We usually want to set a specific _type_ for the document in _ElasticSearch_.
70
+ # Simply setting a `type` property is OK.
71
+ #
72
+ store :type => 'article',
73
+ :title => 'Three',
74
+ :tags => ['java'],
75
+ :published_on => '2011-01-02'
76
+
77
+ # We may want to wrap your data in a Ruby class, and use it when storing data.
78
+ # The contract required of such a class is very simple.
79
+ #
80
+ class Article
81
+
82
+ #
83
+ attr_reader :title, :tags, :published_on
84
+ def initialize(attributes={})
85
+ @attributes = attributes
86
+ @attributes.each_pair { |name,value| instance_variable_set :"@#{name}", value }
87
+ end
88
+
89
+ # It must provide a `type`, `_type` or `document_type` method for propper mapping.
90
+ #
91
+ def type
92
+ 'article'
93
+ end
94
+
95
+ # And it must provide a `to_indexed_json` method for conversion to JSON.
96
+ #
97
+ def to_indexed_json
98
+ @attributes.to_json
99
+ end
100
+ end
101
+
102
+ # Note: Since our class takes a Hash of attributes on initialization, we may even
103
+ # wrap the results in instances of this class; we'll see how to do that further below.
104
+ #
105
+ article = Article.new :title => 'Four',
106
+ :tags => ['ruby', 'php'],
107
+ :published_on => '2011-01-03'
108
+
109
+ # Let's store the `article`, now.
110
+ #
111
+ store article
112
+
113
+ # And let's „force refresh“ the index, so we can query it immediately.
72
114
  #
73
115
  refresh
74
116
  end
@@ -77,22 +119,23 @@ end
77
119
  # for the index.
78
120
 
79
121
  Tire.index 'articles' do
80
- # To do so, just pass a Hash containing the specified mapping to the `Index#create` method.
122
+ # To do so, let's just pass a Hash containing the specified mapping to the `Index#create` method.
81
123
  #
82
124
  create :mappings => {
83
125
 
84
- # Specify for which type of documents this mapping should be used.
126
+ # Let's specify for which _type_ of documents this mapping should be used:
127
+ # „article”, in our case.
85
128
  #
86
129
  :article => {
87
130
  :properties => {
88
131
 
89
- # Specify the type of the field, whether it should be analyzed, etc.
132
+ # Let's specify the type of the field, whether it should be analyzed, ...
90
133
  #
91
134
  :id => { :type => 'string', :index => 'not_analyzed', :include_in_all => false },
92
135
 
93
- # Set the boost or analyzer settings for the field, ... The _ElasticSearch_ guide
94
- # has [more information](http://elasticsearch.org/guide/reference/mapping/index.html)
95
- # about this. Proper mapping is key to efficient and effective search.
136
+ # ... set the boost or analyzer settings for the field, etc. The _ElasticSearch_ guide
137
+ # has [more information](http://elasticsearch.org/guide/reference/mapping/index.html).
138
+ # Don't forget, that proper mapping is key to efficient and effective search.
96
139
  # But don't fret about getting the mapping right the first time, you won't.
97
140
  # In most cases, the default, dynamic mapping is just fine for prototyping.
98
141
  #
@@ -116,10 +159,13 @@ articles = [
116
159
 
117
160
  # Notice that such objects must have an `id` property!
118
161
  #
119
- { :id => '1', :title => 'one', :tags => ['ruby'], :published_on => '2011-01-01' },
120
- { :id => '2', :title => 'two', :tags => ['ruby', 'python'], :published_on => '2011-01-02' },
121
- { :id => '3', :title => 'three', :tags => ['java'], :published_on => '2011-01-02' },
122
- { :id => '4', :title => 'four', :tags => ['ruby', 'php'], :published_on => '2011-01-03' }
162
+ { :id => '1', :type => 'article', :title => 'one', :tags => ['ruby'], :published_on => '2011-01-01' },
163
+
164
+ # And, of course, they should contain the `type` property for the mapping to work!
165
+ #
166
+ { :id => '2', :type => 'article', :title => 'two', :tags => ['ruby', 'python'], :published_on => '2011-01-02' },
167
+ { :id => '3', :type => 'article', :title => 'three', :tags => ['java'], :published_on => '2011-01-02' },
168
+ { :id => '4', :type => 'article', :title => 'four', :tags => ['ruby', 'php'], :published_on => '2011-01-03' }
123
169
  ]
124
170
 
125
171
  # We can just push them into the index in one go.
@@ -209,7 +255,7 @@ end
209
255
  # To do that, we have to use a slight variation of the DSL.
210
256
  #
211
257
 
212
- # Let's assume we have a plain Article class.
258
+ # Let's assume we have a plain Ruby class, named `Article`.
213
259
  #
214
260
  class Article
215
261
 
@@ -219,7 +265,7 @@ class Article
219
265
  "title:T*"
220
266
  end
221
267
 
222
- # ... and wrap the _Tire_ search method.
268
+ # ... and wrap the _Tire_ search method in another one.
223
269
  def self.search
224
270
 
225
271
  # Notice how we pass the `search` object around as a block argument.
@@ -298,13 +344,14 @@ Tire.configure do
298
344
  #
299
345
  url "http://search.example.com"
300
346
 
301
- # Second, we may want to wrap the result items in our own class.
347
+ # Second, we may want to wrap the result items in our own class, for instance
348
+ # the `Article` class set above.
302
349
  #
303
- class MySpecialWrapper; end
304
- wrapper MySpecialWrapper
350
+ wrapper Article
305
351
 
306
352
  # Finally, we can reset one or all configuration settings to their defaults.
307
353
  #
354
+ reset :url
308
355
  reset
309
356
 
310
357
  end
data/lib/tire/client.rb CHANGED
@@ -2,25 +2,7 @@ module Tire
2
2
 
3
3
  module Client
4
4
 
5
- class Base
6
- def get(url, data=nil)
7
- raise_no_method_error
8
- end
9
- def post(url, data)
10
- raise_no_method_error
11
- end
12
- def put(url, data)
13
- raise NoMethodError, "Implement this method in your client class"
14
- end
15
- def delete(url)
16
- raise_no_method_error
17
- end
18
- def raise_no_method_error
19
- raise NoMethodError, "Implement this method in your client class"
20
- end
21
- end
22
-
23
- class RestClient < Base
5
+ class RestClient
24
6
  def self.get(url, data=nil)
25
7
  ::RestClient::Request.new(:method => :get, :url => url, :payload => data).execute
26
8
  end
@@ -3,7 +3,7 @@ module Tire
3
3
  class Configuration
4
4
 
5
5
  def self.url(value=nil)
6
- @url = value || @url || "http://localhost:9200"
6
+ @url = (value ? value.to_s.gsub(%r|/*$|, '') : nil) || @url || "http://localhost:9200"
7
7
  end
8
8
 
9
9
  def self.client(klass=nil)
data/lib/tire/index.rb CHANGED
@@ -41,13 +41,20 @@ module Tire
41
41
  end
42
42
 
43
43
  def store(*args)
44
- # TODO: Infer type from the document (hash property, method)
45
- # TODO: Refactor common logic for getting id, JSON, into private methods
46
-
47
- case args.size
48
- when 3 then (type, document, options = args)
49
- when 2 then (type, document = args)
50
- else (document = args.pop; type = :document)
44
+ case
45
+ when ( args.size === 3 && (args.first.is_a?(String) || args.first.is_a?(Symbol)) )
46
+ Tire.warn "Passing the document type as argument in Index#store has been deprecated, " +
47
+ "please pass a Hash with _type/type property, or " +
48
+ "an object with _type/type/document_type method."
49
+ type, document, options = args
50
+ when ( args.size === 2 && (args.first.is_a?(String) || args.first.is_a?(Symbol)) )
51
+ Tire.warn "Passing the document type as argument in Index#store has been deprecated" +
52
+ "please pass a Hash with _type/type property, or " +
53
+ "an object with _type/type/document_type method."
54
+ type, document = args
55
+ else
56
+ document, options = args
57
+ type = get_type_from_document(document)
51
58
  end
52
59
 
53
60
  if options
@@ -55,18 +62,8 @@ module Tire
55
62
  percolate = "*" if percolate === true
56
63
  end
57
64
 
58
- old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#id deprecation warnings
59
- id = case true
60
- when document.is_a?(Hash) then document[:id] || document['id']
61
- when document.respond_to?(:id) && document.id != document.object_id then document.id
62
- end
63
- $VERBOSE = old_verbose
64
-
65
- document = case true
66
- when document.is_a?(String) then document
67
- when document.respond_to?(:to_indexed_json) then document.to_indexed_json
68
- else raise ArgumentError, "Please pass a JSON string or object with a 'to_indexed_json' method"
69
- end
65
+ id = get_id_from_document(document)
66
+ document = convert_document_to_json(document)
70
67
 
71
68
  url = id ? "#{Configuration.url}/#{@name}/#{type}/#{id}" : "#{Configuration.url}/#{@name}/#{type}/"
72
69
  url += "?percolate=#{percolate}" if percolate
@@ -83,22 +80,14 @@ module Tire
83
80
 
84
81
  def bulk_store documents
85
82
  payload = documents.map do |document|
86
- old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#id deprecation warnings
87
- id = case
88
- when document.is_a?(Hash) then document[:id] || document['id']
89
- when document.respond_to?(:id) && document.id != document.object_id then document.id
90
- # TODO: Raise error when no id present
91
- end
92
- $VERBOSE = old_verbose
83
+ id = get_id_from_document(document)
84
+ type = get_type_from_document(document)
93
85
 
94
- type = case
95
- when document.is_a?(Hash) then document[:type] || document['type']
96
- when document.respond_to?(:document_type) then document.document_type
97
- end || 'document'
86
+ STDERR.puts "[ERROR] Document #{document.inspect} does not have ID" unless id
98
87
 
99
88
  output = []
100
89
  output << %Q|{"index":{"_index":"#{@name}","_type":"#{type}","_id":"#{id}"}}|
101
- output << document.to_indexed_json
90
+ output << convert_document_to_json(document)
102
91
  output.join("\n")
103
92
  end
104
93
  payload << ""
@@ -107,8 +96,6 @@ module Tire
107
96
  count = 0
108
97
 
109
98
  begin
110
- # STDERR.puts "Posting payload..."
111
- # STDERR.puts payload.join("\n")
112
99
  Configuration.client.post("#{Configuration.url}/_bulk", payload.join("\n"))
113
100
  rescue Exception => error
114
101
  if count < tries
@@ -127,14 +114,11 @@ module Tire
127
114
  end
128
115
 
129
116
  def import(klass_or_collection, method=nil, options={})
130
- # p [klass_or_collection, method, options]
131
-
132
117
  case
133
-
134
118
  when method
135
119
  options = {:page => 1, :per_page => 1000}.merge options
136
120
  while documents = klass_or_collection.send(method.to_sym, options.merge(:page => options[:page])) \
137
- and not documents.empty?
121
+ and documents.size > 0
138
122
  documents = yield documents if block_given?
139
123
 
140
124
  bulk_store documents
@@ -144,34 +128,31 @@ module Tire
144
128
  when klass_or_collection.respond_to?(:map)
145
129
  documents = block_given? ? yield(klass_or_collection) : klass_or_collection
146
130
  bulk_store documents
131
+
147
132
  else
148
- raise ArgumentError, "Please pass either a collection of objects, "+
133
+ raise ArgumentError, "Please pass either a collection of objects, " +
149
134
  "or method for fetching records, or Enumerable compatible class"
150
135
  end
151
136
  end
152
137
 
153
138
  def remove(*args)
154
- # TODO: Infer type from the document (hash property, method)
155
-
156
139
  if args.size > 1
157
- (type, document = args)
140
+ type, document = args
141
+ id = get_id_from_document(document) || document
158
142
  else
159
- (document = args.pop; type = :document)
143
+ document = args.pop
144
+ type = get_type_from_document(document)
145
+ id = get_id_from_document(document) || document
160
146
  end
161
-
162
- old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#id deprecation warnings
163
- id = case true
164
- when document.is_a?(Hash) then document[:id] || document['id']
165
- when document.respond_to?(:id) && document.id != document.object_id then document.id
166
- else document
167
- end
168
- $VERBOSE = old_verbose
147
+ raise ArgumentError, "Please pass a document ID" unless id
169
148
 
170
149
  result = Configuration.client.delete "#{Configuration.url}/#{@name}/#{type}/#{id}"
171
150
  MultiJson.decode(result) if result
172
151
  end
173
152
 
174
153
  def retrieve(type, id)
154
+ raise ArgumentError, "Please pass a document ID" unless id
155
+
175
156
  @response = Configuration.client.get "#{Configuration.url}/#{@name}/#{type}/#{id}"
176
157
  h = MultiJson.decode(@response.body)
177
158
  if Configuration.wrapper == Hash then h
@@ -237,19 +218,17 @@ module Tire
237
218
  end
238
219
 
239
220
  def percolate(*args, &block)
240
- # TODO: Infer type from the document (hash property, method)
241
-
242
221
  if args.size > 1
243
- (type, document = args)
222
+ Tire.warn "Passing the document type as argument in Index#percolate has been deprecated, " +
223
+ "please pass a Hash with _type/type property, or " +
224
+ "an object with _type/type/document_type method."
225
+ type, document = args
244
226
  else
245
- (document = args.pop; type = :document)
227
+ document = args.pop
228
+ type = get_type_from_document(document)
246
229
  end
247
230
 
248
- document = case true
249
- when document.is_a?(String) then document
250
- when document.respond_to?(:to_hash) then document.to_hash
251
- else raise ArgumentError, "Please pass a JSON string or object with a 'to_hash' method"
252
- end
231
+ document = MultiJson.decode convert_document_to_json(document)
253
232
 
254
233
  query = Search::Query.new(&block).to_hash if block_given?
255
234
 
@@ -288,5 +267,41 @@ module Tire
288
267
  end
289
268
  end
290
269
 
270
+ def get_type_from_document(document)
271
+ old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#type deprecation warnings
272
+ type = case
273
+ when document.respond_to?(:document_type)
274
+ document.document_type
275
+ when document.is_a?(Hash)
276
+ document.delete(:_type) || document.delete('_type') || document.delete(:type) || document.delete('type')
277
+ when document.respond_to?(:_type)
278
+ document._type
279
+ when document.respond_to?(:type) && document.type != document.class
280
+ document.type
281
+ end
282
+ $VERBOSE = old_verbose
283
+ type ||= :document
284
+ end
285
+
286
+ def get_id_from_document(document)
287
+ old_verbose, $VERBOSE = $VERBOSE, nil # Silence Object#id deprecation warnings
288
+ id = case
289
+ when document.is_a?(Hash)
290
+ document[:_id] || document['_id'] || document[:id] || document['id']
291
+ when document.respond_to?(:id) && document.id != document.object_id
292
+ document.id
293
+ end
294
+ $VERBOSE = old_verbose
295
+ id
296
+ end
297
+
298
+ def convert_document_to_json(document)
299
+ document = case
300
+ when document.is_a?(String) then document
301
+ when document.respond_to?(:to_indexed_json) then document.to_indexed_json
302
+ else raise ArgumentError, "Please pass a JSON string or object with a 'to_indexed_json' method"
303
+ end
304
+ end
305
+
291
306
  end
292
307
  end
@@ -35,15 +35,14 @@ module Tire
35
35
  def search(query=nil, options={}, &block)
36
36
  old_wrapper = Tire::Configuration.wrapper
37
37
  Tire::Configuration.wrapper self
38
- sort = options[:order] || options[:sort]
39
- sort = Array(sort)
38
+ sort = Array( options[:order] || options[:sort] )
40
39
  unless block_given?
41
40
  s = Tire::Search::Search.new(elasticsearch_index.name, options)
42
41
  s.query { string query }
43
42
  s.sort do
44
43
  sort.each do |t|
45
44
  field_name, direction = t.split(' ')
46
- field_name.include?('.') ? field(field_name, direction) : send(field_name, direction)
45
+ by field_name, direction
47
46
  end
48
47
  end unless sort.empty?
49
48
  s.size( options[:per_page].to_i ) if options[:per_page]
@@ -75,7 +74,7 @@ module Tire
75
74
  module InstanceMethods
76
75
 
77
76
  def score
78
- STDERR.puts "DEPRECATED! Please use #{self.class}#_score instead."
77
+ Tire.warn "#{self.class}#score has been deprecated, please use #{self.class}#_score instead."
79
78
  attributes['_score']
80
79
  end
81
80
 
@@ -86,9 +85,9 @@ module Tire
86
85
  def update_elastic_search_index
87
86
  _run_update_elastic_search_index_callbacks do
88
87
  if destroyed?
89
- index.remove document_type, self
88
+ index.remove self
90
89
  else
91
- response = index.store( document_type, self, {:percolate => self.percolator} )
90
+ response = index.store( self, {:percolate => self.percolator} )
92
91
  self.id ||= response['_id'] if self.respond_to?(:id=)
93
92
  self._index = response['_index']
94
93
  self._type = response['_type']
@@ -98,6 +97,7 @@ module Tire
98
97
  end
99
98
  end
100
99
  end
100
+ alias :update_elasticsearch_index :update_elastic_search_index
101
101
 
102
102
  def to_indexed_json
103
103
  if self.class.mapping.empty?
@@ -4,20 +4,19 @@ module Tire
4
4
  class Sort
5
5
  def initialize(&block)
6
6
  @value = []
7
- self.instance_eval(&block) if block_given?
7
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
8
8
  end
9
9
 
10
- def field(name, direction=nil)
10
+ def by(name, direction=nil)
11
11
  @value << ( direction ? { name => direction } : name )
12
12
  self
13
13
  end
14
14
 
15
15
  def method_missing(id, *args, &block)
16
- case arg = args.shift
17
- when String, Symbol, Hash then @value << { id => arg }
18
- else @value << id
19
- end
20
- self
16
+ Tire.warn "Using methods when sorting has been deprecated, please use the `by` method: " +
17
+ "sort { by :#{id}#{ args.empty? ? '' : ', ' + args.first.inspect } }"
18
+
19
+ by id, args.shift
21
20
  end
22
21
 
23
22
  def to_ary
data/lib/tire/version.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  module Tire
2
- VERSION = "0.1.14"
2
+ VERSION = "0.1.15"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
6
6
 
7
- # Added `<after/before>_update_elastic_search_index` callbacks for models
8
- # Added percolator support for DSL and ActiveModel
9
- # Lazy loading of ResultsCollection into memory
10
- # Fixes for debug logging
7
+ # Cleanup of code for getting document type, id, JSON serialization
8
+ # Bunch of deprecations: sorting, passing document type to store/remove
9
+ # Displaying a warning when no ID is passed when storing in bulk
10
+ # Correct handling of import for Mongoid/Kaminari combos
11
11
  END
12
12
  end
data/lib/tire.rb CHANGED
@@ -7,7 +7,6 @@ require 'tire/rubyext/symbol'
7
7
  require 'tire/logger'
8
8
  require 'tire/configuration'
9
9
  require 'tire/client'
10
- require 'tire/client'
11
10
  require 'tire/search'
12
11
  require 'tire/search/query'
13
12
  require 'tire/search/sort'
@@ -33,4 +32,10 @@ require 'tire/tasks'
33
32
 
34
33
  module Tire
35
34
  extend DSL
35
+
36
+ def warn(message)
37
+ line = caller.detect { |line| line !~ %r|lib\/tire\/| }.sub(/:in .*/, '')
38
+ STDERR.puts "", "\e[31m[DEPRECATION WARNING] #{message}", "(Called from #{line})", "\e[0m"
39
+ end
40
+ module_function :warn
36
41
  end
@@ -132,7 +132,7 @@ module Tire
132
132
  should "find first page with five results" do
133
133
  results = ActiveRecordArticle.search do |search|
134
134
  search.query { |query| query.string @q }
135
- search.sort { title }
135
+ search.sort { by :title }
136
136
  search.from 0
137
137
  search.size 5
138
138
  end
@@ -149,7 +149,7 @@ module Tire
149
149
  should "find next page with five results" do
150
150
  results = ActiveRecordArticle.search do |search|
151
151
  search.query { |query| query.string @q }
152
- search.sort { title }
152
+ search.sort { by :title }
153
153
  search.from 5
154
154
  search.size 5
155
155
  end
@@ -166,7 +166,7 @@ module Tire
166
166
  should "not find a missing page" do
167
167
  results = ActiveRecordArticle.search do |search|
168
168
  search.query { |query| query.string @q }
169
- search.sort { title }
169
+ search.sort { by :title }
170
170
  search.from 10
171
171
  search.size 5
172
172
  end
@@ -12,7 +12,7 @@ module Tire
12
12
 
13
13
  index = Tire.index 'mapped-index' do
14
14
  create
15
- store :article, :title => 'One'
15
+ store :type => :article, :title => 'One'
16
16
  refresh
17
17
  end
18
18
  sleep 1.5
@@ -83,17 +83,17 @@ module Tire
83
83
  end
84
84
 
85
85
  should "return an empty array when no query matches" do
86
- response = @index.store :document, {:message => 'Situation normal'}, {:percolate => true}
86
+ response = @index.store( {:message => 'Situation normal'}, {:percolate => true} )
87
87
  assert_equal [], response['matches']
88
88
  end
89
89
 
90
90
  should "return an array of matching query names" do
91
- response = @index.store :document, {:message => 'Severe weather warning'}, {:percolate => true}
91
+ response = @index.store( {:message => 'Severe weather warning'}, {:percolate => true} )
92
92
  assert_equal ['alert','weather'], response['matches'].sort
93
93
  end
94
94
 
95
95
  should "return an array of matching query names for specific percolated queries" do
96
- response = @index.store :document, {:message => 'Severe weather warning'}, {:percolate => 'tags:weather'}
96
+ response = @index.store( {:message => 'Severe weather warning'}, {:percolate => 'tags:weather'} )
97
97
  assert_equal ['weather'], response['matches']
98
98
  end
99
99
  end
@@ -11,7 +11,7 @@ module Tire
11
11
  q = '*'
12
12
  s = Tire.search('articles-test') do
13
13
  query { string q }
14
- sort { title }
14
+ sort { by :title }
15
15
  end
16
16
 
17
17
  assert_equal 5, s.results.count
@@ -22,7 +22,7 @@ module Tire
22
22
  q = '*'
23
23
  s = Tire.search('articles-test') do
24
24
  query { string q }
25
- sort { title :desc }
25
+ sort { by :title, :desc }
26
26
  end
27
27
 
28
28
  assert_equal 5, s.results.count
@@ -4,25 +4,6 @@ module Tire
4
4
 
5
5
  class ClientTest < Test::Unit::TestCase
6
6
 
7
- context "Base" do
8
- setup { @http ||= Client::Base.new }
9
-
10
- should "have abstract methods" do
11
- assert_raise(ArgumentError) { @http.get }
12
- assert_raise(NoMethodError) { @http.get 'URL' }
13
-
14
- assert_raise(ArgumentError) { @http.post }
15
- assert_raise(ArgumentError) { @http.post 'URL' }
16
- assert_raise(NoMethodError) { @http.post 'URL', 'DATA' }
17
-
18
- assert_raise(ArgumentError) { @http.put }
19
- assert_raise(ArgumentError) { @http.put 'URL' }
20
-
21
- assert_raise(ArgumentError) { @http.delete }
22
- assert_raise(NoMethodError) { @http.delete 'URL' }
23
- end
24
- end
25
-
26
7
  context "RestClient" do
27
8
 
28
9
  should "be default" do
@@ -27,13 +27,13 @@ module Tire
27
27
  assert_equal 'http://example.com', Configuration.url
28
28
  end
29
29
 
30
- should "return default client" do
31
- assert_equal Client::RestClient, Configuration.client
30
+ should "strip trailing slash from the URL" do
31
+ assert_nothing_raised { Configuration.url 'http://slash.com:9200/' }
32
+ assert_equal 'http://slash.com:9200', Configuration.url
32
33
  end
33
34
 
34
- should "allow setting and retrieving the client" do
35
- assert_nothing_raised { Configuration.client Client::Base }
36
- assert_equal Client::Base, Configuration.client
35
+ should "return default client" do
36
+ assert_equal Client::RestClient, Configuration.client
37
37
  end
38
38
 
39
39
  should "return nil as logger by default" do
@@ -48,21 +48,18 @@ module Tire
48
48
 
49
49
  should "allow to reset the configuration for specific property" do
50
50
  Configuration.url 'http://example.com'
51
- Configuration.client Client::Base
52
- assert_equal 'http://example.com', Configuration.url
51
+ assert_equal 'http://example.com', Configuration.url
53
52
  Configuration.reset :url
54
- assert_equal 'http://localhost:9200', Configuration.url
55
- assert_equal Client::Base, Configuration.client
53
+ assert_equal 'http://localhost:9200', Configuration.url
56
54
  end
57
55
 
58
56
  should "allow to reset the configuration for all properties" do
59
- Configuration.url 'http://example.com'
60
- Configuration.client Client::Base
61
- assert_equal 'http://example.com', Configuration.url
62
- assert_equal Client::Base, Configuration.client
57
+ Configuration.url 'http://example.com'
58
+ Configuration.wrapper Hash
59
+ assert_equal 'http://example.com', Configuration.url
63
60
  Configuration.reset
64
- assert_equal 'http://localhost:9200', Configuration.url
65
- assert_equal Client::RestClient, Configuration.client
61
+ assert_equal 'http://localhost:9200', Configuration.url
62
+ assert_equal Client::RestClient, Configuration.client
66
63
  end
67
64
  end
68
65
 
@@ -101,10 +101,42 @@ module Tire
101
101
 
102
102
  context "when storing" do
103
103
 
104
- should "properly set type from args" do
105
- Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/", '{"title":"Test"}').returns(mock_response('{"ok":true,"_id":"test"}')).twice
106
- @index.store 'article', :title => 'Test'
107
- @index.store :article, :title => 'Test'
104
+ should "set type from Hash :type property" do
105
+ Configuration.client.expects(:post).with do |url,document|
106
+ url == "#{Configuration.url}/dummy/article/"
107
+ end.returns(mock_response('{"ok":true,"_id":"test"}'))
108
+ @index.store :type => 'article', :title => 'Test'
109
+ end
110
+
111
+ should "set type from Hash :_type property" do
112
+ Configuration.client.expects(:post).with do |url,document|
113
+ url == "#{Configuration.url}/dummy/article/"
114
+ end.returns(mock_response('{"ok":true,"_id":"test"}'))
115
+ @index.store :_type => 'article', :title => 'Test'
116
+ end
117
+
118
+ should "set type from Object _type method" do
119
+ Configuration.client.expects(:post).with do |url,document|
120
+ url == "#{Configuration.url}/dummy/article/"
121
+ end.returns(mock_response('{"ok":true,"_id":"test"}'))
122
+
123
+ article = Class.new do
124
+ def _type; 'article'; end
125
+ def to_indexed_json; "{}"; end
126
+ end.new
127
+ @index.store article
128
+ end
129
+
130
+ should "set type from Object type method" do
131
+ Configuration.client.expects(:post).with do |url,document|
132
+ url == "#{Configuration.url}/dummy/article/"
133
+ end.returns(mock_response('{"ok":true,"_id":"test"}'))
134
+
135
+ article = Class.new do
136
+ def type; 'article'; end
137
+ def to_indexed_json; "{}"; end
138
+ end.new
139
+ @index.store article
108
140
  end
109
141
 
110
142
  should "set default type" do
@@ -151,7 +183,7 @@ module Tire
151
183
 
152
184
  Configuration.client.stubs(:post).with("#{Configuration.url}/dummy/article/", '{"title":"Test"}').
153
185
  returns(mock_response('{"ok":true,"_id":"id-1"}'))
154
- @index.store :article, :title => 'Test'
186
+ @index.store :type => 'article', :title => 'Test'
155
187
  end
156
188
 
157
189
  should "return document in default wrapper" do
@@ -182,21 +214,27 @@ module Tire
182
214
  assert_equal 'Test', article.title
183
215
  end
184
216
 
217
+ should "raise error when no ID passed" do
218
+ assert_raise ArgumentError do
219
+ @index.retrieve 'article', nil
220
+ end
221
+ end
222
+
185
223
  end
186
224
 
187
225
  context "when removing" do
188
226
 
189
- should "properly set type from args" do
190
- Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/article/").
191
- returns('{"ok":true,"_id":"test"}').twice
192
- @index.remove 'article', :title => 'Test'
193
- @index.remove :article, :title => 'Test'
227
+ should "get type from document" do
228
+ Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/article/1").
229
+ returns('{"ok":true,"_id":"1"}').twice
230
+ @index.remove :id => 1, :type => 'article', :title => 'Test'
231
+ @index.remove :id => 1, :type => 'article', :title => 'Test'
194
232
  end
195
233
 
196
234
  should "set default type" do
197
- Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/").
198
- returns('{"ok":true,"_id":"test"}')
199
- @index.remove :title => 'Test'
235
+ Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/1").
236
+ returns('{"ok":true,"_id":"1"}')
237
+ @index.remove :id => 1, :title => 'Test'
200
238
  end
201
239
 
202
240
  should "get ID from hash" do
@@ -212,10 +250,16 @@ module Tire
212
250
  @index.remove document
213
251
  end
214
252
 
215
- should "get ID from arguments" do
216
- Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/document/1").
253
+ should "get type and ID from arguments" do
254
+ Configuration.client.expects(:delete).with("#{Configuration.url}/dummy/article/1").
217
255
  returns('{"ok":true,"_id":"1"}')
218
- @index.remove :document, 1
256
+ @index.remove :article, 1
257
+ end
258
+
259
+ should "raise error when no ID passed" do
260
+ assert_raise ArgumentError do
261
+ @index.remove :document, nil
262
+ end
219
263
  end
220
264
 
221
265
  end
@@ -275,8 +319,12 @@ module Tire
275
319
  end
276
320
  end
277
321
 
278
- should_eventually "raise exception when collection item does not have ID" do
279
- # TODO: raise exception when collection item does not have ID
322
+ should "raise exception when collection item does not have ID" do
323
+ Configuration.client.expects(:post).with { |url, json| url == "#{Configuration.url}/_bulk" }
324
+ STDERR.expects(:puts).once
325
+
326
+ documents = [ { :title => 'Bogus' }, { :title => 'Real', :id => 1 } ]
327
+ ActiveModelArticle.elasticsearch_index.bulk_store documents
280
328
  end
281
329
 
282
330
  end
@@ -479,7 +527,7 @@ module Tire
479
527
  end.
480
528
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
481
529
 
482
- matches = @index.percolate :article, :title => 'Test'
530
+ matches = @index.percolate :type => 'article', :title => 'Test'
483
531
  assert_equal ["alerts"], matches
484
532
  end
485
533
 
@@ -502,13 +550,13 @@ module Tire
502
550
  should "percolate document against all registered queries" do
503
551
  Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/?percolate=*", '{"title":"Test"}').
504
552
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
505
- @index.store :article, {:title => 'Test'}, {:percolate => true}
553
+ @index.store( {:type => 'article', :title => 'Test'}, {:percolate => true} )
506
554
  end
507
555
 
508
556
  should "percolate document against specific queries" do
509
557
  Configuration.client.expects(:post).with("#{Configuration.url}/dummy/article/?percolate=tag:alerts", '{"title":"Test"}').
510
558
  returns(mock_response('{"ok":true,"_id":"test","matches":["alerts"]}'))
511
- response = @index.store :article, {:title => 'Test'}, {:percolate => 'tag:alerts'}
559
+ response = @index.store( {:type => 'article', :title => 'Test'}, {:percolate => 'tag:alerts'} )
512
560
  assert_equal response['matches'], ['alerts']
513
561
  end
514
562
 
@@ -96,7 +96,7 @@ module Tire
96
96
  document = collection.first
97
97
 
98
98
  assert_instance_of ActiveModelArticle, document
99
- assert_not_nil document.score
99
+ assert_not_nil document._score
100
100
  assert_equal 1, document.id
101
101
  assert_equal 'Article', document.title
102
102
  end
@@ -139,26 +139,26 @@ module Tire
139
139
  end
140
140
 
141
141
  should "allow to pass :order option" do
142
- Tire::Search::Sort.any_instance.expects(:title)
142
+ Tire::Search::Sort.any_instance.expects(:by).with('title', nil)
143
143
 
144
144
  ActiveModelArticle.search @q, :order => 'title'
145
145
  end
146
146
 
147
147
  should "allow to pass :sort option as :order option" do
148
- Tire::Search::Sort.any_instance.expects(:title)
148
+ Tire::Search::Sort.any_instance.expects(:by).with('title', nil)
149
149
 
150
150
  ActiveModelArticle.search @q, :sort => 'title'
151
151
  end
152
152
 
153
153
  should "allow to specify sort direction" do
154
- Tire::Search::Sort.any_instance.expects(:title).with('DESC')
154
+ Tire::Search::Sort.any_instance.expects(:by).with('title', 'DESC')
155
155
 
156
156
  ActiveModelArticle.search @q, :order => 'title DESC'
157
157
  end
158
158
 
159
159
  should "allow to specify more fields to sort on" do
160
- Tire::Search::Sort.any_instance.expects(:title).with('DESC')
161
- Tire::Search::Sort.any_instance.expects(:field).with('author.name', nil)
160
+ Tire::Search::Sort.any_instance.expects(:by).with('title', 'DESC')
161
+ Tire::Search::Sort.any_instance.expects(:by).with('author.name', nil)
162
162
 
163
163
  ActiveModelArticle.search @q, :order => ['title DESC', 'author.name']
164
164
  end
@@ -417,8 +417,8 @@ module Tire
417
417
  should "mark the instance for percolation on index update" do
418
418
  @article.percolate = true
419
419
 
420
- Tire::Index.any_instance.expects(:store).with do |type,doc,options|
421
- # p [type,doc,options]
420
+ Tire::Index.any_instance.expects(:store).with do |doc,options|
421
+ # p [doc,options]
422
422
  options[:percolate] == true
423
423
  end.returns(MultiJson.decode('{"ok":true,"_id":"test","matches":["alerts"]}'))
424
424
 
@@ -426,8 +426,8 @@ module Tire
426
426
  end
427
427
 
428
428
  should "not percolate document on index update when not set for percolation" do
429
- Tire::Index.any_instance.expects(:store).with do |type,doc,options|
430
- # p [type,doc,options]
429
+ Tire::Index.any_instance.expects(:store).with do |doc,options|
430
+ # p [doc,options]
431
431
  options[:percolate] == nil
432
432
  end.returns(MultiJson.decode('{"ok":true,"_id":"test"}'))
433
433
 
@@ -455,8 +455,8 @@ module Tire
455
455
  percolate!
456
456
  end
457
457
 
458
- Tire::Index.any_instance.expects(:store).with do |type,doc,options|
459
- # p [type,doc,options]
458
+ Tire::Index.any_instance.expects(:store).with do |doc,options|
459
+ # p [doc,options]
460
460
  options[:percolate] == true
461
461
  end.returns(MultiJson.decode('{"ok":true,"_id":"test","matches":["alerts"]}'))
462
462
 
@@ -472,7 +472,7 @@ module Tire
472
472
  percolated = ActiveModelArticleWithPercolation.new :title => 'Percolate me!'
473
473
 
474
474
  Tire::Index.any_instance.expects(:store).
475
- with do |type,doc,options|
475
+ with do |doc,options|
476
476
  doc == percolated &&
477
477
  options[:percolate] == true
478
478
  end.
@@ -491,7 +491,7 @@ module Tire
491
491
  percolated = ActiveModelArticleWithPercolation.new :title => 'Percolate me!'
492
492
 
493
493
  Tire::Index.any_instance.expects(:store).
494
- with do |type,doc,options|
494
+ with do |doc,options|
495
495
  doc == percolated &&
496
496
  options[:percolate] == 'tags:alert'
497
497
  end.
@@ -6,41 +6,74 @@ module Tire::Search
6
6
 
7
7
  context "Sort" do
8
8
 
9
- should "be serialized to JSON" do
10
- assert_respond_to Sort.new, :to_json
11
- end
9
+ context "with the old interface" do
12
10
 
13
- should "encode simple strings" do
14
- assert_equal [:foo].to_json, Sort.new.foo.to_json
15
- end
11
+ should "encode simple strings" do
12
+ assert_equal [:foo].to_json, Sort.new.foo.to_json
13
+ end
16
14
 
17
- should "encode method arguments" do
18
- assert_equal [:foo => 'desc'].to_json, Sort.new.foo('desc').to_json
19
- end
15
+ should "encode method arguments" do
16
+ assert_equal [:foo => 'desc'].to_json, Sort.new.foo('desc').to_json
17
+ end
20
18
 
21
- should "encode hash" do
22
- assert_equal [ :foo => { :reverse => true } ].to_json, Sort.new.foo(:reverse => true).to_json
23
- end
19
+ should "encode hash" do
20
+ assert_equal [ :foo => { :reverse => true } ].to_json, Sort.new.foo(:reverse => true).to_json
21
+ end
24
22
 
25
- should "encode multiple sort fields" do
26
- assert_equal [:foo, :bar].to_json, Sort.new.foo.bar.to_json
27
- end
23
+ should "encode multiple sort fields" do
24
+ assert_equal [:foo, :bar].to_json, Sort.new.foo.bar.to_json
25
+ end
28
26
 
29
- should "encode fields when passed as a block to constructor" do
30
- s = Sort.new do
31
- foo
32
- bar 'desc'
33
- _score
27
+ should "encode fields when passed as a block to constructor with deprecated interface" do
28
+ s = Sort.new do
29
+ foo
30
+ bar 'desc'
31
+ _score
32
+ end
33
+ assert_equal [ :foo, {:bar => 'desc'}, :_score ].to_json, s.to_json
34
34
  end
35
- assert_equal [ :foo, {:bar => 'desc'}, :_score ].to_json, s.to_json
35
+
36
36
  end
37
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
38
+ context "with the new interface" do
39
+
40
+ should "be serialized to JSON" do
41
+ assert_respond_to Sort.new, :to_json
42
+ end
43
+
44
+ should "encode simple strings" do
45
+ assert_equal [:foo].to_json, Sort.new.by(:foo).to_json
46
+ end
47
+
48
+ should "encode method arguments" do
49
+ assert_equal [:foo => 'desc'].to_json, Sort.new.by(:foo, 'desc').to_json
50
+ end
51
+
52
+ should "encode hash" do
53
+ assert_equal [ :foo => { :reverse => true } ].to_json, Sort.new.by(:foo, :reverse => true).to_json
54
+ end
55
+
56
+ should "encode multiple sort fields in chain" do
57
+ assert_equal [:foo, :bar].to_json, Sort.new.by(:foo).by(:bar).to_json
58
+ end
59
+
60
+ should "encode fields when passed as a block to constructor" do
61
+ s = Sort.new do
62
+ by :foo
63
+ by :bar, 'desc'
64
+ by :_score
65
+ end
66
+ assert_equal [ :foo, {:bar => 'desc'}, :_score ].to_json, s.to_json
67
+ end
68
+
69
+ should "encode fields deeper in json" do
70
+ s = Sort.new { by 'author.name' }
71
+ assert_equal [ 'author.name' ].to_json, s.to_json
72
+
73
+ s = Sort.new { by 'author.name', 'desc' }
74
+ assert_equal [ {'author.name' => 'desc'} ].to_json, s.to_json
75
+ end
41
76
 
42
- s = Sort.new { field 'author.name', :desc }
43
- assert_equal [ {'author.name' => 'desc'} ].to_json, s.to_json
44
77
  end
45
78
 
46
79
  end
@@ -67,7 +67,11 @@ module Tire
67
67
 
68
68
  should "allow chaining" do
69
69
  assert_nothing_raised do
70
- Search::Search.new('index').query { }.sort { title 'desc' }.size(5).sort { name 'asc' }.from(1)
70
+ Search::Search.new('index').query { }.
71
+ sort { by :title, 'desc' }.
72
+ size(5).
73
+ sort { by :name, 'asc' }.
74
+ from(1)
71
75
  end
72
76
  end
73
77
 
@@ -108,8 +112,8 @@ module Tire
108
112
  should "allow sorting by multiple fields" do
109
113
  s = Search::Search.new('index') do
110
114
  sort do
111
- title 'desc'
112
- _score
115
+ by :title, 'desc'
116
+ by :_score
113
117
  end
114
118
  end
115
119
  hash = MultiJson.decode( s.to_json )
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tire
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 14
10
- version: 0.1.14
9
+ - 15
10
+ version: 0.1.15
11
11
  platform: ruby
12
12
  authors:
13
13
  - Karel Minarik
@@ -15,8 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-06-27 00:00:00 +02:00
19
- default_executable:
18
+ date: 2011-07-04 00:00:00 Z
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
21
  name: rake
@@ -336,7 +335,6 @@ files:
336
335
  - test/unit/search_test.rb
337
336
  - test/unit/tire_test.rb
338
337
  - tire.gemspec
339
- has_rdoc: true
340
338
  homepage: http://github.com/karmi/tire
341
339
  licenses: []
342
340
 
@@ -349,12 +347,12 @@ post_install_message: |
349
347
 
350
348
  IMPORTANT CHANGES LATELY:
351
349
 
352
- # Added `<after/before>_update_elastic_search_index` callbacks for models
353
- # Added percolator support for DSL and ActiveModel
354
- # Lazy loading of ResultsCollection into memory
355
- # Fixes for debug logging
350
+ # Cleanup of code for getting document type, id, JSON serialization
351
+ # Bunch of deprecations: sorting, passing document type to store/remove
352
+ # Displaying a warning when no ID is passed when storing in bulk
353
+ # Correct handling of import for Mongoid/Kaminari combos
356
354
 
357
- See the full changelog at <http://github.com/karmi/tire/commits/v0.1.14>.
355
+ See the full changelog at <http://github.com/karmi/tire/commits/v0.1.15>.
358
356
 
359
357
  --------------------------------------------------------------------------------
360
358
 
@@ -385,7 +383,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
385
383
  requirements: []
386
384
 
387
385
  rubyforge_project: tire
388
- rubygems_version: 1.5.0
386
+ rubygems_version: 1.8.5
389
387
  signing_key:
390
388
  specification_version: 3
391
389
  summary: Ruby client for ElasticSearch