tire 0.3.3 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +34 -3
- data/examples/tire-dsl.rb +1 -1
- data/lib/tire/http/client.rb +5 -5
- data/lib/tire/index.rb +7 -3
- data/lib/tire/model/callbacks.rb +0 -8
- data/lib/tire/model/indexing.rb +3 -0
- data/lib/tire/model/naming.rb +9 -1
- data/lib/tire/model/persistence/finders.rb +3 -3
- data/lib/tire/model/persistence/storage.rb +0 -4
- data/lib/tire/model/search.rb +8 -3
- data/lib/tire/results/pagination.rb +9 -0
- data/lib/tire/search.rb +2 -1
- data/lib/tire/version.rb +5 -25
- data/test/integration/active_record_searchable_test.rb +47 -0
- data/test/integration/mongoid_searchable_test.rb +308 -0
- data/test/integration/persistent_model_test.rb +10 -1
- data/test/models/active_record_models.rb +9 -12
- data/test/models/mongoid_models.rb +98 -0
- data/test/unit/http_client_test.rb +7 -0
- data/test/unit/model_callbacks_test.rb +36 -16
- data/test/unit/model_search_test.rb +43 -0
- data/test/unit/results_collection_test.rb +7 -0
- data/test/unit/search_test.rb +3 -1
- data/tire.gemspec +1 -0
- metadata +228 -172
data/README.markdown
CHANGED
@@ -134,6 +134,17 @@ We can easily manipulate the documents before storing them in the index, by pass
|
|
134
134
|
end
|
135
135
|
```
|
136
136
|
|
137
|
+
If this _declarative_ notation does not fit well in your context,
|
138
|
+
you can use _Tire's_ classes directly, in a more imperative manner:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
index = Tire::Index.new('oldskool')
|
142
|
+
index.delete
|
143
|
+
index.create
|
144
|
+
index.store :title => "Let's do it the old way!"
|
145
|
+
index.refresh
|
146
|
+
```
|
147
|
+
|
137
148
|
OK. Now, let's go search all the data.
|
138
149
|
|
139
150
|
We will be searching for articles whose `title` begins with letter “T”, sorted by `title` in `descending` order,
|
@@ -281,7 +292,21 @@ a plain old Ruby `Hash` (or JSON string) with the query declaration to the `sear
|
|
281
292
|
If this sounds like a great idea to you, you are probably able to write your application
|
282
293
|
using just `curl`, `sed` and `awk`.
|
283
294
|
|
284
|
-
|
295
|
+
Do note again, however, that you're not tied to the declarative block-style DSL _Tire_ offers to you.
|
296
|
+
If it makes more sense in your context, you can use its classes directly, in a more imperative style:
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
search = Tire::Search::Search.new('articles')
|
300
|
+
search.query { string('title:T*') }
|
301
|
+
search.filter :terms, :tags => ['ruby']
|
302
|
+
search.sort { by :title, 'desc' }
|
303
|
+
search.facet('global-tags') { terms :tags, :global => true }
|
304
|
+
# ...
|
305
|
+
p search.perform.results
|
306
|
+
```
|
307
|
+
|
308
|
+
To debug the query we have laboriously set up like this,
|
309
|
+
we can display the full query JSON for close inspection:
|
285
310
|
|
286
311
|
```ruby
|
287
312
|
puts s.to_json
|
@@ -692,6 +717,14 @@ This will result in Article instances being stored in an index called 'test_arti
|
|
692
717
|
Please be sure to peruse the [integration test suite](https://github.com/karmi/tire/tree/master/test/integration)
|
693
718
|
for examples of the API and _ActiveModel_ integration usage.
|
694
719
|
|
720
|
+
|
721
|
+
Extensions and Additions
|
722
|
+
------------------------
|
723
|
+
|
724
|
+
The [_tire-contrib_](http://github.com/karmi/tire-contrib/) project contains additions
|
725
|
+
and extensions to the _Tire_ functionality.
|
726
|
+
|
727
|
+
|
695
728
|
Todo, Plans & Ideas
|
696
729
|
-------------------
|
697
730
|
|
@@ -699,8 +732,6 @@ _Tire_ is already used in production by its authors. Nevertheless, it's not cons
|
|
699
732
|
|
700
733
|
There are todos, plans and ideas, some of which are listed below, in the order of importance:
|
701
734
|
|
702
|
-
* Wrap all Tire functionality mixed into a model in a "forwardable" object, and proxy everything via this object. (The immediate problem: [Mongoid](http://mongoid.org/docs/indexing.html))
|
703
|
-
* If we're not stepping on other's toes, bring Tire methods like `index`, `search`, `mapping` also to the class/instance top-level namespace.
|
704
735
|
* Proper RDoc annotations for the source code
|
705
736
|
* [Statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html) facets
|
706
737
|
* [Geo Distance](http://www.elasticsearch.org/guide/reference/api/search/facets/geo-distance-facet.html) facets
|
data/examples/tire-dsl.rb
CHANGED
data/lib/tire/http/client.rb
CHANGED
@@ -8,31 +8,31 @@ module Tire
|
|
8
8
|
|
9
9
|
def self.get(url, data=nil)
|
10
10
|
perform ::RestClient::Request.new(:method => :get, :url => url, :payload => data).execute
|
11
|
-
rescue Exception => e
|
11
|
+
rescue ::RestClient::Exception => e
|
12
12
|
Response.new e.http_body, e.http_code
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.post(url, data)
|
16
16
|
perform ::RestClient.post(url, data)
|
17
|
-
rescue Exception => e
|
17
|
+
rescue ::RestClient::Exception => e
|
18
18
|
Response.new e.http_body, e.http_code
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.put(url, data)
|
22
22
|
perform ::RestClient.put(url, data)
|
23
|
-
rescue Exception => e
|
23
|
+
rescue ::RestClient::Exception => e
|
24
24
|
Response.new e.http_body, e.http_code
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.delete(url)
|
28
28
|
perform ::RestClient.delete(url)
|
29
|
-
rescue Exception => e
|
29
|
+
rescue ::RestClient::Exception => e
|
30
30
|
Response.new e.http_body, e.http_code
|
31
31
|
end
|
32
32
|
|
33
33
|
def self.head(url)
|
34
34
|
perform ::RestClient.head(url)
|
35
|
-
rescue Exception => e
|
35
|
+
rescue ::RestClient::Exception => e
|
36
36
|
Response.new e.http_body, e.http_code
|
37
37
|
end
|
38
38
|
|
data/lib/tire/index.rb
CHANGED
@@ -9,7 +9,11 @@ module Tire
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def exists?
|
12
|
-
Configuration.client.head("#{Configuration.url}/#{@name}")
|
12
|
+
@response = Configuration.client.head("#{Configuration.url}/#{@name}")
|
13
|
+
@response.success?
|
14
|
+
ensure
|
15
|
+
curl = %Q|curl -I "#{Configuration.url}/#{@name}"|
|
16
|
+
logged(@response.body, 'HEAD', curl) if @response
|
13
17
|
end
|
14
18
|
|
15
19
|
def delete
|
@@ -26,7 +30,7 @@ module Tire
|
|
26
30
|
@response.success? ? @response : false
|
27
31
|
ensure
|
28
32
|
curl = %Q|curl -X POST "#{Configuration.url}/#{@name}" -d '#{MultiJson.encode(options)}'|
|
29
|
-
logged(@response.body, 'CREATE', curl)
|
33
|
+
logged(@response.body, 'CREATE', curl) if @response
|
30
34
|
end
|
31
35
|
|
32
36
|
def mapping
|
@@ -230,7 +234,7 @@ module Tire
|
|
230
234
|
if Configuration.logger.level.to_s == 'debug'
|
231
235
|
# FIXME: Depends on RestClient implementation
|
232
236
|
body = if @response
|
233
|
-
defined?(Yajl) ? Yajl::Encoder.encode(@
|
237
|
+
defined?(Yajl) ? Yajl::Encoder.encode(@response.body, :pretty => true) : MultiJson.encode(@response.body)
|
234
238
|
else
|
235
239
|
error.http_body rescue ''
|
236
240
|
end
|
data/lib/tire/model/callbacks.rb
CHANGED
@@ -9,9 +9,6 @@ module Tire
|
|
9
9
|
# The model must respond to `after_save` and `after_destroy` callbacks
|
10
10
|
# (ActiveModel and ActiveRecord models do so, by default).
|
11
11
|
#
|
12
|
-
# By including the model, you will also have the `after_update_elasticsearch_index` and
|
13
|
-
# `before_update_elasticsearch_index` callbacks available.
|
14
|
-
#
|
15
12
|
module Callbacks
|
16
13
|
|
17
14
|
# A hook triggered by the `include Tire::Model::Callbacks` statement in the model.
|
@@ -35,11 +32,6 @@ module Tire
|
|
35
32
|
end
|
36
33
|
end
|
37
34
|
|
38
|
-
# Define _Tire's_ callbacks.
|
39
|
-
#
|
40
|
-
base.class_eval do
|
41
|
-
define_model_callbacks(:update_elasticsearch_index, :only => [:after, :before])
|
42
|
-
end if base.respond_to?(:define_model_callbacks)
|
43
35
|
end
|
44
36
|
|
45
37
|
end
|
data/lib/tire/model/indexing.rb
CHANGED
@@ -92,6 +92,9 @@ module Tire
|
|
92
92
|
unless index.exists?
|
93
93
|
index.create :mappings => mapping_to_hash, :settings => settings
|
94
94
|
end
|
95
|
+
rescue Errno::ECONNREFUSED => e
|
96
|
+
STDERR.puts "Skipping index creation, cannot connect to ElasticSearch",
|
97
|
+
"(The original exception was: #{e.inspect})"
|
95
98
|
end
|
96
99
|
|
97
100
|
def store_mapping?
|
data/lib/tire/model/naming.rb
CHANGED
@@ -20,8 +20,16 @@ module Tire
|
|
20
20
|
#
|
21
21
|
# Article.index_name 'my-custom-name'
|
22
22
|
#
|
23
|
-
|
23
|
+
# You can also use a block for defining the index name,
|
24
|
+
# which is evaluated in the class context:
|
25
|
+
#
|
26
|
+
# Article.index_name { "articles-#{Time.now.year}" }
|
27
|
+
#
|
28
|
+
# Article.index_name { "articles-#{Rails.env}" }
|
29
|
+
#
|
30
|
+
def index_name name=nil, &block
|
24
31
|
@index_name = name if name
|
32
|
+
@index_name = block if block_given?
|
25
33
|
@index_name || [index_prefix, klass.model_name.plural].compact.join('_')
|
26
34
|
end
|
27
35
|
|
@@ -22,7 +22,7 @@ module Tire
|
|
22
22
|
else
|
23
23
|
case args = args.pop
|
24
24
|
when Fixnum, String
|
25
|
-
|
25
|
+
index.retrieve document_type, args
|
26
26
|
when :all, :first
|
27
27
|
send(args)
|
28
28
|
else
|
@@ -37,7 +37,7 @@ module Tire
|
|
37
37
|
# TODO: Options like `sort`; Possibly `filters`
|
38
38
|
old_wrapper = Tire::Configuration.wrapper
|
39
39
|
Tire::Configuration.wrapper self
|
40
|
-
s = Tire::Search::Search.new(
|
40
|
+
s = Tire::Search::Search.new(index.name).query { all }
|
41
41
|
s.perform.results
|
42
42
|
ensure
|
43
43
|
Tire::Configuration.wrapper old_wrapper
|
@@ -47,7 +47,7 @@ module Tire
|
|
47
47
|
# TODO: Options like `sort`; Possibly `filters`
|
48
48
|
old_wrapper = Tire::Configuration.wrapper
|
49
49
|
Tire::Configuration.wrapper self
|
50
|
-
s = Tire::Search::Search.new(
|
50
|
+
s = Tire::Search::Search.new(index.name).query { all }.size(1)
|
51
51
|
s.perform.results.first
|
52
52
|
ensure
|
53
53
|
Tire::Configuration.wrapper old_wrapper
|
data/lib/tire/model/search.rb
CHANGED
@@ -99,7 +99,8 @@ module Tire
|
|
99
99
|
# Example usage: `Article.index.refresh`.
|
100
100
|
#
|
101
101
|
def index
|
102
|
-
|
102
|
+
name = index_name.respond_to?(:to_proc) ? klass.instance_eval(&index_name) : index_name
|
103
|
+
@index = Index.new(name)
|
103
104
|
end
|
104
105
|
|
105
106
|
end
|
@@ -120,8 +121,7 @@ module Tire
|
|
120
121
|
#
|
121
122
|
# On model destroy, it will remove the corresponding document from the index.
|
122
123
|
#
|
123
|
-
# It will also
|
124
|
-
# if defined by the model.
|
124
|
+
# It will also execute any `<after|before>_update_elasticsearch_index` callback hooks.
|
125
125
|
#
|
126
126
|
def update_index
|
127
127
|
instance.send :_run_update_elasticsearch_index_callbacks do
|
@@ -237,6 +237,11 @@ module Tire
|
|
237
237
|
@__tire__
|
238
238
|
end
|
239
239
|
|
240
|
+
# Define _Tire's_ callbacks (<after|before>_update_elasticsearch_index).
|
241
|
+
#
|
242
|
+
define_model_callbacks(:update_elasticsearch_index, :only => [:after, :before]) if \
|
243
|
+
respond_to?(:define_model_callbacks)
|
244
|
+
|
240
245
|
# Serialize the model as a Hash.
|
241
246
|
#
|
242
247
|
# Uses `serializable_hash` representation of the model,
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Tire
|
2
2
|
module Results
|
3
3
|
|
4
|
+
# Adds support for WillPaginate and Kaminari
|
5
|
+
#
|
4
6
|
module Pagination
|
5
7
|
|
6
8
|
def total_entries
|
@@ -39,6 +41,13 @@ module Tire
|
|
39
41
|
current_page > total_pages
|
40
42
|
end
|
41
43
|
|
44
|
+
# Kaminari support
|
45
|
+
#
|
46
|
+
alias :limit_value :per_page
|
47
|
+
alias :total_count :total_entries
|
48
|
+
alias :num_pages :total_pages
|
49
|
+
alias :offset_value :offset
|
50
|
+
|
42
51
|
end
|
43
52
|
|
44
53
|
end
|
data/lib/tire/search.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Tire
|
2
2
|
module Search
|
3
|
+
class SearchRequestFailed < StandardError; end
|
3
4
|
|
4
5
|
class Search
|
5
6
|
|
@@ -70,7 +71,7 @@ module Tire
|
|
70
71
|
@response = Configuration.client.get(@url, self.to_json)
|
71
72
|
if @response.failure?
|
72
73
|
STDERR.puts "[REQUEST FAILED] #{self.to_curl}\n"
|
73
|
-
|
74
|
+
raise SearchRequestFailed
|
74
75
|
end
|
75
76
|
@json = MultiJson.decode(@response.body)
|
76
77
|
@results = Results::Collection.new(@json, @options)
|
data/lib/tire/version.rb
CHANGED
@@ -1,34 +1,14 @@
|
|
1
1
|
module Tire
|
2
|
-
VERSION = "0.3.
|
2
|
+
VERSION = "0.3.4"
|
3
3
|
|
4
4
|
CHANGELOG =<<-END
|
5
5
|
IMPORTANT CHANGES LATELY:
|
6
6
|
|
7
|
-
0.
|
7
|
+
0.3.4
|
8
8
|
---------------------------------------------------------
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# Deprecated the dynamic sort methods in favour of the 'sort { by :field_name }' syntax
|
13
|
-
|
14
|
-
0.2.1
|
15
|
-
---------------------------------------------------------
|
16
|
-
# Lighweight check for index presence
|
17
|
-
# Added the 'settings' method for models to define index settings
|
18
|
-
# Fixed errors when importing data with will_paginate vs Kaminari (MongoDB)
|
19
|
-
# Added support for histogram facets [Paco Guzman]
|
20
|
-
|
21
|
-
0.3.0
|
22
|
-
---------------------------------------------------------
|
23
|
-
# Isolated Tire ActiveModel integration into `tire` class and instance method.
|
24
|
-
|
25
|
-
When there's no conflict with existing methods, Tire methods are added
|
26
|
-
to the class namespace, as well, so the change is 100% backwards-compatible.
|
27
|
-
|
28
|
-
0.3.3
|
29
|
-
---------------------------------------------------------
|
30
|
-
# Added proper will_paginate compatibility
|
31
|
-
# Added support for plugging in another HTTP library (see lib/http/clients/curb for an example)
|
9
|
+
# Added documentation (RDoc annotations and README)
|
10
|
+
# Kaminari pagination support
|
11
|
+
# Bugfixes (dependency on ES running, callbacks, bogus exceptions)
|
32
12
|
|
33
13
|
END
|
34
14
|
end
|
@@ -29,6 +29,9 @@ module Tire
|
|
29
29
|
create_table :active_record_class_with_tire_methods do |t|
|
30
30
|
t.string :title
|
31
31
|
end
|
32
|
+
create_table :active_record_class_with_dynamic_index_names do |t|
|
33
|
+
t.string :title
|
34
|
+
end
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
@@ -72,6 +75,14 @@ module Tire
|
|
72
75
|
assert_equal 'Test', results.first.title
|
73
76
|
end
|
74
77
|
|
78
|
+
should "raise exception on invalid query" do
|
79
|
+
ActiveRecordArticle.create! :title => 'Test'
|
80
|
+
|
81
|
+
assert_raise Search::SearchRequestFailed do
|
82
|
+
ActiveRecordArticle.search '[x'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
75
86
|
context "with eager loading" do
|
76
87
|
setup do
|
77
88
|
ActiveRecordArticle.destroy_all
|
@@ -147,11 +158,20 @@ module Tire
|
|
147
158
|
results = ActiveRecordArticle.search 'test*', :sort => 'title', :per_page => 5, :page => 1
|
148
159
|
assert_equal 5, results.size
|
149
160
|
|
161
|
+
# WillPaginate
|
162
|
+
#
|
150
163
|
assert_equal 2, results.total_pages
|
151
164
|
assert_equal 1, results.current_page
|
152
165
|
assert_equal nil, results.previous_page
|
153
166
|
assert_equal 2, results.next_page
|
154
167
|
|
168
|
+
# Kaminari
|
169
|
+
#
|
170
|
+
assert_equal 5, results.limit_value
|
171
|
+
assert_equal 9, results.total_count
|
172
|
+
assert_equal 2, results.num_pages
|
173
|
+
assert_equal 0, results.offset_value
|
174
|
+
|
155
175
|
assert_equal 'Test1', results.first.title
|
156
176
|
end
|
157
177
|
|
@@ -164,6 +184,12 @@ module Tire
|
|
164
184
|
assert_equal 1, results.previous_page
|
165
185
|
assert_equal nil, results.next_page
|
166
186
|
|
187
|
+
#kaminari
|
188
|
+
assert_equal 5, results.limit_value
|
189
|
+
assert_equal 9, results.total_count
|
190
|
+
assert_equal 2, results.num_pages
|
191
|
+
assert_equal 5, results.offset_value
|
192
|
+
|
167
193
|
assert_equal 'Test6', results.first.title
|
168
194
|
end
|
169
195
|
|
@@ -176,6 +202,12 @@ module Tire
|
|
176
202
|
assert_equal 2, results.previous_page
|
177
203
|
assert_equal nil, results.next_page
|
178
204
|
|
205
|
+
#kaminari
|
206
|
+
assert_equal 5, results.limit_value
|
207
|
+
assert_equal 9, results.total_count
|
208
|
+
assert_equal 2, results.num_pages
|
209
|
+
assert_equal 10, results.offset_value
|
210
|
+
|
179
211
|
assert_nil results.first
|
180
212
|
end
|
181
213
|
|
@@ -267,6 +299,21 @@ module Tire
|
|
267
299
|
|
268
300
|
end
|
269
301
|
|
302
|
+
context "with dynamic index name" do
|
303
|
+
setup do
|
304
|
+
@a = ActiveRecordClassWithDynamicIndexName.create! :title => 'Test'
|
305
|
+
@a.index.refresh
|
306
|
+
end
|
307
|
+
|
308
|
+
should "search in proper index" do
|
309
|
+
assert_equal 'dynamic_index', ActiveRecordClassWithDynamicIndexName.index.name
|
310
|
+
assert_equal 'dynamic_index', @a.index.name
|
311
|
+
|
312
|
+
results = ActiveRecordClassWithDynamicIndexName.search 'test'
|
313
|
+
assert_equal 'dynamic_index', results.first._index
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
270
317
|
context "within Rails" do
|
271
318
|
|
272
319
|
setup do
|