tire 0.5.4 → 0.5.5
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.
- 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
|