tire 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.travis.yml +18 -2
  2. data/README.markdown +2 -2
  3. data/examples/tire-dsl.rb +1 -1
  4. data/lib/tire.rb +1 -0
  5. data/lib/tire/http/clients/faraday.rb +71 -0
  6. data/lib/tire/index.rb +30 -7
  7. data/lib/tire/logger.rb +1 -1
  8. data/lib/tire/model/persistence.rb +2 -3
  9. data/lib/tire/model/persistence/finders.rb +2 -2
  10. data/lib/tire/model/persistence/storage.rb +3 -5
  11. data/lib/tire/model/search.rb +2 -0
  12. data/lib/tire/results/collection.rb +8 -8
  13. data/lib/tire/results/item.rb +4 -1
  14. data/lib/tire/search.rb +30 -2
  15. data/lib/tire/search/facet.rb +5 -1
  16. data/lib/tire/search/query.rb +36 -1
  17. data/lib/tire/search/script_field.rb +23 -0
  18. data/lib/tire/tasks.rb +1 -1
  19. data/lib/tire/version.rb +8 -15
  20. data/test/integration/dis_max_queries_test.rb +68 -0
  21. data/test/integration/facets_test.rb +27 -0
  22. data/test/integration/index_store_test.rb +17 -1
  23. data/test/integration/index_update_document_test.rb +111 -0
  24. data/test/integration/persistent_model_test.rb +13 -0
  25. data/test/integration/prefix_query_test.rb +43 -0
  26. data/test/integration/reindex_test.rb +10 -0
  27. data/test/integration/script_fields_test.rb +38 -0
  28. data/test/test_helper.rb +6 -1
  29. data/test/unit/index_test.rb +58 -5
  30. data/test/unit/model_persistence_test.rb +14 -2
  31. data/test/unit/model_search_test.rb +14 -0
  32. data/test/unit/results_item_test.rb +9 -2
  33. data/test/unit/rubyext_test.rb +6 -0
  34. data/test/unit/search_query_test.rb +61 -2
  35. data/test/unit/search_script_field_test.rb +26 -0
  36. data/test/unit/search_test.rb +46 -2
  37. data/tire.gemspec +1 -1
  38. metadata +62 -53
data/.travis.yml CHANGED
@@ -2,12 +2,28 @@
2
2
  # Configuration file for http://travis-ci.org/#!/karmi/tire
3
3
  # ---------------------------------------------------------
4
4
 
5
- script: "bundle exec rake test:unit"
5
+ language: ruby
6
6
 
7
7
  rvm:
8
- - 1.8.7
9
8
  - 1.9.3
9
+ - 1.8.7
10
10
  - ree
11
11
 
12
+ env:
13
+ - TEST_COMMAND="rake test:unit"
14
+ - TEST_COMMAND="rake test:integration"
15
+
16
+ script: "bundle exec $TEST_COMMAND"
17
+
18
+ before_install:
19
+ - sudo service elasticsearch start
20
+
21
+ matrix:
22
+ exclude:
23
+ - rvm: 1.8.7
24
+ env: TEST_COMMAND="rake test:integration"
25
+ - rvm: ree
26
+ env: TEST_COMMAND="rake test:integration"
27
+
12
28
  notifications:
13
29
  disable: true
data/README.markdown CHANGED
@@ -675,7 +675,7 @@ OK. All this time we have been talking about `ActiveRecord` models, since
675
675
  it is a reasonable _Rails_' default for the storage layer.
676
676
 
