tire 0.5.4 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -0
- data/README.markdown +49 -29
- data/examples/rails-application-template.rb +15 -15
- data/examples/tire-dsl.rb +24 -24
- data/lib/tire.rb +2 -1
- data/lib/tire/alias.rb +3 -3
- data/lib/tire/dsl.rb +17 -19
- data/lib/tire/index.rb +67 -10
- data/lib/tire/model/callbacks.rb +1 -1
- data/lib/tire/model/indexing.rb +2 -2
- data/lib/tire/model/naming.rb +1 -1
- data/lib/tire/model/percolate.rb +3 -3
- data/lib/tire/model/persistence.rb +1 -1
- data/lib/tire/model/persistence/attributes.rb +2 -2
- data/lib/tire/model/persistence/storage.rb +2 -2
- data/lib/tire/model/search.rb +8 -8
- data/lib/tire/multi_search.rb +4 -4
- data/lib/tire/rubyext/ruby_1_8.rb +1 -1
- data/lib/tire/search.rb +1 -1
- data/lib/tire/search/scan.rb +1 -1
- data/lib/tire/tasks.rb +1 -1
- data/lib/tire/version.rb +7 -11
- data/test/integration/active_record_searchable_test.rb +42 -0
- data/test/integration/dis_max_queries_test.rb +1 -1
- data/test/integration/dsl_search_test.rb +9 -0
- data/test/integration/index_mapping_test.rb +82 -7
- data/test/integration/persistent_model_test.rb +17 -0
- data/test/integration/sort_test.rb +16 -0
- data/test/models/active_record_models.rb +9 -0
- data/test/models/persistent_article.rb +1 -1
- data/test/models/persistent_article_in_index.rb +1 -1
- data/test/models/persistent_article_in_namespace.rb +1 -1
- data/test/models/persistent_article_with_percolation.rb +5 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/index_alias_test.rb +1 -1
- data/test/unit/index_test.rb +79 -1
- data/test/unit/model_persistence_test.rb +11 -1
- data/test/unit/model_search_test.rb +2 -2
- data/test/unit/tire_test.rb +11 -7
- data/tire.gemspec +4 -3
- metadata +61 -51
data/lib/tire.rb
CHANGED
@@ -6,7 +6,8 @@ require 'cgi'
|
|
6
6
|
|
7
7
|
require 'active_support/core_ext/object/to_param'
|
8
8
|
require 'active_support/core_ext/object/to_query'
|
9
|
-
require 'active_support/core_ext/hash/except
|
9
|
+
require 'active_support/core_ext/hash/except'
|
10
|
+
require 'active_support/json'
|
10
11
|
|
11
12
|
# Ruby 1.8 compatibility
|
12
13
|
require 'tire/rubyext/ruby_1_8' if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
|
data/lib/tire/alias.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Tire
|
2
2
|
|
3
|
-
# Represents an *alias* in
|
3
|
+
# Represents an *alias* in _Elasticsearch_. An alias may point to one or multiple
|
4
4
|
# indices, for instance to separate physical indices into logical entities, where
|
5
5
|
# each user has a "virtual index" or for setting up "sliding window" scenarios.
|
6
6
|
#
|
@@ -156,7 +156,7 @@ module Tire
|
|
156
156
|
type ? (@attributes[:filter] = Search::Filter.new(type, *options).to_hash and return self ) : @attributes[:filter]
|
157
157
|
end
|
158
158
|
|
159
|
-
# Save the alias in
|
159
|
+
# Save the alias in _Elasticsearch_
|
160
160
|
#
|
161
161
|
def save
|
162
162
|
@response = Configuration.client.post "#{Configuration.url}/_aliases", to_json
|
@@ -184,7 +184,7 @@ module Tire
|
|
184
184
|
{ :actions => actions }
|
185
185
|
end
|
186
186
|
|
187
|
-
# Return alias serialized in JSON for
|
187
|
+
# Return alias serialized in JSON for _Elasticsearch_
|
188
188
|
#
|
189
189
|
def to_json(options=nil)
|
190
190
|
as_json.to_json
|
data/lib/tire/dsl.rb
CHANGED
@@ -5,29 +5,27 @@ module Tire
|
|
5
5
|
Configuration.class_eval(&block)
|
6
6
|
end
|
7
7
|
|
8
|
-
def search(indices=nil,
|
8
|
+
def search(indices=nil, payload={}, &block)
|
9
9
|
if block_given?
|
10
|
-
Search::Search.new(indices,
|
10
|
+
Search::Search.new(indices, payload, &block)
|
11
11
|
else
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
Search::Search.new(indices)
|
12
|
+
raise ArgumentError, "Please pass a Ruby Hash or an object with `to_hash` method, not #{payload.class}" \
|
13
|
+
unless payload.respond_to?(:to_hash)
|
14
|
+
|
15
|
+
# Extract URL parameters from payload
|
16
|
+
#
|
17
|
+
search_params = %w| search_type routing scroll from size timeout |
|
18
|
+
|
19
|
+
options = search_params.inject({}) do |sum,item|
|
20
|
+
if param = (payload.delete(item) || payload.delete(item.to_sym))
|
21
|
+
sum[item.to_sym] = param
|
22
|
+
end
|
23
|
+
sum
|
25
24
|
end
|
25
|
+
|
26
|
+
options.update(:payload => payload) unless payload.empty?
|
27
|
+
Search::Search.new(indices, options)
|
26
28
|
end
|
27
|
-
rescue Exception => error
|
28
|
-
STDERR.puts "[REQUEST FAILED] #{error.class} #{error.message rescue nil}\n"
|
29
|
-
raise
|
30
|
-
ensure
|
31
29
|
end
|
32
30
|
|
33
31
|
# Build and perform a [multi-search](http://elasticsearch.org/guide/reference/api/multi-search.html)
|
data/lib/tire/index.rb
CHANGED
@@ -52,9 +52,64 @@ module Tire
|
|
52
52
|
alias_name ? Alias.all(@name).select { |a| a.name == alias_name }.first : Alias.all(@name)
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
# Get or update the index mapping
|
56
|
+
#
|
57
|
+
# Without arguments, returns the index mapping as a Hash
|
58
|
+
#
|
59
|
+
# When passed arguments, attempts to update the index mapping:
|
60
|
+
#
|
61
|
+
# index.mapping 'article', properties: { body: { type: "string" } }
|
62
|
+
#
|
63
|
+
# You can pass the `ignore_conflicts` option as a part of the Hash:
|
64
|
+
#
|
65
|
+
# index.mapping 'article', properties: { body: { type: "string" } }, ignore_conflicts: true
|
66
|
+
#
|
67
|
+
def mapping(*args)
|
68
|
+
args.empty? ? get_mapping : put_mapping(*args)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Raises an exception for unsuccessful responses
|
72
|
+
#
|
73
|
+
def mapping!(*args)
|
74
|
+
mapping(*args)
|
75
|
+
raise RuntimeError, response.body unless response.success?
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_mapping
|
56
79
|
@response = Configuration.client.get("#{url}/_mapping")
|
57
|
-
MultiJson.decode(@response.body)[@name]
|
80
|
+
result = MultiJson.decode(@response.body)[@name]
|
81
|
+
@response.success? ? result : false
|
82
|
+
ensure
|
83
|
+
curl = %Q|curl -X GET "#{url}/_mapping?pretty"|
|
84
|
+
logged("GET MAPPING", curl)
|
85
|
+
end
|
86
|
+
|
87
|
+
def put_mapping(type, mapping)
|
88
|
+
params = {}
|
89
|
+
if ignore_conflicts = mapping.delete(:ignore_conflicts) || mapping.delete("ignore_conflicts")
|
90
|
+
params[:ignore_conflicts] = ignore_conflicts
|
91
|
+
end
|
92
|
+
|
93
|
+
url = "#{self.url}/#{type}/_mapping"
|
94
|
+
url += "?#{params.to_param}" unless params.empty?
|
95
|
+
|
96
|
+
payload = { type => mapping }.to_json
|
97
|
+
|
98
|
+
@response = Configuration.client.put url, payload
|
99
|
+
result = MultiJson.decode(@response.body)
|
100
|
+
@response.success? ? result : false
|
101
|
+
ensure
|
102
|
+
curl = %Q|curl -X PUT "#{url}" -d '#{payload}'|
|
103
|
+
logged("PUT MAPPING #{type}", curl)
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete_mapping(type)
|
107
|
+
url = "#{self.url}/#{type}"
|
108
|
+
@response = Configuration.client.delete(url)
|
109
|
+
@response.success?
|
110
|
+
ensure
|
111
|
+
curl = %Q|curl -X DELETE "#{url}"|
|
112
|
+
logged("DELETE MAPPING #{type}", curl)
|
58
113
|
end
|
59
114
|
|
60
115
|
def settings
|
@@ -79,10 +134,11 @@ module Tire
|
|
79
134
|
|
80
135
|
params[:parent] = options[:parent] if options[:parent]
|
81
136
|
params[:routing] = options[:routing] if options[:routing]
|
137
|
+
params[:replication] = options[:replication] if options[:replication]
|
82
138
|
|
83
139
|
params_encoded = params.empty? ? '' : "?#{params.to_param}"
|
84
140
|
|
85
|
-
url = id ? "#{self.url}/#{type}/#{id}#{params_encoded}" : "#{self.url}/#{type}/#{params_encoded}"
|
141
|
+
url = id ? "#{self.url}/#{type}/#{Utils.escape(id)}#{params_encoded}" : "#{self.url}/#{type}/#{params_encoded}"
|
86
142
|
|
87
143
|
@response = Configuration.client.post url, document
|
88
144
|
MultiJson.decode(@response.body)
|
@@ -104,6 +160,8 @@ module Tire
|
|
104
160
|
# Shortcut methods `bulk_store`, `bulk_delete` and `bulk_create` are available.
|
105
161
|
#
|
106
162
|
def bulk(action, documents, options={})
|
163
|
+
return false if documents.empty?
|
164
|
+
|
107
165
|
# TODO: A more Ruby-like DSL notation should be supported:
|
108
166
|
#
|
109
167
|
# Tire.index('myindex').bulk do
|
@@ -111,7 +169,7 @@ module Tire
|
|
111
169
|
# delete id: 1
|
112
170
|
# # ...
|
113
171
|
# end
|
114
|
-
|
172
|
+
|
115
173
|
payload = documents.map do |document|
|
116
174
|
type = get_type_from_document(document, :escape => false) # Do not URL-escape the _type
|
117
175
|
id = get_id_from_document(document)
|
@@ -236,20 +294,19 @@ module Tire
|
|
236
294
|
end
|
237
295
|
raise ArgumentError, "Please pass a document ID" unless id
|
238
296
|
|
239
|
-
url = "#{self.url}/#{type}/#{id}"
|
297
|
+
url = "#{self.url}/#{type}/#{Utils.escape(id)}"
|
240
298
|
result = Configuration.client.delete url
|
241
299
|
MultiJson.decode(result.body) if result.success?
|
242
300
|
|
243
301
|
ensure
|
244
302
|
curl = %Q|curl -X DELETE "#{url}"|
|
245
|
-
logged(id, curl)
|
303
|
+
logged("#{type}/#{id}", curl)
|
246
304
|
end
|
247
305
|
|
248
306
|
def retrieve(type, id, options={})
|
249
307
|
raise ArgumentError, "Please pass a document ID" unless id
|
250
308
|
|
251
|
-
|
252
|
-
url = "#{self.url}/#{type}/#{id}"
|
309
|
+
url = "#{self.url}/#{Utils.escape(type)}/#{Utils.escape(id)}"
|
253
310
|
|
254
311
|
params = {}
|
255
312
|
params[:routing] = options[:routing] if options[:routing]
|
@@ -271,7 +328,7 @@ module Tire
|
|
271
328
|
|
272
329
|
ensure
|
273
330
|
curl = %Q|curl -X GET "#{url}"|
|
274
|
-
logged(id, curl)
|
331
|
+
logged("#{type}/#{id}", curl)
|
275
332
|
end
|
276
333
|
|
277
334
|
def update(type, id, payload={}, options={})
|
@@ -280,7 +337,7 @@ module Tire
|
|
280
337
|
raise ArgumentError, "Please pass a script or partial document in the payload hash" unless payload[:script] || payload[:doc]
|
281
338
|
|
282
339
|
type = Utils.escape(type)
|
283
|
-
url = "#{self.url}/#{type}/#{id}/_update"
|
340
|
+
url = "#{self.url}/#{type}/#{Utils.escape(id)}/_update"
|
284
341
|
url += "?#{options.to_param}" unless options.keys.empty?
|
285
342
|
@response = Configuration.client.post url, MultiJson.encode(payload)
|
286
343
|
MultiJson.decode(@response.body)
|
data/lib/tire/model/callbacks.rb
CHANGED
@@ -2,7 +2,7 @@ module Tire
|
|
2
2
|
module Model
|
3
3
|
|
4
4
|
# Main module containing the infrastructure for automatic updating
|
5
|
-
# of the
|
5
|
+
# of the _Elasticsearch_ index on model instance create, update or delete.
|
6
6
|
#
|
7
7
|
# Include it in your model: `include Tire::Model::Callbacks`
|
8
8
|
#
|
data/lib/tire/model/indexing.rb
CHANGED
@@ -28,7 +28,7 @@ module Tire
|
|
28
28
|
end
|
29
29
|
|
30
30
|
# Define the [_mapping_](http://www.elasticsearch.org/guide/reference/mapping/index.html)
|
31
|
-
# for the corresponding index, telling
|
31
|
+
# for the corresponding index, telling _Elasticsearch_ how to understand your documents:
|
32
32
|
# what type is which property, whether it is analyzed or no, which analyzer to use, etc.
|
33
33
|
#
|
34
34
|
# You may pass the top level mapping properties (such as `_source` or `_all`) as a Hash.
|
@@ -113,7 +113,7 @@ module Tire
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
rescue Errno::ECONNREFUSED => e
|
116
|
-
STDERR.puts "Skipping index creation, cannot connect to
|
116
|
+
STDERR.puts "Skipping index creation, cannot connect to Elasticsearch",
|
117
117
|
"(The original exception was: #{e.inspect})"
|
118
118
|
false
|
119
119
|
end
|
data/lib/tire/model/naming.rb
CHANGED
data/lib/tire/model/percolate.rb
CHANGED
@@ -2,7 +2,7 @@ module Tire
|
|
2
2
|
module Model
|
3
3
|
|
4
4
|
# Contains support for the [percolation](http://www.elasticsearch.org/guide/reference/api/percolate.html)
|
5
|
-
# feature of
|
5
|
+
# feature of _Elasticsearch_.
|
6
6
|
#
|
7
7
|
module Percolate
|
8
8
|
|
@@ -31,7 +31,7 @@ module Tire
|
|
31
31
|
# See <http://www.elasticsearch.org/guide/reference/api/index_.html> for more information.
|
32
32
|
#
|
33
33
|
def percolate!(pattern=true)
|
34
|
-
|
34
|
+
@_percolator = pattern
|
35
35
|
self
|
36
36
|
end
|
37
37
|
|
@@ -57,7 +57,7 @@ module Tire
|
|
57
57
|
# Returns the status or pattern of percolator for this class.
|
58
58
|
#
|
59
59
|
def percolator
|
60
|
-
|
60
|
+
@_percolator
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Tire
|
2
2
|
module Model
|
3
3
|
|
4
|
-
# Allows to use
|
4
|
+
# Allows to use _Elasticsearch_ as a primary database (storage).
|
5
5
|
#
|
6
6
|
# Contains all the `Tire::Model::Search` features and provides
|
7
7
|
# an [_ActiveModel_](http://rubygems.org/gems/activemodel)-compatible
|
@@ -19,7 +19,7 @@ module Tire
|
|
19
19
|
# property :tags, :analyzer => 'keywords', :default => []
|
20
20
|
# end
|
21
21
|
#
|
22
|
-
# You can pass mapping definition for
|
22
|
+
# You can pass mapping definition for Elasticsearch in the options Hash.
|
23
23
|
#
|
24
24
|
# You can define default property values.
|
25
25
|
#
|
@@ -134,7 +134,7 @@ module Tire
|
|
134
134
|
|
135
135
|
else
|
136
136
|
# Strings formatted as <http://en.wikipedia.org/wiki/ISO8601> are automatically converted to Time
|
137
|
-
value = Time.parse(value) if value.is_a?(String) && value =~ /^\d{4}[\/\-]\d{2}[\/\-]\d{2}T\d{2}\:\d{2}\:\d{2}
|
137
|
+
value = Time.parse(value).utc if value.is_a?(String) && value =~ /^\d{4}[\/\-]\d{2}[\/\-]\d{2}T\d{2}\:\d{2}\:\d{2}/
|
138
138
|
value
|
139
139
|
end
|
140
140
|
end
|
@@ -3,7 +3,7 @@ module Tire
|
|
3
3
|
|
4
4
|
module Persistence
|
5
5
|
|
6
|
-
# Provides infrastructure for storing records in
|
6
|
+
# Provides infrastructure for storing records in _Elasticsearch_.
|
7
7
|
#
|
8
8
|
module Storage
|
9
9
|
def self.included(base)
|
@@ -34,7 +34,7 @@ module Tire
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def update_index
|
37
|
-
|
37
|
+
run_callbacks :update_elasticsearch_index do
|
38
38
|
if destroyed?
|
39
39
|
index.remove self
|
40
40
|
else
|
data/lib/tire/model/search.rb
CHANGED
@@ -52,7 +52,7 @@ module Tire
|
|
52
52
|
# end
|
53
53
|
#
|
54
54
|
# This methods returns a Tire::Results::Collection instance, containing instances
|
55
|
-
# of Tire::Results::Item, populated by the data available in
|
55
|
+
# of Tire::Results::Item, populated by the data available in _Elasticsearch, by default.
|
56
56
|
#
|
57
57
|
# If you'd like to load the "real" models from the database, you may use the `:load` option:
|
58
58
|
#
|
@@ -132,7 +132,7 @@ module Tire
|
|
132
132
|
instance.class.tire.index
|
133
133
|
end
|
134
134
|
|
135
|
-
# Updates the index in
|
135
|
+
# Updates the index in _Elasticsearch_.
|
136
136
|
#
|
137
137
|
# On model instance create or update, it will store its serialized representation in the index.
|
138
138
|
#
|
@@ -141,7 +141,7 @@ module Tire
|
|
141
141
|
# It will also execute any `<after|before>_update_elasticsearch_index` callback hooks.
|
142
142
|
#
|
143
143
|
def update_index
|
144
|
-
instance.
|
144
|
+
instance.run_callbacks :update_elasticsearch_index do
|
145
145
|
if instance.destroyed?
|
146
146
|
index.remove instance
|
147
147
|
else
|
@@ -158,7 +158,7 @@ module Tire
|
|
158
158
|
#
|
159
159
|
# If you don't define any mapping, the model is serialized as-is.
|
160
160
|
#
|
161
|
-
# If you do define the mapping for
|
161
|
+
# If you do define the mapping for _Elasticsearch_, only attributes
|
162
162
|
# declared in the mapping are serialized.
|
163
163
|
#
|
164
164
|
# For properties declared with the `:as` option, the passed String or Proc
|
@@ -191,15 +191,15 @@ module Tire
|
|
191
191
|
|
192
192
|
def matches
|
193
193
|
instance.instance_eval do
|
194
|
-
@
|
195
|
-
@
|
194
|
+
@tire__attributes ||= {}
|
195
|
+
@tire__attributes['matches']
|
196
196
|
end
|
197
197
|
end
|
198
198
|
|
199
199
|
def matches=(value)
|
200
200
|
instance.instance_eval do
|
201
|
-
@
|
202
|
-
@
|
201
|
+
@tire__attributes ||= {}
|
202
|
+
@tire__attributes['matches'] = value
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
data/lib/tire/multi_search.rb
CHANGED
@@ -218,12 +218,12 @@ module Tire
|
|
218
218
|
end
|
219
219
|
|
220
220
|
def perform
|
221
|
-
@
|
222
|
-
if @
|
221
|
+
@response = Configuration.client.get(url + params, to_payload)
|
222
|
+
if @response.failure?
|
223
223
|
STDERR.puts "[REQUEST FAILED] #{to_curl}\n"
|
224
|
-
raise SearchRequestFailed, @
|
224
|
+
raise SearchRequestFailed, @response.to_s
|
225
225
|
end
|
226
|
-
@json = MultiJson.decode(@
|
226
|
+
@json = MultiJson.decode(@response.body)
|
227
227
|
@results = Tire::Search::Multi::Results.new @searches, @json['responses']
|
228
228
|
return self
|
229
229
|
ensure
|
@@ -1 +1 @@
|
|
1
|
-
require 'tire/rubyext/uri_escape'
|
1
|
+
require 'tire/rubyext/uri_escape' unless defined?(URI.encode_www_form_component) && defined?(URI.decode_www_form_component)
|
data/lib/tire/search.rb
CHANGED
data/lib/tire/search/scan.rb
CHANGED
@@ -34,7 +34,7 @@ module Tire
|
|
34
34
|
# query { term 'author.exact', 'John Smith' }
|
35
35
|
# end
|
36
36
|
#
|
37
|
-
# See
|
37
|
+
# See Elasticsearch documentation for further reference:
|
38
38
|
#
|
39
39
|
# * http://www.elasticsearch.org/guide/reference/api/search/search-type.html
|
40
40
|
# * http://www.elasticsearch.org/guide/reference/api/search/scroll.html
|
data/lib/tire/tasks.rb
CHANGED
@@ -76,7 +76,7 @@ namespace :tire do
|
|
76
76
|
if defined?(Kaminari) && klass.respond_to?(:page)
|
77
77
|
klass.instance_eval do
|
78
78
|
def paginate(options = {})
|
79
|
-
page(options[:page]).per(options[:per_page])
|
79
|
+
page(options[:page]).per(options[:per_page])
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end unless klass.respond_to?(:paginate)
|
data/lib/tire/version.rb
CHANGED
@@ -1,18 +1,14 @@
|
|
1
1
|
module Tire
|
2
|
-
VERSION = "0.5.
|
2
|
+
VERSION = "0.5.5"
|
3
3
|
|
4
4
|
CHANGELOG =<<-END
|
5
5
|
IMPORTANT CHANGES LATELY:
|
6
6
|
|
7
|
-
*
|
8
|
-
*
|
9
|
-
*
|
10
|
-
* Added
|
11
|
-
* Added
|
12
|
-
*
|
13
|
-
* Cast collection properties in Model::Persistence as empty Array by default
|
14
|
-
* Allow passing `:index` option to `MyModel.import`
|
15
|
-
* Update to Mocha ~> 0.13
|
16
|
-
* Update to MultiJson ~> 1.3
|
7
|
+
* Improved documentation
|
8
|
+
* Improved isolation of Tire methods in model integrations
|
9
|
+
* Improved handling of times/dates in `Model::Persistence`
|
10
|
+
* Added support for "Put Mapping" and "Delete mapping" APIs
|
11
|
+
* Added escaping document IDs in URLs
|
12
|
+
* Allowed passing URL options when passing search definition as a Hash
|
17
13
|
END
|
18
14
|
end
|