load_balanced_tire 0.1
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/.gitignore +14 -0
- data/.travis.yml +29 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +760 -0
- data/Rakefile +78 -0
- data/examples/rails-application-template.rb +249 -0
- data/examples/tire-dsl.rb +876 -0
- data/lib/tire.rb +55 -0
- data/lib/tire/alias.rb +296 -0
- data/lib/tire/configuration.rb +30 -0
- data/lib/tire/dsl.rb +43 -0
- data/lib/tire/http/client.rb +62 -0
- data/lib/tire/http/clients/curb.rb +61 -0
- data/lib/tire/http/clients/faraday.rb +71 -0
- data/lib/tire/http/response.rb +27 -0
- data/lib/tire/index.rb +361 -0
- data/lib/tire/logger.rb +60 -0
- data/lib/tire/model/callbacks.rb +40 -0
- data/lib/tire/model/import.rb +26 -0
- data/lib/tire/model/indexing.rb +128 -0
- data/lib/tire/model/naming.rb +100 -0
- data/lib/tire/model/percolate.rb +99 -0
- data/lib/tire/model/persistence.rb +71 -0
- data/lib/tire/model/persistence/attributes.rb +143 -0
- data/lib/tire/model/persistence/finders.rb +66 -0
- data/lib/tire/model/persistence/storage.rb +69 -0
- data/lib/tire/model/search.rb +307 -0
- data/lib/tire/results/collection.rb +114 -0
- data/lib/tire/results/item.rb +86 -0
- data/lib/tire/results/pagination.rb +54 -0
- data/lib/tire/rubyext/hash.rb +8 -0
- data/lib/tire/rubyext/ruby_1_8.rb +7 -0
- data/lib/tire/rubyext/symbol.rb +11 -0
- data/lib/tire/search.rb +188 -0
- data/lib/tire/search/facet.rb +74 -0
- data/lib/tire/search/filter.rb +28 -0
- data/lib/tire/search/highlight.rb +37 -0
- data/lib/tire/search/query.rb +186 -0
- data/lib/tire/search/scan.rb +114 -0
- data/lib/tire/search/script_field.rb +23 -0
- data/lib/tire/search/sort.rb +25 -0
- data/lib/tire/tasks.rb +135 -0
- data/lib/tire/utils.rb +17 -0
- data/lib/tire/version.rb +22 -0
- data/test/fixtures/articles/1.json +1 -0
- data/test/fixtures/articles/2.json +1 -0
- data/test/fixtures/articles/3.json +1 -0
- data/test/fixtures/articles/4.json +1 -0
- data/test/fixtures/articles/5.json +1 -0
- data/test/integration/active_model_indexing_test.rb +51 -0
- data/test/integration/active_model_searchable_test.rb +114 -0
- data/test/integration/active_record_searchable_test.rb +446 -0
- data/test/integration/boolean_queries_test.rb +43 -0
- data/test/integration/count_test.rb +34 -0
- data/test/integration/custom_score_queries_test.rb +88 -0
- data/test/integration/dis_max_queries_test.rb +68 -0
- data/test/integration/dsl_search_test.rb +22 -0
- data/test/integration/explanation_test.rb +44 -0
- data/test/integration/facets_test.rb +259 -0
- data/test/integration/filtered_queries_test.rb +66 -0
- data/test/integration/filters_test.rb +63 -0
- data/test/integration/fuzzy_queries_test.rb +20 -0
- data/test/integration/highlight_test.rb +64 -0
- data/test/integration/index_aliases_test.rb +122 -0
- data/test/integration/index_mapping_test.rb +43 -0
- data/test/integration/index_store_test.rb +96 -0
- data/test/integration/index_update_document_test.rb +111 -0
- data/test/integration/mongoid_searchable_test.rb +309 -0
- data/test/integration/percolator_test.rb +111 -0
- data/test/integration/persistent_model_test.rb +130 -0
- data/test/integration/prefix_query_test.rb +43 -0
- data/test/integration/query_return_version_test.rb +70 -0
- data/test/integration/query_string_test.rb +52 -0
- data/test/integration/range_queries_test.rb +36 -0
- data/test/integration/reindex_test.rb +46 -0
- data/test/integration/results_test.rb +39 -0
- data/test/integration/scan_test.rb +56 -0
- data/test/integration/script_fields_test.rb +38 -0
- data/test/integration/sort_test.rb +36 -0
- data/test/integration/text_query_test.rb +39 -0
- data/test/models/active_model_article.rb +31 -0
- data/test/models/active_model_article_with_callbacks.rb +49 -0
- data/test/models/active_model_article_with_custom_document_type.rb +7 -0
- data/test/models/active_model_article_with_custom_index_name.rb +7 -0
- data/test/models/active_record_models.rb +122 -0
- data/test/models/article.rb +15 -0
- data/test/models/mongoid_models.rb +97 -0
- data/test/models/persistent_article.rb +11 -0
- data/test/models/persistent_article_in_namespace.rb +12 -0
- data/test/models/persistent_article_with_casting.rb +28 -0
- data/test/models/persistent_article_with_defaults.rb +11 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
- data/test/models/supermodel_article.rb +17 -0
- data/test/models/validated_model.rb +11 -0
- data/test/test_helper.rb +93 -0
- data/test/unit/active_model_lint_test.rb +17 -0
- data/test/unit/configuration_test.rb +74 -0
- data/test/unit/http_client_test.rb +76 -0
- data/test/unit/http_response_test.rb +49 -0
- data/test/unit/index_alias_test.rb +275 -0
- data/test/unit/index_test.rb +894 -0
- data/test/unit/logger_test.rb +125 -0
- data/test/unit/model_callbacks_test.rb +116 -0
- data/test/unit/model_import_test.rb +71 -0
- data/test/unit/model_persistence_test.rb +528 -0
- data/test/unit/model_search_test.rb +913 -0
- data/test/unit/results_collection_test.rb +281 -0
- data/test/unit/results_item_test.rb +162 -0
- data/test/unit/rubyext_test.rb +66 -0
- data/test/unit/search_facet_test.rb +153 -0
- data/test/unit/search_filter_test.rb +42 -0
- data/test/unit/search_highlight_test.rb +46 -0
- data/test/unit/search_query_test.rb +301 -0
- data/test/unit/search_scan_test.rb +113 -0
- data/test/unit/search_script_field_test.rb +26 -0
- data/test/unit/search_sort_test.rb +50 -0
- data/test/unit/search_test.rb +499 -0
- data/test/unit/tire_test.rb +126 -0
- data/tire.gemspec +90 -0
- metadata +549 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module Tire
|
2
|
+
module Model
|
3
|
+
|
4
|
+
# Allows to use _ElasticSearch_ as a primary database (storage).
|
5
|
+
#
|
6
|
+
# Contains all the `Tire::Model::Search` features and provides
|
7
|
+
# an [_ActiveModel_](http://rubygems.org/gems/activemodel)-compatible
|
8
|
+
# interface for persistance.
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# class Article
|
13
|
+
# include Tire::Model::Persistence
|
14
|
+
#
|
15
|
+
# property :title
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Article.create :id => 1, :title => 'One'
|
19
|
+
#
|
20
|
+
# article = Article.find
|
21
|
+
#
|
22
|
+
# article.destroy
|
23
|
+
#
|
24
|
+
module Persistence
|
25
|
+
|
26
|
+
def self.included(base)
|
27
|
+
|
28
|
+
base.class_eval do
|
29
|
+
include ActiveModel::AttributeMethods
|
30
|
+
include ActiveModel::Validations
|
31
|
+
include ActiveModel::Serialization
|
32
|
+
include ActiveModel::Serializers::JSON
|
33
|
+
include ActiveModel::Naming
|
34
|
+
include ActiveModel::Conversion
|
35
|
+
|
36
|
+
extend ActiveModel::Callbacks
|
37
|
+
define_model_callbacks :save, :destroy
|
38
|
+
|
39
|
+
include Tire::Model::Search
|
40
|
+
include Tire::Model::Callbacks
|
41
|
+
|
42
|
+
extend Persistence::Finders::ClassMethods
|
43
|
+
extend Persistence::Attributes::ClassMethods
|
44
|
+
include Persistence::Attributes::InstanceMethods
|
45
|
+
|
46
|
+
include Persistence::Storage
|
47
|
+
|
48
|
+
['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches', '_explanation'].each do |attr|
|
49
|
+
define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
|
50
|
+
define_method("#{attr}") { @attributes[attr] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.search(*args, &block)
|
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 }
|
56
|
+
|
57
|
+
self.__search_without_persistence(*args, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.__search_without_persistence(*args, &block)
|
61
|
+
self.tire.search(*args, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Tire
|
2
|
+
module Model
|
3
|
+
|
4
|
+
module Persistence
|
5
|
+
|
6
|
+
# Provides infrastructure for declaring the model properties and accessing them.
|
7
|
+
#
|
8
|
+
module Attributes
|
9
|
+
|
10
|
+
module ClassMethods
|
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
|
+
#
|
26
|
+
def property(name, options = {})
|
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:
|
39
|
+
properties << name.to_s unless properties.include?(name.to_s)
|
40
|
+
|
41
|
+
# Define convenience <NAME>? method:
|
42
|
+
define_query_method name.to_sym
|
43
|
+
|
44
|
+
# ActiveModel compatibility. NEEDED?
|
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:
|
56
|
+
mapping[name] = options
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def properties
|
61
|
+
@properties ||= []
|
62
|
+
end
|
63
|
+
|
64
|
+
def property_defaults
|
65
|
+
@property_defaults ||= {}
|
66
|
+
end
|
67
|
+
|
68
|
+
def property_types
|
69
|
+
@property_types ||= {}
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def define_query_method name
|
75
|
+
define_method("#{name}?") { !! send(name) }
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
module InstanceMethods
|
81
|
+
|
82
|
+
attr_accessor :id
|
83
|
+
|
84
|
+
def initialize(attributes={})
|
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))
|
93
|
+
end
|
94
|
+
|
95
|
+
def attributes
|
96
|
+
self.class.properties.
|
97
|
+
inject( self.id ? {'id' => self.id} : {} ) {|attributes, key| attributes[key] = send(key); attributes}
|
98
|
+
end
|
99
|
+
|
100
|
+
def attribute_names
|
101
|
+
self.class.properties.sort
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_attribute?(name)
|
105
|
+
properties.include?(name.to_s)
|
106
|
+
end
|
107
|
+
alias :has_property? :has_attribute?
|
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
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Tire
|
2
|
+
module Model
|
3
|
+
|
4
|
+
module Persistence
|
5
|
+
|
6
|
+
# Provides infrastructure for an _ActiveRecord_-like interface for finding records.
|
7
|
+
#
|
8
|
+
module Finders
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def find *args
|
13
|
+
# TODO: Options like `sort`
|
14
|
+
old_wrapper = Tire::Configuration.wrapper
|
15
|
+
Tire::Configuration.wrapper self
|
16
|
+
options = args.pop if args.last.is_a?(Hash)
|
17
|
+
args.flatten!
|
18
|
+
if args.size > 1
|
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
|
25
|
+
else
|
26
|
+
case args = args.pop
|
27
|
+
when Fixnum, String
|
28
|
+
index.retrieve document_type, args
|
29
|
+
when :all, :first
|
30
|
+
send(args)
|
31
|
+
else
|
32
|
+
raise ArgumentError, "Please pass either ID as Fixnum or String, or :all, :first as an argument"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
ensure
|
36
|
+
Tire::Configuration.wrapper old_wrapper
|
37
|
+
end
|
38
|
+
|
39
|
+
def all
|
40
|
+
# TODO: Options like `sort`; Possibly `filters`
|
41
|
+
old_wrapper = Tire::Configuration.wrapper
|
42
|
+
Tire::Configuration.wrapper self
|
43
|
+
s = Tire::Search::Search.new(index.name).query { all }
|
44
|
+
s.version(true).results
|
45
|
+
ensure
|
46
|
+
Tire::Configuration.wrapper old_wrapper
|
47
|
+
end
|
48
|
+
|
49
|
+
def first
|
50
|
+
# TODO: Options like `sort`; Possibly `filters`
|
51
|
+
old_wrapper = Tire::Configuration.wrapper
|
52
|
+
Tire::Configuration.wrapper self
|
53
|
+
s = Tire::Search::Search.new(index.name).query { all }.size(1)
|
54
|
+
s.version(true).results.first
|
55
|
+
ensure
|
56
|
+
Tire::Configuration.wrapper old_wrapper
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Tire
|
2
|
+
module Model
|
3
|
+
|
4
|
+
module Persistence
|
5
|
+
|
6
|
+
# Provides infrastructure for storing records in _ElasticSearch_.
|
7
|
+
#
|
8
|
+
module Storage
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
|
12
|
+
base.class_eval do
|
13
|
+
extend ClassMethods
|
14
|
+
include InstanceMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
def create(args={})
|
22
|
+
document = new(args)
|
23
|
+
return false unless document.valid?
|
24
|
+
document.save
|
25
|
+
document
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
module InstanceMethods
|
31
|
+
|
32
|
+
def update_attribute(name, value)
|
33
|
+
__update_attributes name => value
|
34
|
+
save
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_attributes(attributes={})
|
38
|
+
__update_attributes attributes
|
39
|
+
save
|
40
|
+
end
|
41
|
+
|
42
|
+
def save
|
43
|
+
return false unless valid?
|
44
|
+
run_callbacks :save do
|
45
|
+
# Document#id is set in the +update_elasticsearch_index+ method,
|
46
|
+
# where we have access to the JSON response
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy
|
52
|
+
run_callbacks :destroy do
|
53
|
+
@destroyed = true
|
54
|
+
end
|
55
|
+
self.freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroyed? ; !!@destroyed; end
|
59
|
+
def persisted? ; !!id && !!_version; end
|
60
|
+
def new_record? ; !persisted?; end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,307 @@
|
|
1
|
+
module Tire
|
2
|
+
module Model
|
3
|
+
|
4
|
+
# Main module containing the search infrastructure for ActiveModel classes.
|
5
|
+
#
|
6
|
+
# By including this module, you'll provide the model with facilities to
|
7
|
+
# perform searches against index, define index settings and mappings,
|
8
|
+
# access the index object, etc.
|
9
|
+
#
|
10
|
+
# All the _Tire_ methods are accessible via the "proxy" class and instance
|
11
|
+
# methods of the model, named `tire`, eg. `Article.tire.search 'foo'`.
|
12
|
+
#
|
13
|
+
# When there's no clash with a method in the class (your own, defined by another gem, etc)
|
14
|
+
# _Tire_ will bring these methods to the top-level namespace of the class,
|
15
|
+
# eg. `Article.search 'foo'`.
|
16
|
+
#
|
17
|
+
# You'll find the relevant methods in the ClassMethods and InstanceMethods module.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
module Search
|
21
|
+
|
22
|
+
# Alias for Tire::Model::Naming::ClassMethods.index_prefix
|
23
|
+
#
|
24
|
+
def self.index_prefix(*args)
|
25
|
+
Naming::ClassMethods.index_prefix(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
|
30
|
+
# Returns search results for a given query.
|
31
|
+
#
|
32
|
+
# Query can be passed simply as a String:
|
33
|
+
#
|
34
|
+
# Article.search 'love'
|
35
|
+
#
|
36
|
+
# Any options, such as pagination or sorting, can be passed as a second argument:
|
37
|
+
#
|
38
|
+
# Article.search 'love', :per_page => 25, :page => 2
|
39
|
+
# Article.search 'love', :sort => 'title'
|
40
|
+
#
|
41
|
+
# For more powerful query definition, use the query DSL passed as a block:
|
42
|
+
#
|
43
|
+
# Article.search do
|
44
|
+
# query { terms :tags, ['ruby', 'python'] }
|
45
|
+
# facet 'tags' { terms :tags }
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# You can pass options as the first argument, in this case:
|
49
|
+
#
|
50
|
+
# Article.search :per_page => 25, :page => 2 do
|
51
|
+
# query { string 'love' }
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# This methods returns a Tire::Results::Collection instance, containing instances
|
55
|
+
# of Tire::Results::Item, populated by the data available in _ElasticSearch, by default.
|
56
|
+
#
|
57
|
+
# If you'd like to load the "real" models from the database, you may use the `:load` option:
|
58
|
+
#
|
59
|
+
# Article.search 'love', :load => true
|
60
|
+
#
|
61
|
+
# You can pass options as a Hash to the model's `find` method:
|
62
|
+
#
|
63
|
+
# Article.search :load => { :include => 'comments' } do ... end
|
64
|
+
#
|
65
|
+
def search(*args, &block)
|
66
|
+
default_options = {:type => document_type, :index => index.name}
|
67
|
+
|
68
|
+
if block_given?
|
69
|
+
options = args.shift || {}
|
70
|
+
else
|
71
|
+
query, options = args
|
72
|
+
options ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
sort = Array( options[:order] || options[:sort] )
|
76
|
+
options = default_options.update(options)
|
77
|
+
|
78
|
+
s = Tire::Search::Search.new(options.delete(:index), options)
|
79
|
+
s.size( options[:per_page].to_i ) if options[:per_page]
|
80
|
+
s.from( options[:page].to_i <= 1 ? 0 : (options[:per_page].to_i * (options[:page].to_i-1)) ) if options[:page] && options[:per_page]
|
81
|
+
s.sort do
|
82
|
+
sort.each do |t|
|
83
|
+
field_name, direction = t.split(' ')
|
84
|
+
by field_name, direction
|
85
|
+
end
|
86
|
+
end unless sort.empty?
|
87
|
+
|
88
|
+
if version = options.delete(:version); s.version(version); end
|
89
|
+
|
90
|
+
if block_given?
|
91
|
+
block.arity < 1 ? s.instance_eval(&block) : block.call(s)
|
92
|
+
else
|
93
|
+
s.query { string query }
|
94
|
+
# TODO: Actualy, allow passing all the valid options from
|
95
|
+
# <http://www.elasticsearch.org/guide/reference/api/search/uri-request.html>
|
96
|
+
s.fields Array(options[:fields]) if options[:fields]
|
97
|
+
end
|
98
|
+
|
99
|
+
s.results
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns a Tire::Index instance for this model.
|
103
|
+
#
|
104
|
+
# Example usage: `Article.index.refresh`.
|
105
|
+
#
|
106
|
+
def index
|
107
|
+
name = index_name.respond_to?(:to_proc) ? klass.instance_eval(&index_name) : index_name
|
108
|
+
@index = Index.new(name)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
module InstanceMethods
|
114
|
+
|
115
|
+
# Returns a Tire::Index instance for this instance of the model.
|
116
|
+
#
|
117
|
+
# Example usage: `@article.index.refresh`.
|
118
|
+
#
|
119
|
+
def index
|
120
|
+
instance.class.tire.index
|
121
|
+
end
|
122
|
+
|
123
|
+
# Updates the index in _ElasticSearch_.
|
124
|
+
#
|
125
|
+
# On model instance create or update, it will store its serialized representation in the index.
|
126
|
+
#
|
127
|
+
# On model destroy, it will remove the corresponding document from the index.
|
128
|
+
#
|
129
|
+
# It will also execute any `<after|before>_update_elasticsearch_index` callback hooks.
|
130
|
+
#
|
131
|
+
def update_index
|
132
|
+
instance.send :_run_update_elasticsearch_index_callbacks do
|
133
|
+
if instance.destroyed?
|
134
|
+
index.remove instance
|
135
|
+
else
|
136
|
+
response = index.store( instance, {:percolate => percolator} )
|
137
|
+
instance.id ||= response['_id'] if instance.respond_to?(:id=)
|
138
|
+
instance._index = response['_index'] if instance.respond_to?(:_index=)
|
139
|
+
instance._type = response['_type'] if instance.respond_to?(:_type=)
|
140
|
+
instance._version = response['_version'] if instance.respond_to?(:_version=)
|
141
|
+
instance.matches = response['matches'] if instance.respond_to?(:matches=)
|
142
|
+
self
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
alias :update_elasticsearch_index :update_index
|
147
|
+
alias :update_elastic_search_index :update_index
|
148
|
+
|
149
|
+
# The default JSON serialization of the model, based on its `#to_hash` representation.
|
150
|
+
#
|
151
|
+
# If you don't define any mapping, the model is serialized as-is.
|
152
|
+
#
|
153
|
+
# If you do define the mapping for _ElasticSearch_, only attributes
|
154
|
+
# declared in the mapping are serialized.
|
155
|
+
#
|
156
|
+
# For properties declared with the `:as` option, the passed String or Proc
|
157
|
+
# is evaluated in the instance context.
|
158
|
+
#
|
159
|
+
def to_indexed_json
|
160
|
+
if instance.class.tire.mapping.empty?
|
161
|
+
# Reject the id and type keys
|
162
|
+
instance.to_hash.reject {|key,_| key.to_s == 'id' || key.to_s == 'type' }.to_json
|
163
|
+
else
|
164
|
+
mapping = instance.class.tire.mapping
|
165
|
+
# Reject keys not declared in mapping
|
166
|
+
hash = instance.to_hash.reject { |key, value| ! mapping.keys.map(&:to_s).include?(key.to_s) }
|
167
|
+
|
168
|
+
# Evalute the `:as` options
|
169
|
+
mapping.each do |key, options|
|
170
|
+
case options[:as]
|
171
|
+
when String
|
172
|
+
hash[key] = instance.instance_eval(options[:as])
|
173
|
+
when Proc
|
174
|
+
hash[key] = instance.instance_eval(&options[:as])
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
hash.to_json
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def matches
|
183
|
+
@attributes['matches']
|
184
|
+
end
|
185
|
+
|
186
|
+
def matches=(value)
|
187
|
+
@attributes ||= {}; @attributes['matches'] = value
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
module Loader
|
193
|
+
|
194
|
+
# Load the "real" model from the database via the corresponding model's `find` method.
|
195
|
+
#
|
196
|
+
# Notice that there's an option to eagerly load models with the `:load` option
|
197
|
+
# for the search method.
|
198
|
+
#
|
199
|
+
def load(options=nil)
|
200
|
+
options ? self.class.find(self.id, options) : self.class.find(self.id)
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
# An object containing _Tire's_ model class methods, accessed as `Article.tire`.
|
206
|
+
#
|
207
|
+
class ClassMethodsProxy
|
208
|
+
include Tire::Model::Naming::ClassMethods
|
209
|
+
include Tire::Model::Import::ClassMethods
|
210
|
+
include Tire::Model::Indexing::ClassMethods
|
211
|
+
include Tire::Model::Percolate::ClassMethods
|
212
|
+
include ClassMethods
|
213
|
+
|
214
|
+
INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
|
215
|
+
|
216
|
+
attr_reader :klass
|
217
|
+
def initialize(klass)
|
218
|
+
@klass = klass
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
# An object containing _Tire's_ model instance methods, accessed as `@article.tire`.
|
224
|
+
#
|
225
|
+
class InstanceMethodsProxy
|
226
|
+
include Tire::Model::Naming::InstanceMethods
|
227
|
+
include Tire::Model::Percolate::InstanceMethods
|
228
|
+
include InstanceMethods
|
229
|
+
|
230
|
+
INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
|
231
|
+
|
232
|
+
attr_reader :instance
|
233
|
+
def initialize(instance)
|
234
|
+
@instance = instance
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# A hook triggered by the `include Tire::Model::Search` statement in the model.
|
239
|
+
#
|
240
|
+
def self.included(base)
|
241
|
+
base.class_eval do
|
242
|
+
|
243
|
+
# Returns proxy to the _Tire's_ class methods.
|
244
|
+
#
|
245
|
+
def self.tire &block
|
246
|
+
@__tire__ ||= ClassMethodsProxy.new(self)
|
247
|
+
|
248
|
+
@__tire__.instance_eval(&block) if block_given?
|
249
|
+
@__tire__
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns proxy to the _Tire's_ instance methods.
|
253
|
+
#
|
254
|
+
def tire &block
|
255
|
+
@__tire__ ||= InstanceMethodsProxy.new(self)
|
256
|
+
|
257
|
+
@__tire__.instance_eval(&block) if block_given?
|
258
|
+
@__tire__
|
259
|
+
end
|
260
|
+
|
261
|
+
# Define _Tire's_ callbacks (<after|before>_update_elasticsearch_index).
|
262
|
+
#
|
263
|
+
define_model_callbacks(:update_elasticsearch_index, :only => [:after, :before]) if \
|
264
|
+
respond_to?(:define_model_callbacks)
|
265
|
+
|
266
|
+
# Serialize the model as a Hash.
|
267
|
+
#
|
268
|
+
# Uses `serializable_hash` representation of the model,
|
269
|
+
# unless implemented in the model already.
|
270
|
+
#
|
271
|
+
def to_hash
|
272
|
+
self.serializable_hash
|
273
|
+
end unless instance_methods.map(&:to_sym).include?(:to_hash)
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
# Alias _Tire's_ class methods in the top-level namespace of the model,
|
278
|
+
# unless there's a conflict with existing method.
|
279
|
+
#
|
280
|
+
ClassMethodsProxy::INTERFACE.each do |method|
|
281
|
+
base.class_eval <<-"end;", __FILE__, __LINE__ unless base.public_methods.map(&:to_sym).include?(method.to_sym)
|
282
|
+
def self.#{method}(*args, &block) # def search(*args, &block)
|
283
|
+
tire.__send__(#{method.inspect}, *args, &block) # tire.__send__(:search, *args, &block)
|
284
|
+
end # end
|
285
|
+
end;
|
286
|
+
end
|
287
|
+
|
288
|
+
# Alias _Tire's_ instance methods in the top-level namespace of the model,
|
289
|
+
# unless there's a conflict with existing method
|
290
|
+
InstanceMethodsProxy::INTERFACE.each do |method|
|
291
|
+
base.class_eval <<-"end;", __FILE__, __LINE__ unless base.instance_methods.map(&:to_sym).include?(method.to_sym)
|
292
|
+
def #{method}(*args, &block) # def to_indexed_json(*args, &block)
|
293
|
+
tire.__send__(#{method.inspect}, *args, &block) # tire.__send__(:to_indexed_json, *args, &block)
|
294
|
+
end # end
|
295
|
+
end;
|
296
|
+
end
|
297
|
+
|
298
|
+
# Include the `load` functionality in Results::Item
|
299
|
+
#
|
300
|
+
Results::Item.send :include, Loader
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
end
|