677
677
  But what if you use another database such as [MongoDB](http://www.mongodb.org/),
678
- another object mapping library, such as [Mongoid](http://mongoid.org/)?
678
+ another object mapping library, such as [Mongoid](http://mongoid.org/) or [MongoMapper](http://mongomapper.com/)?
679
679
 
680
680
  Well, things stay mostly the same:
681
681
 
@@ -691,7 +691,7 @@ Well, things stay mostly the same:
691
691
  # These Mongo guys sure do get funky with their IDs in +serializable_hash+, let's fix it.
692
692
  #
693
693
  def to_indexed_json
694
- self.as_json
694
+ self.to_json
695
695
  end
696
696
 
697
697
  end
data/examples/tire-dsl.rb CHANGED
@@ -12,7 +12,7 @@
12
12
  # from <https://github.com/karmi/tire>.
13
13
  #
14
14
  # By following these instructions you should have the search running
15
- # on a sane operation system in less then 10 minutes.
15
+ # on a sane operating system in less then 10 minutes.
16
16
 
17
17
  # Note, that this file can be executed directly:
18
18
  #
data/lib/tire.rb CHANGED
@@ -25,6 +25,7 @@ require 'tire/search/facet'
25
25
  require 'tire/search/filter'
26
26
  require 'tire/search/highlight'
27
27
  require 'tire/search/scan'
28
+ require 'tire/search/script_field'
28
29
  require 'tire/results/pagination'
29
30
  require 'tire/results/collection'
30
31
  require 'tire/results/item'
@@ -0,0 +1,71 @@
1
+ require 'faraday'
2
+
3
+ # A Faraday-based HTTP client, which allows you to choose a HTTP client.
4
+ #
5
+ # See <https://github.com/technoweenie/faraday/tree/master/lib/faraday/adapter>
6
+ #
7
+ # NOTE: Tire will switch to Faraday for the HTTP abstraction layer. This client is a temporary solution.
8
+ #
9
+ # Example:
10
+ # --------
11
+ #
12
+ # require 'typhoeus'
13
+ # require 'tire/http/clients/faraday'
14
+ #
15
+ # Tire.configure do |config|
16
+ #
17
+ # # Unless specified, tire will use Faraday.default_adapter and no middleware
18
+ # Tire::HTTP::Client::Faraday.faraday_middleware = Proc.new do |builder|
19
+ # builder.adapter :typhoeus
20
+ # end
21
+ #
22
+ # config.client(Tire::HTTP::Client::Faraday)
23
+ #
24
+ # end
25
+ #
26
+ #
27
+ module Tire
28
+ module HTTP
29
+ module Client
30
+ class Faraday
31
+
32
+ # Default middleware stack.
33
+ DEFAULT_MIDDLEWARE = Proc.new do |builder|
34
+ builder.adapter ::Faraday.default_adapter
35
+ end
36
+
37
+ class << self
38
+ # A customized stack of Faraday middleware that will be used to make each request.
39
+ attr_accessor :faraday_middleware
40
+
41
+ def get(url, data = nil)
42
+ request(:get, url, data)
43
+ end
44
+
45
+ def post(url, data)
46
+ request(:post, url, data)
47
+ end
48
+
49
+ def put(url, data)
50
+ request(:put, url, data)
51
+ end
52
+
53
+ def delete(url, data = nil)
54
+ request(:delete, url, data)
55
+ end
56
+
57
+ def head(url)
58
+ request(:head, url)
59
+ end
60
+
61
+ private
62
+ def request(method, url, data = nil)
63
+ conn = ::Faraday.new( &(faraday_middleware || DEFAULT_MIDDLEWARE) )
64
+ response = conn.run_request(method, url, data, nil)
65
+ Response.new(response.body, response.status, response.headers)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/tire/index.rb CHANGED
@@ -103,9 +103,9 @@ module Tire
103
103
  count = 0
104
104
 
105
105
  begin
106
- response = Configuration.client.post("#{url}/_bulk", payload.join("\n"))
107
- raise RuntimeError, "#{response.code} > #{response.body}" if response.failure?
108
- response
106
+ @response = Configuration.client.post("#{url}/_bulk", payload.join("\n"))
107
+ raise RuntimeError, "#{@response.code} > #{@response.body}" if @response && @response.failure?
108
+ @response
109
109
  rescue StandardError => error
110
110
  if count < tries
111
111
  count += 1
@@ -149,10 +149,17 @@ module Tire
149
149
  new_index = Index.new(name)
150
150
  new_index.create(options) unless new_index.exists?
151
151
 
152
+ transform = options.delete(:transform)
153
+
152
154
  Search::Scan.new(self.name, &block).each do |results|
153
- new_index.bulk_store results.map do |document|
154
- document.to_hash.except(:type, :_index, :_explanation, :_score, :_version, :highlight, :sort)
155
+
156
+ documents = results.map do |document|
157
+ document = document.to_hash.except(:type, :_index, :_explanation, :_score, :_version, :highlight, :sort)
158
+ document = transform.call(document) if transform
159
+ document
155
160
  end
161
+
162
+ new_index.bulk_store documents
156
163
  end
157
164
  end
158
165
 
@@ -198,6 +205,22 @@ module Tire
198
205
  logged(id, curl)
199
206
  end
200
207
 
208
+ def update(type, id, payload={}, options={})
209
+ raise ArgumentError, "Please pass a document type" unless type
210
+ raise ArgumentError, "Please pass a document ID" unless id
211
+ raise ArgumentError, "Please pass a script in the payload hash" unless payload[:script]
212
+
213
+ type = Utils.escape(type)
214
+ url = "#{self.url}/#{type}/#{id}/_update"
215
+ url += "?#{options.to_param}" unless options.keys.empty?
216
+ @response = Configuration.client.post url, MultiJson.encode(payload)
217
+ MultiJson.decode(@response.body)
218
+
219
+ ensure
220
+ curl = %Q|curl -X POST "#{url}" -d '#{MultiJson.encode(payload)}'|
221
+ logged(id, curl)
222
+ end
223
+
201
224
  def refresh
202
225
  @response = Configuration.client.post "#{url}/_refresh", ''
203
226
 
@@ -243,7 +266,7 @@ module Tire
243
266
  MultiJson.decode(@response.body)['ok']
244
267
 
245
268
  ensure
246
- curl = %Q|curl -X PUT "#{Configuration.url}/_percolator/#{@name}/?pretty=1" -d '#{MultiJson.encode(options)}'|
269
+ curl = %Q|curl -X PUT "#{Configuration.url}/_percolator/#{@name}/#{name}?pretty=1" -d '#{MultiJson.encode(options)}'|
247
270
  logged('_percolator', curl)
248
271
  end
249
272
 
@@ -281,7 +304,7 @@ module Tire
281
304
 
282
305
  Configuration.logger.log_request endpoint, @name, curl
283
306
 
284
- code = @response ? @response.code : error.class rescue 200
307
+ code = @response ? @response.code : error.class rescue 'N/A'
285
308
 
286
309
  if Configuration.logger.level.to_s == 'debug'
287
310
  body = if @response
data/lib/tire/logger.rb CHANGED
@@ -9,7 +9,7 @@ module Tire
9
9
  end
10
10
  @device.sync = true if @device.respond_to?(:sync)
11
11
  @options = options
12
- at_exit { @device.close unless @device.closed? } if @device.respond_to?(:closed?) && @device.respond_to?(:close)
12
+ # at_exit { @device.close unless @device.closed? } if @device.respond_to?(:closed?) && @device.respond_to?(:close)
13
13
  end
14
14
 
15
15
  def level
@@ -51,9 +51,8 @@ module Tire
51
51
  end
52
52
 
53
53
  def self.search(*args, &block)
54
- # Update options Hash with the wrapper definition
55
- args.last.update(:wrapper => self) if args.last.is_a? Hash
56
- args << { :wrapper => self } unless args.any? { |a| a.is_a? Hash }
54
+ args.last.update(:wrapper => self, :version => true) if args.last.is_a? Hash
55
+ args << { :wrapper => self, :version => true } unless args.any? { |a| a.is_a? Hash }
57
56
 
58
57
  self.__search_without_persistence(*args, &block)
59
58
  end
@@ -41,7 +41,7 @@ module Tire
41
41
  old_wrapper = Tire::Configuration.wrapper
42
42
  Tire::Configuration.wrapper self
43
43
  s = Tire::Search::Search.new(index.name).query { all }
44
- s.results
44
+ s.version(true).results
45
45
  ensure
46
46
  Tire::Configuration.wrapper old_wrapper
47
47
  end
@@ -51,7 +51,7 @@ module Tire
51
51
  old_wrapper = Tire::Configuration.wrapper
52
52
  Tire::Configuration.wrapper self
53
53
  s = Tire::Search::Search.new(index.name).query { all }.size(1)
54
- s.results.first
54
+ s.version(true).results.first
55
55
  ensure
56
56
  Tire::Configuration.wrapper old_wrapper
57
57
  end
@@ -55,11 +55,9 @@ module Tire
55
55
  self.freeze
56
56
  end
57
57
 
58
- # TODO: Implement `new_record?` and clean up
59
-
60
- def destroyed?; !!@destroyed; end
61
-
62
- def persisted?; !!id; end
58
+ def destroyed? ; !!@destroyed; end
59
+ def persisted? ; !!id && !!_version; end
60
+ def new_record? ; !persisted?; end
63
61
 
64
62
  end
65
63
 
@@ -85,6 +85,8 @@ module Tire
85
85
  end
86
86
  end unless sort.empty?
87
87
 
88
+ if version = options.delete(:version); s.version(version); end
89
+
88
90
  if block_given?
89
91
  block.arity < 1 ? s.instance_eval(&block) : block.call(s)
90
92
  else
@@ -25,17 +25,17 @@ module Tire
25
25
  hits
26
26
  else
27
27
  hits.map do |h|
28
- document = {}
28
+ document = {}
29
29
 
30
- # Update the document with content and ID
31
- document = h['_source'] ? document.update( h['_source'] || {} ) : document.update( __parse_fields__(h['fields']) )
32
- document.update( {'id' => h['_id']} )
30
+ # Update the document with content and ID
31
+ document = h['_source'] ? document.update( h['_source'] || {} ) : document.update( __parse_fields__(h['fields']) )
32
+ document.update( {'id' => h['_id']} )
33
33
 
34
- # Update the document with meta information
35
- ['_score', '_type', '_index', '_version', 'sort', 'highlight', '_explanation'].each { |key| document.update( {key => h[key]} || {} ) }
34
+ # Update the document with meta information
35
+ ['_score', '_type', '_index', '_version', 'sort', 'highlight', '_explanation'].each { |key| document.update( {key => h[key]} || {} ) }
36
36
 
37
- # Return an instance of the "wrapper" class
38
- @wrapper.new(document)
37
+ # Return an instance of the "wrapper" class
38
+ @wrapper.new(document)
39
39
  end
40
40
  end
41
41
 
@@ -56,7 +56,10 @@ module Tire
56
56
  end
57
57
 
58
58
  def to_hash
59
- @attributes
59
+ @attributes.reduce({}) do |sum, item|
60
+ sum[ item.first ] = item.last.respond_to?(:to_hash) ? item.last.to_hash : item.last
61
+ sum
62
+ end
60
63
  end
61
64
 
62
65
  # Let's pretend we're someone else in Rails
data/lib/tire/search.rb CHANGED
@@ -4,10 +4,15 @@ module Tire
4
4
 
5
5
  class Search
6
6
 
7
- attr_reader :indices, :json, :query, :facets, :filters, :options, :explain
7
+ attr_reader :indices, :query, :facets, :filters, :options, :explain, :script_fields
8
8
 
9
9
  def initialize(indices=nil, options={}, &block)
10
- @indices = Array(indices)
10
+ if indices.is_a?(Hash)
11
+ set_indices_options(indices)
12
+ @indices = indices.keys
13
+ else
14
+ @indices = Array(indices)
15
+ end
11
16
  @types = Array(options.delete(:type)).map { |type| Utils.escape(type) }
12
17
  @options = options
13
18
 
@@ -16,6 +21,16 @@ module Tire
16
21
  block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
17
22
  end
18
23
 
24
+
25
+ def set_indices_options(indices)
26
+ indices.each do |index, index_options|
27
+ if index_options[:boost]
28
+ @indices_boost ||= {}
29
+ @indices_boost[index] = index_options[:boost]
30
+ end
31
+ end
32
+ end
33
+
19
34
  def results
20
35
  @results || (perform; @results)
21
36
  end
@@ -24,6 +39,10 @@ module Tire
24
39
  @response || (perform; @response)
25
40
  end
26
41
 
42
+ def json
43
+ @json || (perform; @json)
44
+ end
45
+
27
46
  def url
28
47
  Configuration.url + @path
29
48
  end
@@ -55,6 +74,12 @@ module Tire
55
74
  self
56
75
  end
57
76
 
77
+ def script_field(name, options={})
78
+ @script_fields ||= {}
79
+ @script_fields.merge! ScriptField.new(name, options).to_hash
80
+ self
81
+ end
82
+
58
83
  def highlight(*args)
59
84
  unless args.empty?
60
85
  @highlight = Highlight.new(*args)
@@ -88,6 +113,7 @@ module Tire
88
113
 
89
114
  def version(value)
90
115
  @version = value
116
+ self
91
117
  end
92
118
 
93
119
  def perform
@@ -110,6 +136,7 @@ module Tire
110
136
  def to_hash
111
137
  @options.delete(:payload) || begin
112
138
  request = {}
139
+ request.update( { :indices_boost => @indices_boost } ) if @indices_boost
113
140
  request.update( { :query => @query.to_hash } ) if @query
114
141
  request.update( { :sort => @sort.to_ary } ) if @sort
115
142
  request.update( { :facets => @facets.to_hash } ) if @facets
@@ -119,6 +146,7 @@ module Tire
119
146
  request.update( { :size => @size } ) if @size
120
147
  request.update( { :from => @from } ) if @from
121
148
  request.update( { :fields => @fields } ) if @fields
149
+ request.update( { :script_fields => @script_fields } ) if @script_fields
122
150
  request.update( { :version => @version } ) if @version
123
151
  request.update( { :explain => @explain } ) if @explain
124
152
  request
@@ -17,7 +17,11 @@ module Tire
17
17
  def terms(field, options={})
18
18
  size = options.delete(:size) || 10
19
19
  all_terms = options.delete(:all_terms) || false
20
- @value = { :terms => { :field => field, :size => size, :all_terms => all_terms }.update(options) }
20
+ @value = if field.is_a?(Enumerable) and not field.is_a?(String)
21
+ { :terms => { :fields => field }.update({ :size => size, :all_terms => all_terms }).update(options) }
22
+ else
23
+ { :terms => { :field => field }.update({ :size => size, :all_terms => all_terms }).update(options) }
24
+ end
21
25
  self
22
26
  end
23
27
 
@@ -34,6 +34,14 @@ module Tire
34
34
  @value
35
35
  end
36
36
 
37
+ def prefix(field, value, options={})
38
+ if options[:boost]
39
+ @value = { :prefix => { field => { :prefix => value, :boost => options[:boost] } } }
40
+ else
41
+ @value = { :prefix => { field => value } }
42
+ end
43
+ end
44
+
37
45
  def custom_score(options={}, &block)
38
46
  @custom_score ||= Query.new(&block)
39
47
  @value[:custom_score] = options
@@ -60,6 +68,13 @@ module Tire
60
68
  @value
61
69
  end
62
70
 
71
+ def dis_max(options={}, &block)
72
+ @dis_max ||= DisMaxQuery.new(options)
73
+ block.arity < 1 ? @dis_max.instance_eval(&block) : block.call(@dis_max) if block_given?
74
+ @value[:dis_max] = @dis_max.to_hash
75
+ @value
76
+ end
77
+
63
78
  def all
64
79
  @value = { :match_all => {} }
65
80
  @value
@@ -119,7 +134,6 @@ module Tire
119
134
  end
120
135
  end
121
136
 
122
-
123
137
  class FilteredQuery
124
138
  def initialize(&block)
125
139
  @value = {}
@@ -147,5 +161,26 @@ module Tire
147
161
  end
148
162
  end
149
163
 
164
+ class DisMaxQuery
165
+ def initialize(options={}, &block)
166
+ @options = options
167
+ @value = {}
168
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
169
+ end
170
+
171
+ def query(&block)
172
+ (@value[:queries] ||= []) << Query.new(&block).to_hash
173
+ @value
174
+ end
175
+
176
+ def to_hash
177
+ @value.update(@options)
178
+ end
179
+
180
+ def to_json
181
+ to_hash.to_json
182
+ end
183
+ end
184
+
150
185
  end
151
186
  end