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.
Files changed (121) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +29 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.markdown +760 -0
  6. data/Rakefile +78 -0
  7. data/examples/rails-application-template.rb +249 -0
  8. data/examples/tire-dsl.rb +876 -0
  9. data/lib/tire.rb +55 -0
  10. data/lib/tire/alias.rb +296 -0
  11. data/lib/tire/configuration.rb +30 -0
  12. data/lib/tire/dsl.rb +43 -0
  13. data/lib/tire/http/client.rb +62 -0
  14. data/lib/tire/http/clients/curb.rb +61 -0
  15. data/lib/tire/http/clients/faraday.rb +71 -0
  16. data/lib/tire/http/response.rb +27 -0
  17. data/lib/tire/index.rb +361 -0
  18. data/lib/tire/logger.rb +60 -0
  19. data/lib/tire/model/callbacks.rb +40 -0
  20. data/lib/tire/model/import.rb +26 -0
  21. data/lib/tire/model/indexing.rb +128 -0
  22. data/lib/tire/model/naming.rb +100 -0
  23. data/lib/tire/model/percolate.rb +99 -0
  24. data/lib/tire/model/persistence.rb +71 -0
  25. data/lib/tire/model/persistence/attributes.rb +143 -0
  26. data/lib/tire/model/persistence/finders.rb +66 -0
  27. data/lib/tire/model/persistence/storage.rb +69 -0
  28. data/lib/tire/model/search.rb +307 -0
  29. data/lib/tire/results/collection.rb +114 -0
  30. data/lib/tire/results/item.rb +86 -0
  31. data/lib/tire/results/pagination.rb +54 -0
  32. data/lib/tire/rubyext/hash.rb +8 -0
  33. data/lib/tire/rubyext/ruby_1_8.rb +7 -0
  34. data/lib/tire/rubyext/symbol.rb +11 -0
  35. data/lib/tire/search.rb +188 -0
  36. data/lib/tire/search/facet.rb +74 -0
  37. data/lib/tire/search/filter.rb +28 -0
  38. data/lib/tire/search/highlight.rb +37 -0
  39. data/lib/tire/search/query.rb +186 -0
  40. data/lib/tire/search/scan.rb +114 -0
  41. data/lib/tire/search/script_field.rb +23 -0
  42. data/lib/tire/search/sort.rb +25 -0
  43. data/lib/tire/tasks.rb +135 -0
  44. data/lib/tire/utils.rb +17 -0
  45. data/lib/tire/version.rb +22 -0
  46. data/test/fixtures/articles/1.json +1 -0
  47. data/test/fixtures/articles/2.json +1 -0
  48. data/test/fixtures/articles/3.json +1 -0
  49. data/test/fixtures/articles/4.json +1 -0
  50. data/test/fixtures/articles/5.json +1 -0
  51. data/test/integration/active_model_indexing_test.rb +51 -0
  52. data/test/integration/active_model_searchable_test.rb +114 -0
  53. data/test/integration/active_record_searchable_test.rb +446 -0
  54. data/test/integration/boolean_queries_test.rb +43 -0
  55. data/test/integration/count_test.rb +34 -0
  56. data/test/integration/custom_score_queries_test.rb +88 -0
  57. data/test/integration/dis_max_queries_test.rb +68 -0
  58. data/test/integration/dsl_search_test.rb +22 -0
  59. data/test/integration/explanation_test.rb +44 -0
  60. data/test/integration/facets_test.rb +259 -0
  61. data/test/integration/filtered_queries_test.rb +66 -0
  62. data/test/integration/filters_test.rb +63 -0
  63. data/test/integration/fuzzy_queries_test.rb +20 -0
  64. data/test/integration/highlight_test.rb +64 -0
  65. data/test/integration/index_aliases_test.rb +122 -0
  66. data/test/integration/index_mapping_test.rb +43 -0
  67. data/test/integration/index_store_test.rb +96 -0
  68. data/test/integration/index_update_document_test.rb +111 -0
  69. data/test/integration/mongoid_searchable_test.rb +309 -0
  70. data/test/integration/percolator_test.rb +111 -0
  71. data/test/integration/persistent_model_test.rb +130 -0
  72. data/test/integration/prefix_query_test.rb +43 -0
  73. data/test/integration/query_return_version_test.rb +70 -0
  74. data/test/integration/query_string_test.rb +52 -0
  75. data/test/integration/range_queries_test.rb +36 -0
  76. data/test/integration/reindex_test.rb +46 -0
  77. data/test/integration/results_test.rb +39 -0
  78. data/test/integration/scan_test.rb +56 -0
  79. data/test/integration/script_fields_test.rb +38 -0
  80. data/test/integration/sort_test.rb +36 -0
  81. data/test/integration/text_query_test.rb +39 -0
  82. data/test/models/active_model_article.rb +31 -0
  83. data/test/models/active_model_article_with_callbacks.rb +49 -0
  84. data/test/models/active_model_article_with_custom_document_type.rb +7 -0
  85. data/test/models/active_model_article_with_custom_index_name.rb +7 -0
  86. data/test/models/active_record_models.rb +122 -0
  87. data/test/models/article.rb +15 -0
  88. data/test/models/mongoid_models.rb +97 -0
  89. data/test/models/persistent_article.rb +11 -0
  90. data/test/models/persistent_article_in_namespace.rb +12 -0
  91. data/test/models/persistent_article_with_casting.rb +28 -0
  92. data/test/models/persistent_article_with_defaults.rb +11 -0
  93. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  94. data/test/models/supermodel_article.rb +17 -0
  95. data/test/models/validated_model.rb +11 -0
  96. data/test/test_helper.rb +93 -0
  97. data/test/unit/active_model_lint_test.rb +17 -0
  98. data/test/unit/configuration_test.rb +74 -0
  99. data/test/unit/http_client_test.rb +76 -0
  100. data/test/unit/http_response_test.rb +49 -0
  101. data/test/unit/index_alias_test.rb +275 -0
  102. data/test/unit/index_test.rb +894 -0
  103. data/test/unit/logger_test.rb +125 -0
  104. data/test/unit/model_callbacks_test.rb +116 -0
  105. data/test/unit/model_import_test.rb +71 -0
  106. data/test/unit/model_persistence_test.rb +528 -0
  107. data/test/unit/model_search_test.rb +913 -0
  108. data/test/unit/results_collection_test.rb +281 -0
  109. data/test/unit/results_item_test.rb +162 -0
  110. data/test/unit/rubyext_test.rb +66 -0
  111. data/test/unit/search_facet_test.rb +153 -0
  112. data/test/unit/search_filter_test.rb +42 -0
  113. data/test/unit/search_highlight_test.rb +46 -0
  114. data/test/unit/search_query_test.rb +301 -0
  115. data/test/unit/search_scan_test.rb +113 -0
  116. data/test/unit/search_script_field_test.rb +26 -0
  117. data/test/unit/search_sort_test.rb +50 -0
  118. data/test/unit/search_test.rb +499 -0
  119. data/test/unit/tire_test.rb +126 -0
  120. data/tire.gemspec +90 -0
  121. 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