tire 0.3.12 → 0.4.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +41 -45
- data/examples/tire-dsl.rb +7 -2
- data/lib/tire/configuration.rb +1 -1
- data/lib/tire/dsl.rb +1 -1
- data/lib/tire/http/client.rb +11 -0
- data/lib/tire/http/clients/curb.rb +9 -2
- data/lib/tire/index.rb +4 -4
- data/lib/tire/model/indexing.rb +7 -2
- data/lib/tire/model/persistence/attributes.rb +84 -2
- data/lib/tire/model/persistence/finders.rb +8 -5
- data/lib/tire/model/persistence/storage.rb +2 -4
- data/lib/tire/model/persistence.rb +12 -0
- data/lib/tire/model/search.rb +20 -4
- data/lib/tire/search/facet.rb +11 -1
- data/lib/tire/search/query.rb +9 -2
- data/lib/tire/search.rb +21 -4
- data/lib/tire/tasks.rb +14 -1
- data/lib/tire/version.rb +9 -4
- data/lib/tire.rb +1 -0
- data/test/integration/active_model_indexing_test.rb +50 -0
- data/test/integration/facets_test.rb +53 -2
- data/test/integration/filtered_queries_test.rb +23 -4
- data/test/integration/index_store_test.rb +23 -1
- data/test/integration/persistent_model_test.rb +34 -0
- data/test/integration/query_return_version_test.rb +70 -0
- data/test/integration/query_string_test.rb +8 -8
- data/test/integration/text_query_test.rb +25 -0
- data/test/models/persistent_article_with_casting.rb +28 -0
- data/test/models/persistent_article_with_defaults.rb +11 -0
- data/test/test_helper.rb +3 -1
- data/test/unit/configuration_test.rb +6 -0
- data/test/unit/http_client_test.rb +42 -0
- data/test/unit/index_test.rb +22 -2
- data/test/unit/model_persistence_test.rb +55 -9
- data/test/unit/model_search_test.rb +37 -1
- data/test/unit/search_facet_test.rb +28 -0
- data/test/unit/search_query_test.rb +22 -7
- data/test/unit/search_test.rb +38 -3
- data/test/unit/tire_test.rb +15 -0
- data/tire.gemspec +5 -4
- metadata +171 -225
data/README.markdown
CHANGED
@@ -161,8 +161,8 @@ from the database:
|
|
161
161
|
|
162
162
|
sort { by :title, 'desc' }
|
163
163
|
|
164
|
-
facet 'global-tags' do
|
165
|
-
terms :tags
|
164
|
+
facet 'global-tags', :global => true do
|
165
|
+
terms :tags
|
166
166
|
end
|
167
167
|
|
168
168
|
facet 'current-tags' do
|
@@ -282,6 +282,15 @@ Note, that you can pass options for configuring queries, facets, etc. by passing
|
|
282
282
|
end
|
283
283
|
```
|
284
284
|
|
285
|
+
You don't have to define the search criteria in one monolithic _Ruby_ block -- you can build the search step by step,
|
286
|
+
until you call the `results` method:
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
s = Tire.search('articles') { query { string 'title:T*' } }
|
290
|
+
s.filter :terms, :tags => ['ruby']
|
291
|
+
p s.results
|
292
|
+
```
|
293
|
+
|
285
294
|
If configuring the search payload with blocks feels somehow too weak for you, you can pass
|
286
295
|
a plain old Ruby `Hash` (or JSON string) with the query declaration to the `search` method:
|
287
296
|
|
@@ -293,7 +302,7 @@ If this sounds like a great idea to you, you are probably able to write your app
|
|
293
302
|
using just `curl`, `sed` and `awk`.
|
294
303
|
|
295
304
|
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
|
305
|
+
If it makes more sense in your context, you can use the API directly, in a more imperative style:
|
297
306
|
|
298
307
|
```ruby
|
299
308
|
search = Tire::Search::Search.new('articles')
|
@@ -302,7 +311,7 @@ If it makes more sense in your context, you can use its classes directly, in a m
|
|
302
311
|
search.sort { by :title, 'desc' }
|
303
312
|
search.facet('global-tags') { terms :tags, :global => true }
|
304
313
|
# ...
|
305
|
-
p search.
|
314
|
+
p search.results
|
306
315
|
```
|
307
316
|
|
308
317
|
To debug the query we have laboriously set up like this,
|
@@ -413,11 +422,12 @@ For serious usage, though, you'll definitely want to define a custom _mapping_ f
|
|
413
422
|
include Tire::Model::Callbacks
|
414
423
|
|
415
424
|
mapping do
|
416
|
-
indexes :id, :
|
417
|
-
indexes :title, :
|
418
|
-
indexes :content, :
|
419
|
-
indexes :
|
420
|
-
indexes :
|
425
|
+
indexes :id, :index => :not_analyzed
|
426
|
+
indexes :title, :analyzer => 'snowball', :boost => 100
|
427
|
+
indexes :content, :analyzer => 'snowball'
|
428
|
+
indexes :content_size, :as => 'content.size'
|
429
|
+
indexes :author, :analyzer => 'keyword'
|
430
|
+
indexes :published_on, :type => 'date', :include_in_all => false
|
421
431
|
end
|
422
432
|
end
|
423
433
|
```
|
@@ -425,6 +435,13 @@ For serious usage, though, you'll definitely want to define a custom _mapping_ f
|
|
425
435
|
In this case, _only_ the defined model attributes are indexed. The `mapping` declaration creates the
|
426
436
|
index when the class is loaded or when the importing features are used, and _only_ when it does not yet exist.
|
427
437
|
|
438
|
+
You can define different [_analyzers_](http://www.elasticsearch.org/guide/reference/index-modules/analysis/index.html),
|
439
|
+
[_boost_](http://www.elasticsearch.org/guide/reference/mapping/boost-field.html) levels for different properties,
|
440
|
+
or any other configuration for _elasticsearch_.
|
441
|
+
|
442
|
+
You're not limited to 1:1 mapping between your model properties and the serialized document. With the `:as` option,
|
443
|
+
you can pass a string or a _Proc_ object which is evaluated in the instance context (see the `content_size` property).
|
444
|
+
|
428
445
|
Chances are, you want to declare also a custom _settings_ for the index, such as set the number of shards,
|
429
446
|
replicas, or create elaborate analyzer chains, such as the hipster's choice: [_ngrams_](https://gist.github.com/1160430).
|
430
447
|
In this case, just wrap the `mapping` method in a `settings` one, passing it the settings as a Hash:
|
@@ -671,12 +688,11 @@ Well, things stay mostly the same:
|
|
671
688
|
include Tire::Model::Search
|
672
689
|
include Tire::Model::Callbacks
|
673
690
|
|
674
|
-
# Let's use a different index name so stuff doesn't get mixed up
|
691
|
+
# Let's use a different index name so stuff doesn't get mixed up.
|
675
692
|
#
|
676
693
|
index_name 'mongo-articles'
|
677
694
|
|
678
|
-
# These Mongo guys sure do
|
679
|
-
# in +serializable_hash+, let's fix it.
|
695
|
+
# These Mongo guys sure do get funky with their IDs in +serializable_hash+, let's fix it.
|
680
696
|
#
|
681
697
|
def to_indexed_json
|
682
698
|
self.to_json
|
@@ -696,36 +712,31 @@ _Tire_ implements not only _searchable_ features, but also _persistence_ feature
|
|
696
712
|
|
697
713
|
Well, because you're tired of database migrations and lots of hand-holding with your
|
698
714
|
database to store stuff like `{ :name => 'Tire', :tags => [ 'ruby', 'search' ] }`.
|
699
|
-
Because
|
700
|
-
load it back when needed.
|
715
|
+
Because all you need, really, is to just dump a JSON-representation of your data into a database and load it back again.
|
701
716
|
Because you've noticed that _searching_ your data is a much more effective way of retrieval
|
702
717
|
then constructing elaborate database query conditions.
|
703
|
-
Because you have _lots_ of data and want to use _ElasticSearch's_
|
704
|
-
advanced distributed features.
|
718
|
+
Because you have _lots_ of data and want to use _ElasticSearch's_ advanced distributed features.
|
705
719
|
|
706
|
-
|
720
|
+
All good reasons to use _ElasticSearch_ as a schema-free and highly-scalable storage and retrieval/aggregation engine for your data.
|
721
|
+
|
722
|
+
To use the persistence mode, we'll include the `Tire::Persistence` module in our class and define its properties;
|
723
|
+
we can add the standard mapping declarations, set default values, or define casting for the property to create
|
724
|
+
lightweight associations between the models.
|
707
725
|
|
708
726
|
```ruby
|
709
727
|
class Article
|
710
728
|
include Tire::Model::Persistence
|
711
|
-
include Tire::Model::Search
|
712
|
-
include Tire::Model::Callbacks
|
713
729
|
|
714
730
|
validates_presence_of :title, :author
|
715
731
|
|
716
|
-
property :title
|
717
|
-
property :
|
718
|
-
property :
|
719
|
-
property :
|
732
|
+
property :title, :analyzer => 'snowball'
|
733
|
+
property :published_on, :type => 'date'
|
734
|
+
property :tags, :default => [], :analyzer => 'keyword'
|
735
|
+
property :author, :class => Author
|
736
|
+
property :comments, :class => [Comment]
|
720
737
|
end
|
721
738
|
```
|
722
739
|
|
723
|
-
Of course, not all validations or `ActionPack` helpers will be available to your models,
|
724
|
-
but if you can live with that, you've just got a schema-free, highly-scalable storage
|
725
|
-
and retrieval engine for your data.
|
726
|
-
|
727
|
-
This will result in Article instances being stored in an index called 'test_articles' when used in tests but in the index 'development_articles' when used in the development environment.
|
728
|
-
|
729
740
|
Please be sure to peruse the [integration test suite](https://github.com/karmi/tire/tree/master/test/integration)
|
730
741
|
for examples of the API and _ActiveModel_ integration usage.
|
731
742
|
|
@@ -734,22 +745,7 @@ Extensions and Additions
|
|
734
745
|
------------------------
|
735
746
|
|
736
747
|
The [_tire-contrib_](http://github.com/karmi/tire-contrib/) project contains additions
|
737
|
-
and extensions to the _Tire_ functionality.
|
738
|
-
|
739
|
-
|
740
|
-
Todo, Plans & Ideas
|
741
|
-
-------------------
|
742
|
-
|
743
|
-
_Tire_ is already used in production by its authors. Nevertheless, it's not considered finished yet.
|
744
|
-
|
745
|
-
There are todos, plans and ideas, some of which are listed below, in the order of importance:
|
746
|
-
|
747
|
-
* Proper RDoc annotations for the source code
|
748
|
-
* [Statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html) facets
|
749
|
-
* [Geo Distance](http://www.elasticsearch.org/guide/reference/api/search/facets/geo-distance-facet.html) facets
|
750
|
-
* [Index aliases](http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html) management
|
751
|
-
* [Analyze](http://www.elasticsearch.org/guide/reference/api/admin-indices-analyze.html) API support
|
752
|
-
* Embedded webserver to display statistics and to allow easy searches
|
748
|
+
and extensions to the core _Tire_ functionality — be sure to check them out.
|
753
749
|
|
754
750
|
|
755
751
|
Other Clients
|
data/examples/tire-dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
1
3
|
# **Tire** provides rich and comfortable Ruby API for the
|
2
4
|
# [_ElasticSearch_](http://www.elasticsearch.org/) search engine/database.
|
3
5
|
#
|
@@ -474,6 +476,7 @@ end
|
|
474
476
|
# Eventually, _Tire_ will support all of them. So far, only these are supported:
|
475
477
|
#
|
476
478
|
# * [string](http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html)
|
479
|
+
# * [text](http://www.elasticsearch.org/guide/reference/query-dsl/text-query.html)
|
477
480
|
# * [term](http://elasticsearch.org/guide/reference/query-dsl/term-query.html)
|
478
481
|
# * [terms](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
|
479
482
|
# * [bool](http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html)
|
@@ -532,11 +535,11 @@ s = Tire.search 'articles' do
|
|
532
535
|
#
|
533
536
|
query { string 'title:T*' }
|
534
537
|
|
535
|
-
facet 'global-tags' do
|
538
|
+
facet 'global-tags', :global => true do
|
536
539
|
|
537
540
|
# ...but set the `global` scope for the facet in this case.
|
538
541
|
#
|
539
|
-
terms :tags
|
542
|
+
terms :tags
|
540
543
|
end
|
541
544
|
|
542
545
|
# We can even _combine_ facets scoped to the current query
|
@@ -583,6 +586,8 @@ end
|
|
583
586
|
# * [date](http://www.elasticsearch.org/guide/reference/api/search/facets/date-histogram-facet.html)
|
584
587
|
# * [range](http://www.elasticsearch.org/guide/reference/api/search/facets/range-facet.html)
|
585
588
|
# * [histogram](http://www.elasticsearch.org/guide/reference/api/search/facets/histogram-facet.html)
|
589
|
+
# * [statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html)
|
590
|
+
# * [terms_stats](http://www.elasticsearch.org/guide/reference/api/search/facets/terms-stats-facet.html)
|
586
591
|
# * [query](http://www.elasticsearch.org/guide/reference/api/search/facets/query-facet.html)
|
587
592
|
|
588
593
|
# We have seen that _ElasticSearch_ facets enable us to fetch complex aggregations from our data.
|
data/lib/tire/configuration.rb
CHANGED
@@ -3,7 +3,7 @@ module Tire
|
|
3
3
|
class Configuration
|
4
4
|
|
5
5
|
def self.url(value=nil)
|
6
|
-
@url
|
6
|
+
@url = (value ? value.to_s.gsub(%r|/*$|, '') : nil) || @url || ENV['ELASTICSEARCH_URL'] || "http://localhost:9200"
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.client(klass=nil)
|
data/lib/tire/dsl.rb
CHANGED
data/lib/tire/http/client.rb
CHANGED
@@ -5,33 +5,44 @@ module Tire
|
|
5
5
|
module Client
|
6
6
|
|
7
7
|
class RestClient
|
8
|
+
ConnectionExceptions = [::RestClient::ServerBrokeConnection, ::RestClient::RequestTimeout]
|
8
9
|
|
9
10
|
def self.get(url, data=nil)
|
10
11
|
perform ::RestClient::Request.new(:method => :get, :url => url, :payload => data).execute
|
12
|
+
rescue *ConnectionExceptions
|
13
|
+
raise
|
11
14
|
rescue ::RestClient::Exception => e
|
12
15
|
Response.new e.http_body, e.http_code
|
13
16
|
end
|
14
17
|
|
15
18
|
def self.post(url, data)
|
16
19
|
perform ::RestClient.post(url, data)
|
20
|
+
rescue *ConnectionExceptions
|
21
|
+
raise
|
17
22
|
rescue ::RestClient::Exception => e
|
18
23
|
Response.new e.http_body, e.http_code
|
19
24
|
end
|
20
25
|
|
21
26
|
def self.put(url, data)
|
22
27
|
perform ::RestClient.put(url, data)
|
28
|
+
rescue *ConnectionExceptions
|
29
|
+
raise
|
23
30
|
rescue ::RestClient::Exception => e
|
24
31
|
Response.new e.http_body, e.http_code
|
25
32
|
end
|
26
33
|
|
27
34
|
def self.delete(url)
|
28
35
|
perform ::RestClient.delete(url)
|
36
|
+
rescue *ConnectionExceptions
|
37
|
+
raise
|
29
38
|
rescue ::RestClient::Exception => e
|
30
39
|
Response.new e.http_body, e.http_code
|
31
40
|
end
|
32
41
|
|
33
42
|
def self.head(url)
|
34
43
|
perform ::RestClient.head(url)
|
44
|
+
rescue *ConnectionExceptions
|
45
|
+
raise
|
35
46
|
rescue ::RestClient::Exception => e
|
36
47
|
Response.new e.http_body, e.http_code
|
37
48
|
end
|
@@ -8,15 +8,22 @@ module Tire
|
|
8
8
|
|
9
9
|
class Curb
|
10
10
|
@client = ::Curl::Easy.new
|
11
|
+
@client.resolve_mode = :ipv4
|
12
|
+
|
11
13
|
# @client.verbose = true
|
12
14
|
|
13
15
|
def self.get(url, data=nil)
|
14
16
|
@client.url = url
|
15
|
-
|
17
|
+
|
16
18
|
# FIXME: Curb cannot post bodies with GET requests?
|
17
19
|
# Roy Fielding seems to approve:
|
18
20
|
# <http://tech.groups.yahoo.com/group/rest-discuss/message/9962>
|
19
|
-
|
21
|
+
if data
|
22
|
+
@client.post_body = data
|
23
|
+
@client.http_post
|
24
|
+
else
|
25
|
+
@client.http_get
|
26
|
+
end
|
20
27
|
Response.new @client.body_str, @client.response_code
|
21
28
|
end
|
22
29
|
|
data/lib/tire/index.rb
CHANGED
@@ -85,7 +85,7 @@ module Tire
|
|
85
85
|
response = Configuration.client.post("#{Configuration.url}/_bulk", payload.join("\n"))
|
86
86
|
raise RuntimeError, "#{response.code} > #{response.body}" if response.failure?
|
87
87
|
response
|
88
|
-
rescue
|
88
|
+
rescue StandardError => error
|
89
89
|
if count < tries
|
90
90
|
count += 1
|
91
91
|
STDERR.puts "[ERROR] #{error.message}, retrying (#{count})..."
|
@@ -152,8 +152,8 @@ module Tire
|
|
152
152
|
h = MultiJson.decode(@response.body)
|
153
153
|
if Configuration.wrapper == Hash then h
|
154
154
|
else
|
155
|
-
|
156
|
-
document = h['_source']
|
155
|
+
return nil if h['exists'] == false
|
156
|
+
document = h['_source'] || h['fields'] || {}
|
157
157
|
document.update('id' => h['_id'], '_type' => h['_type'], '_index' => h['_index'], '_version' => h['_version'])
|
158
158
|
Configuration.wrapper.new(document)
|
159
159
|
end
|
@@ -177,7 +177,7 @@ module Tire
|
|
177
177
|
MultiJson.decode(@response.body)['ok']
|
178
178
|
|
179
179
|
ensure
|
180
|
-
curl = %Q|curl -X POST "#{Configuration.url}/#{@name}/
|
180
|
+
curl = %Q|curl -X POST "#{Configuration.url}/#{@name}/_open"|
|
181
181
|
logged('_open', curl)
|
182
182
|
end
|
183
183
|
|
data/lib/tire/model/indexing.rb
CHANGED
@@ -38,8 +38,9 @@ module Tire
|
|
38
38
|
# class Article
|
39
39
|
# # ...
|
40
40
|
# mapping :_source => { :compress => true } do
|
41
|
-
# indexes :id, :
|
42
|
-
# indexes :title, :
|
41
|
+
# indexes :id, :index => :not_analyzed
|
42
|
+
# indexes :title, :analyzer => 'snowball', :boost => 100
|
43
|
+
# indexes :words, :as => 'content.split(/\W/).length'
|
43
44
|
# # ...
|
44
45
|
# end
|
45
46
|
# end
|
@@ -66,6 +67,10 @@ module Tire
|
|
66
67
|
#
|
67
68
|
# * Use different analyzer for indexing a property: `indexes :title, :analyzer => 'snowball'`
|
68
69
|
#
|
70
|
+
# * Use the `:as` option to dynamically define the serialized property value, eg:
|
71
|
+
#
|
72
|
+
# :as => 'content.split(/\W/).length'
|
73
|
+
#
|
69
74
|
# Please refer to the
|
70
75
|
# [_mapping_ documentation](http://www.elasticsearch.org/guide/reference/mapping/index.html)
|
71
76
|
# for more information.
|
@@ -9,11 +9,50 @@ module Tire
|
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
|
12
|
+
# Define property of the model:
|
13
|
+
#
|
14
|
+
# class Article
|
15
|
+
# include Tire::Model::Persistence
|
16
|
+
#
|
17
|
+
# property :title, :analyzer => 'snowball'
|
18
|
+
# property :published, :type => 'date'
|
19
|
+
# property :tags, :analyzer => 'keywords', :default => []
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# You can pass mapping definition for ElasticSearch in the options Hash.
|
23
|
+
#
|
24
|
+
# You can define default property values.
|
25
|
+
#
|
12
26
|
def property(name, options = {})
|
13
|
-
|
27
|
+
|
28
|
+
# Define attribute reader:
|
29
|
+
define_method("#{name}") do
|
30
|
+
instance_variable_get(:"@#{name}")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Define attribute writer:
|
34
|
+
define_method("#{name}=") do |value|
|
35
|
+
instance_variable_set(:"@#{name}", value)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Save the property in properties array:
|
14
39
|
properties << name.to_s unless properties.include?(name.to_s)
|
40
|
+
|
41
|
+
# Define convenience <NAME>? method:
|
15
42
|
define_query_method name.to_sym
|
43
|
+
|
44
|
+
# ActiveModel compatibility. NEEDED?
|
16
45
|
define_attribute_methods [name.to_sym]
|
46
|
+
|
47
|
+
# Save property default value (when relevant):
|
48
|
+
unless (default_value = options.delete(:default)).nil?
|
49
|
+
property_defaults[name.to_sym] = default_value
|
50
|
+
end
|
51
|
+
|
52
|
+
# Save property casting (when relevant):
|
53
|
+
property_types[name.to_sym] = options[:class] if options[:class]
|
54
|
+
|
55
|
+
# Store mapping for the property:
|
17
56
|
mapping[name] = options
|
18
57
|
self
|
19
58
|
end
|
@@ -22,6 +61,14 @@ module Tire
|
|
22
61
|
@properties ||= []
|
23
62
|
end
|
24
63
|
|
64
|
+
def property_defaults
|
65
|
+
@property_defaults ||= {}
|
66
|
+
end
|
67
|
+
|
68
|
+
def property_types
|
69
|
+
@property_types ||= {}
|
70
|
+
end
|
71
|
+
|
25
72
|
private
|
26
73
|
|
27
74
|
def define_query_method name
|
@@ -35,7 +82,14 @@ module Tire
|
|
35
82
|
attr_accessor :id
|
36
83
|
|
37
84
|
def initialize(attributes={})
|
38
|
-
|
85
|
+
# Make a copy of objects in the property defaults hash, so default values such as `[]` or `{ foo: [] }` are left intact
|
86
|
+
property_defaults = self.class.property_defaults.inject({}) do |hash, item|
|
87
|
+
key, value = item
|
88
|
+
hash[key.to_s] = value.class.respond_to?(:new) ? value.clone : value
|
89
|
+
hash
|
90
|
+
end
|
91
|
+
|
92
|
+
__update_attributes(property_defaults.merge(attributes))
|
39
93
|
end
|
40
94
|
|
41
95
|
def attributes
|
@@ -52,6 +106,34 @@ module Tire
|
|
52
106
|
end
|
53
107
|
alias :has_property? :has_attribute?
|
54
108
|
|
109
|
+
def __update_attributes(attributes)
|
110
|
+
attributes.each { |name, value| send "#{name}=", __cast_value(name, value) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Casts the values according to the <tt>:class</tt> option set when
|
114
|
+
# defining the property, cast Hashes as Hashr[http://rubygems.org/gems/hashr]
|
115
|
+
# instances and automatically convert UTC formatted strings to Time.
|
116
|
+
#
|
117
|
+
def __cast_value(name, value)
|
118
|
+
case
|
119
|
+
|
120
|
+
when klass = self.class.property_types[name.to_sym]
|
121
|
+
if klass.is_a?(Array) && value.is_a?(Array)
|
122
|
+
value.map { |v| klass.first.new(v) }
|
123
|
+
else
|
124
|
+
klass.new(value)
|
125
|
+
end
|
126
|
+
|
127
|
+
when value.is_a?(Hash)
|
128
|
+
Hashr.new(value)
|
129
|
+
|
130
|
+
else
|
131
|
+
# Strings formatted as <http://en.wikipedia.org/wiki/ISO8601> are automatically converted to Time
|
132
|
+
value = Time.parse(value) if value.is_a?(String) && value =~ /^\d{4}[\/\-]\d{2}[\/\-]\d{2}T\d{2}\:\d{2}\:\d{2}Z$/
|
133
|
+
value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
55
137
|
end
|
56
138
|
|
57
139
|
end
|
@@ -16,9 +16,12 @@ module Tire
|
|
16
16
|
options = args.pop if args.last.is_a?(Hash)
|
17
17
|
args.flatten!
|
18
18
|
if args.size > 1
|
19
|
-
Tire::Search::Search.new(index.name)
|
20
|
-
query
|
21
|
-
|
19
|
+
Tire::Search::Search.new(index.name) do |search|
|
20
|
+
search.query do |query|
|
21
|
+
query.ids(args, document_type)
|
22
|
+
end
|
23
|
+
search.size args.size
|
24
|
+
end.results
|
22
25
|
else
|
23
26
|
case args = args.pop
|
24
27
|
when Fixnum, String
|
@@ -38,7 +41,7 @@ module Tire
|
|
38
41
|
old_wrapper = Tire::Configuration.wrapper
|
39
42
|
Tire::Configuration.wrapper self
|
40
43
|
s = Tire::Search::Search.new(index.name).query { all }
|
41
|
-
s.
|
44
|
+
s.results
|
42
45
|
ensure
|
43
46
|
Tire::Configuration.wrapper old_wrapper
|
44
47
|
end
|
@@ -48,7 +51,7 @@ module Tire
|
|
48
51
|
old_wrapper = Tire::Configuration.wrapper
|
49
52
|
Tire::Configuration.wrapper self
|
50
53
|
s = Tire::Search::Search.new(index.name).query { all }.size(1)
|
51
|
-
s.
|
54
|
+
s.results.first
|
52
55
|
ensure
|
53
56
|
Tire::Configuration.wrapper old_wrapper
|
54
57
|
end
|
@@ -30,14 +30,12 @@ module Tire
|
|
30
30
|
module InstanceMethods
|
31
31
|
|
32
32
|
def update_attribute(name, value)
|
33
|
-
|
33
|
+
__update_attributes name => value
|
34
34
|
save
|
35
35
|
end
|
36
36
|
|
37
37
|
def update_attributes(attributes={})
|
38
|
-
attributes
|
39
|
-
send("#{name}=", value)
|
40
|
-
end
|
38
|
+
__update_attributes attributes
|
41
39
|
save
|
42
40
|
end
|
43
41
|
|
@@ -50,6 +50,18 @@ module Tire
|
|
50
50
|
define_method("#{attr}") { @attributes[attr] }
|
51
51
|
end
|
52
52
|
|
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 }
|
57
|
+
|
58
|
+
self.__search_without_persistence(*args, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.__search_without_persistence(*args, &block)
|
62
|
+
self.tire.search(*args, &block)
|
63
|
+
end
|
64
|
+
|
53
65
|
end
|
54
66
|
|
55
67
|
end
|
data/lib/tire/model/search.rb
CHANGED
@@ -94,7 +94,7 @@ module Tire
|
|
94
94
|
s.fields Array(options[:fields]) if options[:fields]
|
95
95
|
end
|
96
96
|
|
97
|
-
s.
|
97
|
+
s.results
|
98
98
|
end
|
99
99
|
|
100
100
|
# Returns a Tire::Index instance for this model.
|
@@ -151,13 +151,29 @@ module Tire
|
|
151
151
|
# If you do define the mapping for _ElasticSearch_, only attributes
|
152
152
|
# declared in the mapping are serialized.
|
153
153
|
#
|
154
|
+
# For properties declared with the `:as` option, the passed String or Proc
|
155
|
+
# is evaluated in the instance context.
|
156
|
+
#
|
154
157
|
def to_indexed_json
|
155
158
|
if instance.class.tire.mapping.empty?
|
159
|
+
# Reject the id and type keys
|
156
160
|
instance.to_hash.reject {|key,_| key.to_s == 'id' || key.to_s == 'type' }.to_json
|
157
161
|
else
|
158
|
-
instance.
|
159
|
-
|
160
|
-
|
162
|
+
mapping = instance.class.tire.mapping
|
163
|
+
# Reject keys not declared in mapping
|
164
|
+
hash = instance.to_hash.reject { |key, value| ! mapping.keys.map(&:to_s).include?(key.to_s) }
|
165
|
+
|
166
|
+
# Evalute the `:as` options
|
167
|
+
mapping.each do |key, options|
|
168
|
+
case options[:as]
|
169
|
+
when String
|
170
|
+
hash[key] = instance.instance_eval(options[:as])
|
171
|
+
when Proc
|
172
|
+
hash[key] = instance.instance_eval(&options[:as])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
hash.to_json
|
161
177
|
end
|
162
178
|
end
|
163
179
|
|
data/lib/tire/search/facet.rb
CHANGED
@@ -11,7 +11,7 @@ module Tire
|
|
11
11
|
def initialize(name, options={}, &block)
|
12
12
|
@name = name
|
13
13
|
@options = options
|
14
|
-
self.instance_eval(&block) if block_given?
|
14
|
+
block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
|
15
15
|
end
|
16
16
|
|
17
17
|
def terms(field, options={})
|
@@ -37,6 +37,16 @@ module Tire
|
|
37
37
|
self
|
38
38
|
end
|
39
39
|
|
40
|
+
def statistical(field, options={})
|
41
|
+
@value = { :statistical => (options.delete(:statistical) || {:field => field}.update(options)) }
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def terms_stats(key_field, value_field, options={})
|
46
|
+
@value = { :terms_stats => {:key_field => key_field, :value_field => value_field}.update(options) }
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
40
50
|
def query(&block)
|
41
51
|
@value = { :query => Query.new(&block).to_hash }
|
42
52
|
end
|
data/lib/tire/search/query.rb
CHANGED
@@ -21,6 +21,12 @@ module Tire
|
|
21
21
|
@value = { :range => { field => value } }
|
22
22
|
end
|
23
23
|
|
24
|
+
def text(field, value, options={})
|
25
|
+
query_options = { :query => value }.update(options)
|
26
|
+
@value = { :text => { field => query_options } }
|
27
|
+
@value
|
28
|
+
end
|
29
|
+
|
24
30
|
def string(value, options={})
|
25
31
|
@value = { :query_string => { :query => value } }
|
26
32
|
@value[:query_string].update(options)
|
@@ -121,8 +127,9 @@ module Tire
|
|
121
127
|
end
|
122
128
|
|
123
129
|
def filter(type, *options)
|
124
|
-
@value[:filter] ||=
|
125
|
-
@value[:filter]
|
130
|
+
@value[:filter] ||= {}
|
131
|
+
@value[:filter][:and] ||= []
|
132
|
+
@value[:filter][:and] << Filter.new(type, *options).to_hash
|
126
133
|
@value
|
127
134
|
end
|
128
135
|
|