tire 0.4.2 → 0.4.3

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 (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