tire 0.2.1 → 0.3.0

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/README.markdown CHANGED
@@ -434,9 +434,53 @@ In this case, just wrap the `mapping` method in a `settings` one, passing it the
434
434
  ```
435
435
 
436
436
  It may well be reasonable to wrap the index creation logic declared with `Tire.index('urls').create`
437
- in a class method of your model, in a module method, etc, so have better control on index creation when bootstrapping your application with Rake tasks or when setting up the test suite.
437
+ in a class method of your model, in a module method, etc, so have better control on index creation when
438
+ bootstrapping your application with Rake tasks or when setting up the test suite.
438
439
  _Tire_ will not hold that against you.
439
440
 
441
+ You may have just stopped wondering: what if I have my own `settings` class method defined?
442
+ Or what if some other gem defines `settings`, or some other _Tire_ method, such as `update_index`?
443
+ Things will break, right? No, they won't.
444
+
445
+ In fact, all this time you've been using only _proxies_ to the real _Tire_ methods, which live in the `tire`
446
+ class and instance methods of your model. Only when not trampling on someone's foot — which is the majority
447
+ of cases —, will _Tire_ bring its methods to the namespace of your class.
448
+
449
+ So, instead of writing `Article.search`, you could write `Article.tire.search`, and instead of
450
+ `@article.update_index` you could write `@article.tire.update_index`, to be on the safe side.
451
+ Let's have a look on an example with the `mapping` method:
452
+
453
+ ```ruby
454
+ class Article < ActiveRecord::Base
455
+ include Tire::Model::Search
456
+ include Tire::Model::Callbacks
457
+
458
+ tire.mapping do
459
+ indexes :id, :type => 'string', :index => :not_analyzed
460
+ # ...
461
+ end
462
+ end
463
+ ```
464
+
465
+ Of course, you could also use the block form:
466
+
467
+ ```ruby
468
+ class Article < ActiveRecord::Base
469
+ include Tire::Model::Search
470
+ include Tire::Model::Callbacks
471
+
472
+ tire do
473
+ mapping do
474
+ indexes :id, :type => 'string', :index => :not_analyzed
475
+ # ...
476
+ end
477
+ end
478
+ end
479
+ ```
480
+
481
+ Internally, _Tire_ uses these proxy methods exclusively. When you run into issues,
482
+ use the proxied method, eg. `Article.tire.mapping`, directly.
483
+
440
484
  When you want a tight grip on how the attributes are added to the index, just
441
485
  implement the `to_indexed_json` method in your model.
442
486
 
@@ -529,7 +573,7 @@ so you can pass all the usual parameters to the `search` method in the controlle
529
573
  OK. Chances are, you have lots of records stored in your database. How will you get them to _ElasticSearch_? Easy:
530
574
 
531
575
  ```ruby
532
- Article.elasticsearch_index.import Article.all
576
+ Article.index.import Article.all
533
577
  ```
534
578
 
535
579
  This way, however, all your records are loaded into memory, serialized into JSON,
data/lib/tire.rb CHANGED
@@ -21,9 +21,9 @@ require 'tire/dsl'
21
21
  require 'tire/model/naming'
22
22
  require 'tire/model/callbacks'
23
23
  require 'tire/model/percolate'
24
- require 'tire/model/search'
25
24
  require 'tire/model/indexing'
26
25
  require 'tire/model/import'
26
+ require 'tire/model/search'
27
27
  require 'tire/model/persistence/finders'
28
28
  require 'tire/model/persistence/attributes'
29
29
  require 'tire/model/persistence/storage'
@@ -1,14 +1,33 @@
1
1
  module Tire
2
2
  module Model
3
3
 
4
+ # Main module containing the infrastructure for automatic updating
5
+ # of the _ElasticSearch_ index on model instance create, update or delete.
6
+ #
7
+ # Include it in your model: `include Tire::Model::Callbacks`
8
+ #
9
+ # The model must respond to `after_save` and `after_destroy` callbacks
10
+ # (ActiveModel and ActiveRecord models do so, by default).
11
+ #
12
+ # By including the model, you will also have the `after_update_elasticsearch_index` and
13
+ # `before_update_elasticsearch_index` callbacks available.
14
+ #
4
15
  module Callbacks
5
16
 
17
+ # A hook triggered by the `include Tire::Model::Callbacks` statement in the model.
18
+ #
6
19
  def self.included(base)
20
+
21
+ # Update index on model instance change or destroy.
22
+ #
7
23
  if base.respond_to?(:after_save) && base.respond_to?(:after_destroy)
8
- base.send :after_save, :update_elastic_search_index
9
- base.send :after_destroy, :update_elastic_search_index
24
+ base.send :after_save, lambda { tire.update_index }
25
+ base.send :after_destroy, lambda { tire.update_index }
10
26
  end
11
27
 
28
+ # Add neccessary infrastructure for the model, when missing in
29
+ # some half-baked ActiveModel implementations.
30
+ #
12
31
  if base.respond_to?(:before_destroy) && !base.instance_methods.map(&:to_sym).include?(:destroyed?)
13
32
  base.class_eval do
14
33
  before_destroy { @destroyed = true }
@@ -16,8 +35,10 @@ module Tire
16
35
  end
17
36
  end
18
37
 
38
+ # Define _Tire's_ callbacks.
39
+ #
19
40
  base.class_eval do
20
- define_model_callbacks(:update_elastic_search_index, :only => [:after, :before])
41
+ define_model_callbacks(:update_elasticsearch_index, :only => [:after, :before])
21
42
  end if base.respond_to?(:define_model_callbacks)
22
43
  end
23
44
 
@@ -7,7 +7,7 @@ module Tire
7
7
 
8
8
  def import options={}, &block
9
9
  method = options.delete(:method) || 'paginate'
10
- self.elasticsearch_index.import self, method, options, &block
10
+ index.import klass, method, options, &block
11
11
  end
12
12
 
13
13
  end
@@ -45,8 +45,8 @@ module Tire
45
45
  end
46
46
 
47
47
  def create_elasticsearch_index
48
- unless elasticsearch_index.exists?
49
- elasticsearch_index.create :mappings => mapping_to_hash, :settings => settings
48
+ unless index.exists?
49
+ index.create :mappings => mapping_to_hash, :settings => settings
50
50
  end
51
51
  end
52
52
 
@@ -6,21 +6,21 @@ module Tire
6
6
  module ClassMethods
7
7
  def index_name name=nil
8
8
  @index_name = name if name
9
- @index_name || model_name.plural
9
+ @index_name || klass.model_name.plural
10
10
  end
11
11
 
12
12
  def document_type
13
- model_name.singular
13
+ klass.model_name.singular
14
14
  end
15
15
  end
16
16
 
17
17
  module InstanceMethods
18
18
  def index_name
19
- self.class.index_name
19
+ instance.class.tire.index_name
20
20
  end
21
21
 
22
22
  def document_type
23
- self.class.document_type
23
+ instance.class.tire.document_type
24
24
  end
25
25
  end
26
26
 
@@ -11,7 +11,7 @@ module Tire
11
11
 
12
12
  def on_percolate(pattern=true,&block)
13
13
  percolate!(pattern)
14
- after_update_elastic_search_index(block)
14
+ klass.after_update_elasticsearch_index(block)
15
15
  end
16
16
 
17
17
  def percolator
@@ -22,7 +22,7 @@ module Tire
22
22
  module InstanceMethods
23
23
 
24
24
  def percolate(&block)
25
- index.percolate self, block
25
+ index.percolate instance, block
26
26
  end
27
27
 
28
28
  def percolate=(pattern)
@@ -30,7 +30,7 @@ module Tire
30
30
  end
31
31
 
32
32
  def percolator
33
- @_percolator || self.class.percolator || nil
33
+ @_percolator || instance.class.tire.percolator || nil
34
34
  end
35
35
  end
36
36
 
@@ -46,7 +46,7 @@ module Tire
46
46
  def save
47
47
  return false unless valid?
48
48
  run_callbacks :save do
49
- # Document#id is set in the +update_elastic_search_index+ method,
49
+ # Document#id is set in the +update_elasticsearch_index+ method,
50
50
  # where we have access to the JSON response
51
51
  end
52
52
  self
@@ -1,66 +1,63 @@
1
1
  module Tire
2
2
  module Model
3
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
+ #
4
20
  module Search
5
21
 
6
- def self.included(base)
7
- base.class_eval do
8
- extend Tire::Model::Naming::ClassMethods
9
- include Tire::Model::Naming::InstanceMethods
10
-
11
- extend Tire::Model::Indexing::ClassMethods
12
- extend Tire::Model::Import::ClassMethods
13
-
14
- extend Tire::Model::Percolate::ClassMethods
15
- include Tire::Model::Percolate::InstanceMethods
16
-
17
- extend ClassMethods
18
- include InstanceMethods
19
-
20
- ['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
21
- # TODO: Find a sane way to add attributes like _score for ActiveRecord -
22
- # `define_attribute_methods [attr]` does not work in AR.
23
- define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
24
- define_method("#{attr}") { @attributes[attr] }
25
- end
26
-
27
- def to_hash
28
- self.serializable_hash
29
- end unless instance_methods.map(&:to_sym).include?(:to_hash)
30
- end
31
-
32
- Results::Item.send :include, Loader
33
- end
34
-
35
22
  module ClassMethods
36
23
 
37
24
  # Returns search results for a given query.
38
25
  #
39
26
  # Query can be passed simply as a String:
40
27
  #
41
- # Article.search 'love'
28
+ # Article.search 'love'
42
29
  #
43
30
  # Any options, such as pagination or sorting, can be passed as a second argument:
44
31
  #
45
- # Article.search 'love', :per_page => 25, :page => 2
46
- # Article.search 'love', :sort => 'title'
32
+ # Article.search 'love', :per_page => 25, :page => 2
33
+ # Article.search 'love', :sort => 'title'
47
34
  #
48
35
  # For more powerful query definition, use the query DSL passed as a block:
49
36
  #
50
- # Article.search do
51
- # query { terms :tags, ['ruby', 'python'] }
52
- # facet 'tags' { terms :tags }
53
- # end
37
+ # Article.search do
38
+ # query { terms :tags, ['ruby', 'python'] }
39
+ # facet 'tags' { terms :tags }
40
+ # end
54
41
  #
55
42
  # You can pass options as the first argument, in this case:
56
43
  #
57
- # Article.search :per_page => 25, :page => 2 do
58
- # query { string 'love' }
59
- # end
44
+ # Article.search :per_page => 25, :page => 2 do
45
+ # query { string 'love' }
46
+ # end
47
+ #
48
+ # This methods returns a Tire::Results::Collection instance, containing instances
49
+ # of Tire::Results::Item, populated by the data available in _ElasticSearch, by default.
50
+ #
51
+ # If you'd like to load the "real" models from the database, you may use the `:load` option:
60
52
  #
53
+ # Article.search 'love', :load => true
54
+ #
55
+ # You can pass options as a Hash to the model's `find` method:
56
+ #
57
+ # Article.search :load => { :include => 'comments' } do ... end
61
58
  #
62
59
  def search(*args, &block)
63
- default_options = {:type => document_type, :index => elasticsearch_index.name}
60
+ default_options = {:type => document_type, :index => index.name}
64
61
 
65
62
  if block_given?
66
63
  options = args.shift || {}
@@ -91,15 +88,11 @@ module Tire
91
88
  s.perform.results
92
89
  end
93
90
 
94
- # Wrapper for the ES index for this class
95
- #
96
- # TODO: Implement some "forwardable" object named +tire+ for Tire mixins,
97
- # and proxy everything via this object. If we're not stepping on
98
- # other libs toes, extend/include also to the top level.
91
+ # Returns a Tire::Index instance for this model.
99
92
  #
100
- # The original culprit is Mongoid here, see https://github.com/karmi/tire/issues/7
93
+ # Example usage: `Article.index.refresh`.
101
94
  #
102
- def elasticsearch_index
95
+ def index
103
96
  @index = Index.new(index_name)
104
97
  end
105
98
 
@@ -107,33 +100,54 @@ module Tire
107
100
 
108
101
  module InstanceMethods
109
102
 
103
+ # Returns a Tire::Index instance for this instance of the model.
104
+ #
105
+ # Example usage: `@article.index.refresh`.
106
+ #
110
107
  def index
111
- self.class.elasticsearch_index
108
+ instance.class.tire.index
112
109
  end
113
110
 
114
- def update_elastic_search_index
115
- _run_update_elastic_search_index_callbacks do
116
- if destroyed?
117
- index.remove self
111
+ # Updates the index in _ElasticSearch_.
112
+ #
113
+ # On model instance create or update, it will store its serialized representation in the index.
114
+ #
115
+ # On model destroy, it will remove the corresponding document from the index.
116
+ #
117
+ # It will also call the `<after|before>_update_elasticsearch_index` callbacks,
118
+ # if defined by the model.
119
+ #
120
+ def update_index
121
+ instance.send :_run_update_elasticsearch_index_callbacks do
122
+ if instance.destroyed?
123
+ index.remove instance
118
124
  else
119
- response = index.store( self, {:percolate => self.percolator} )
120
- self.id ||= response['_id'] if self.respond_to?(:id=)
121
- self._index = response['_index']
122
- self._type = response['_type']
123
- self._version = response['_version']
124
- self.matches = response['matches']
125
+ response = index.store( instance, {:percolate => percolator} )
126
+ instance.id ||= response['_id'] if instance.respond_to?(:id=)
127
+ instance._index = response['_index'] if instance.respond_to?(:_index=)
128
+ instance._type = response['_type'] if instance.respond_to?(:_type=)
129
+ instance._version = response['_version'] if instance.respond_to?(:_version=)
130
+ instance.matches = response['matches'] if instance.respond_to?(:matches=)
125
131
  self
126
132
  end
127
133
  end
128
134
  end
129
- alias :update_elasticsearch_index :update_elastic_search_index
135
+ alias :update_elasticsearch_index :update_index
136
+ alias :update_elastic_search_index :update_index
130
137
 
138
+ # The default JSON serialization of the model, based on its `#to_hash` representation.
139
+ #
140
+ # If you don't define any mapping, the model is serialized as-is.
141
+ #
142
+ # If you do define the mapping for _ElasticSearch_, only attributes
143
+ # declared in the mapping are serialized.
144
+ #
131
145
  def to_indexed_json
132
- if self.class.mapping.empty?
133
- to_hash.to_json
146
+ if instance.class.tire.mapping.empty?
147
+ instance.to_hash.to_json
134
148
  else
135
- to_hash.
136
- reject { |key, value| ! self.class.mapping.keys.map(&:to_s).include?(key.to_s) }.
149
+ instance.to_hash.
150
+ reject { |key, value| ! instance.class.tire.mapping.keys.map(&:to_s).include?(key.to_s) }.
137
151
  to_json
138
152
  end
139
153
  end
@@ -142,7 +156,10 @@ module Tire
142
156
 
143
157
  module Loader
144
158
 
145
- # Load the "real" model from the database via the corresponding model's `find` method
159
+ # Load the "real" model from the database via the corresponding model's `find` method.
160
+ #
161
+ # Notice that there's an option to eagerly load models with the `:load` option
162
+ # for the search method.
146
163
  #
147
164
  def load(options=nil)
148
165
  options ? self.class.find(self.id, options) : self.class.find(self.id)
@@ -150,7 +167,106 @@ module Tire
150
167
 
151
168
  end
152
169
 
153
- extend ClassMethods
170
+ # An object containing _Tire's_ model class methods, accessed as `Article.tire`.
171
+ #
172
+ class ClassMethodsProxy
173
+ include Tire::Model::Naming::ClassMethods
174
+ include Tire::Model::Import::ClassMethods
175
+ include Tire::Model::Indexing::ClassMethods
176
+ include Tire::Model::Percolate::ClassMethods
177
+ include ClassMethods
178
+
179
+ INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
180
+
181
+ attr_reader :klass
182
+ def initialize(klass)
183
+ @klass = klass
184
+ end
185
+
186
+ end
187
+
188
+ # An object containing _Tire's_ model instance methods, accessed as `@article.tire`.
189
+ #
190
+ class InstanceMethodsProxy
191
+ include Tire::Model::Naming::InstanceMethods
192
+ include Tire::Model::Percolate::InstanceMethods
193
+ include InstanceMethods
194
+
195
+ ['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
196
+ # TODO: Find a sane way to add attributes like _score for ActiveRecord -
197
+ # `define_attribute_methods [attr]` does not work in AR.
198
+ define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
199
+ define_method("#{attr}") { @attributes[attr] }
200
+ end
201
+
202
+ INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
203
+
204
+ attr_reader :instance
205
+ def initialize(instance)
206
+ @instance = instance
207
+ end
208
+ end
209
+
210
+ # A hook triggered by the `include Tire::Model::Search` statement in the model.
211
+ #
212
+ def self.included(base)
213
+ base.class_eval do
214
+
215
+ # Returns proxy to the _Tire's_ class methods.
216
+ #
217
+ def self.tire &block
218
+ @__tire__ ||= ClassMethodsProxy.new(self)
219
+
220
+ @__tire__.instance_eval(&block) if block_given?
221
+ @__tire__
222
+ end
223
+
224
+ # Returns proxy to the _Tire's_ instance methods.
225
+ #
226
+ def tire &block
227
+ @__tire__ ||= InstanceMethodsProxy.new(self)
228
+
229
+ @__tire__.instance_eval(&block) if block_given?
230
+ @__tire__
231
+ end
232
+
233
+ # Serialize the model as a Hash.
234
+ #
235
+ # Uses `serializable_hash` representation of the model,
236
+ # unless implemented in the model already.
237
+ #
238
+ def to_hash
239
+ self.serializable_hash
240
+ end unless instance_methods.map(&:to_sym).include?(:to_hash)
241
+
242
+ end
243
+
244
+ # Alias _Tire's_ class methods in the top-level namespace of the model,
245
+ # unless there's a conflict with existing method.
246
+ #
247
+ ClassMethodsProxy::INTERFACE.each do |method|
248
+ base.class_eval <<-"end;", __FILE__, __LINE__ unless base.public_methods.map(&:to_sym).include?(method.to_sym)
249
+ def self.#{method}(*args, &block) # def search(*args, &block)
250
+ tire.__send__(#{method.inspect}, *args, &block) # tire.__send__(:search, *args, &block)
251
+ end # end
252
+ end;
253
+ end
254
+
255
+ # Alias _Tire's_ instance methods in the top-level namespace of the model,
256
+ # unless there's a conflict with existing method
257
+ InstanceMethodsProxy::INTERFACE.each do |method|
258
+ base.class_eval <<-"end;", __FILE__, __LINE__ unless base.instance_methods.map(&:to_sym).include?(method.to_sym)
259
+ def #{method}(*args, &block) # def to_indexed_json(*args, &block)
260
+ tire.__send__(#{method.inspect}, *args, &block) # tire.__send__(:to_indexed_json, *args, &block)
261
+ end # end
262
+ end;
263
+ end
264
+
265
+ # Include the `load` functionality in Results::Item
266
+ #
267
+ Results::Item.send :include, Loader
268
+ end
269
+
154
270
  end
155
271
 
156
272
  end
data/lib/tire/tasks.rb CHANGED
@@ -44,7 +44,7 @@ namespace :tire do
44
44
  klass = eval(ENV['CLASS'].to_s)
45
45
  params = eval(ENV['PARAMS'].to_s) || {}
46
46
 
47
- index = Tire::Index.new( ENV['INDEX'] || klass.elasticsearch_index.name )
47
+ index = Tire::Index.new( ENV['INDEX'] || klass.tire.index.name )
48
48
 
49
49
  if ENV['FORCE']
50
50
  puts "[IMPORT] Deleting index '#{index.name}'"
@@ -52,10 +52,10 @@ namespace :tire do
52
52
  end
53
53
 
54
54
  unless index.exists?
55
- mapping = defined?(Yajl) ? Yajl::Encoder.encode(klass.mapping_to_hash, :pretty => true) :
56
- MultiJson.encode(klass.mapping_to_hash)
55
+ mapping = defined?(Yajl) ? Yajl::Encoder.encode(klass.tire.mapping_to_hash, :pretty => true) :
56
+ MultiJson.encode(klass.tire.mapping_to_hash)
57
57
  puts "[IMPORT] Creating index '#{index.name}' with mapping:", mapping
58
- index.create :mappings => klass.mapping_to_hash
58
+ index.create :mappings => klass.tire.mapping_to_hash
59
59
  end
60
60
 
61
61
  STDOUT.sync = true
data/lib/tire/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Tire
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
@@ -17,5 +17,12 @@ module Tire
17
17
  # Added the 'settings' method for models to define index settings
18
18
  # Fixed errors when importing data with will_paginate vs Kaminari (MongoDB)
19
19
  # Added support for histogram facets [Paco Guzman]
20
+
21
+ 0.2.1
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.
20
27
  END
21
28
  end
@@ -28,7 +28,7 @@ module Tire
28
28
  assert_equal 'czech', SupermodelArticle.mapping[:title][:analyzer]
29
29
  assert_equal 15, SupermodelArticle.mapping[:title][:boost]
30
30
 
31
- assert_equal 'czech', SupermodelArticle.elasticsearch_index.mapping['supermodel_article']['properties']['title']['analyzer']
31
+ assert_equal 'czech', SupermodelArticle.index.mapping['supermodel_article']['properties']['title']['analyzer']
32
32
  end
33
33
 
34
34
  should "save document into index on save and find it with score" do
@@ -75,7 +75,7 @@ module Tire
75
75
  SupermodelArticle.create! :title => 'foo'
76
76
  SupermodelArticle.create! :id => 'abc123', :title => 'bar'
77
77
 
78
- SupermodelArticle.elasticsearch_index.refresh
78
+ SupermodelArticle.index.refresh
79
79
  results = SupermodelArticle.search 'foo OR bar^100'
80
80
 
81
81
  assert_equal 2, results.count
@@ -26,6 +26,9 @@ module Tire
26
26
  t.string :period
27
27
  t.references :article
28
28
  end
29
+ create_table :active_record_class_with_tire_methods do |t|
30
+ t.string :title
31
+ end
29
32
  end
30
33
  end
31
34
 
@@ -41,7 +44,7 @@ module Tire
41
44
  assert_equal 'snowball', ActiveRecordArticle.mapping[:title][:analyzer]
42
45
  assert_equal 10, ActiveRecordArticle.mapping[:title][:boost]
43
46
 
44
- assert_equal 'snowball', ActiveRecordArticle.elasticsearch_index.mapping['active_record_article']['properties']['title']['analyzer']
47
+ assert_equal 'snowball', ActiveRecordArticle.index.mapping['active_record_article']['properties']['title']['analyzer']
45
48
  end
46
49
 
47
50
  should "save document into index on save and find it" do
@@ -68,7 +71,7 @@ module Tire
68
71
  setup do
69
72
  ActiveRecordArticle.destroy_all
70
73
  5.times { |n| ActiveRecordArticle.create! :title => "Test #{n+1}" }
71
- ActiveRecordArticle.elasticsearch_index.refresh
74
+ ActiveRecordArticle.index.refresh
72
75
  end
73
76
 
74
77
  should "load records on query search" do
@@ -103,16 +106,15 @@ module Tire
103
106
  end
104
107
 
105
108
  should "remove document from index on destroy" do
106
- a = ActiveRecordArticle.new :title => 'Test'
109
+ a = ActiveRecordArticle.new :title => 'Test remove...'
107
110
  a.save!
108
111
  assert_equal 1, ActiveRecordArticle.count
109
112
 
110
113
  a.destroy
111
- assert_equal 0, SupermodelArticle.all.size
114
+ assert_equal 0, ActiveRecordArticle.all.size
112
115
 
113
116
  a.index.refresh
114
117
  results = ActiveRecordArticle.search 'test'
115
-
116
118
  assert_equal 0, results.count
117
119
  end
118
120
 
@@ -120,7 +122,7 @@ module Tire
120
122
  ActiveRecordArticle.create! :title => 'foo'
121
123
  ActiveRecordArticle.create! :title => 'bar'
122
124
 
123
- ActiveRecordArticle.elasticsearch_index.refresh
125
+ ActiveRecordArticle.index.refresh
124
126
  results = ActiveRecordArticle.search 'foo OR bar^100'
125
127
  assert_equal 2, results.count
126
128
 
@@ -130,7 +132,7 @@ module Tire
130
132
  context "with pagination" do
131
133
  setup do
132
134
  1.upto(9) { |number| ActiveRecordArticle.create :title => "Test#{number}" }
133
- ActiveRecordArticle.elasticsearch_index.refresh
135
+ ActiveRecordArticle.index.refresh
134
136
  end
135
137
 
136
138
  context "and parameter searches" do
@@ -231,6 +233,34 @@ module Tire
231
233
 
232
234
  end
233
235
 
236
+ context "with proxy" do
237
+
238
+ should "allow access to Tire instance methods" do
239
+ a = ActiveRecordClassWithTireMethods.create :title => 'One'
240
+ assert_equal "THIS IS MY INDEX!", a.index
241
+ assert_instance_of Tire::Index, a.tire.index
242
+ assert a.tire.index.exists?, "Index should exist"
243
+ end
244
+
245
+ should "allow access to Tire class methods" do
246
+ class ::ActiveRecordClassWithTireMethods < ActiveRecord::Base
247
+ def self.search(*)
248
+ "THIS IS MY SEARCH!"
249
+ end
250
+ end
251
+
252
+ ActiveRecordClassWithTireMethods.create :title => 'One'
253
+ ActiveRecordClassWithTireMethods.tire.index.refresh
254
+
255
+ assert_equal "THIS IS MY SEARCH!", ActiveRecordClassWithTireMethods.search
256
+
257
+ results = ActiveRecordClassWithTireMethods.tire.search 'one'
258
+
259
+ assert_equal 'One', results.first.title
260
+ end
261
+
262
+ end
263
+
234
264
  context "within Rails" do
235
265
 
236
266
  setup do
@@ -33,7 +33,7 @@ module Tire
33
33
 
34
34
  setup do
35
35
  1.upto(9) { |number| PersistentArticle.create :title => "Test#{number}" }
36
- PersistentArticle.elasticsearch_index.refresh
36
+ PersistentArticle.index.refresh
37
37
  end
38
38
 
39
39
  should "find first page with five results" do
@@ -5,19 +5,33 @@ class ActiveRecordArticle < ActiveRecord::Base
5
5
  has_many :comments, :class_name => "ActiveRecordComment", :foreign_key => "article_id"
6
6
  has_many :stats, :class_name => "ActiveRecordStat", :foreign_key => "article_id"
7
7
 
8
+ # def index
9
+ # "KEEP OFF MY INDEX!!!"
10
+ # end
11
+ #
12
+ # def self.settings
13
+ # "KEEP OFF MY SETTINGS!!!"
14
+ # end
15
+
8
16
  include Tire::Model::Search
9
17
  include Tire::Model::Callbacks
10
18
 
11
- mapping do
12
- indexes :title, :type => 'string', :boost => 10, :analyzer => 'snowball'
13
- indexes :created_at, :type => 'date'
19
+ tire do
20
+ mapping do
21
+ indexes :title, :type => 'string', :boost => 10, :analyzer => 'snowball'
22
+ indexes :created_at, :type => 'date'
14
23
 
15
- indexes :comments do
16
- indexes :author
17
- indexes :body
24
+ indexes :comments do
25
+ indexes :author
26
+ indexes :body
27
+ end
18
28
  end
19
29
  end
20
30
 
31
+ # tire.mapping do
32
+ # indexes :title, :type => 'string', :boost => 10, :analyzer => 'snowball'
33
+ # end
34
+
21
35
  def to_indexed_json
22
36
  {
23
37
  :title => title,
@@ -47,3 +61,23 @@ end
47
61
  class ActiveRecordStat < ActiveRecord::Base
48
62
  belongs_to :article, :class_name => "ActiveRecordArticle", :foreign_key => "article_id"
49
63
  end
64
+
65
+ class ActiveRecordClassWithTireMethods < ActiveRecord::Base
66
+
67
+ def self.mapping
68
+ "THIS IS MY MAPPING!"
69
+ end
70
+
71
+ def index
72
+ "THIS IS MY INDEX!"
73
+ end
74
+
75
+ include Tire::Model::Search
76
+ include Tire::Model::Callbacks
77
+
78
+ tire do
79
+ mapping do
80
+ indexes :title, :type => 'string', :analyzer => 'snowball'
81
+ end
82
+ end
83
+ end
data/test/test_helper.rb CHANGED
@@ -51,6 +51,8 @@ module Test::Integration
51
51
  fixtures_path.join('articles').join(f).read
52
52
  end
53
53
  ::RestClient.post "#{URL}/articles-test/_refresh", ''
54
+
55
+ Dir[File.dirname(__FILE__) + '/models/**/*.rb'].each { |m| load m }
54
56
  end
55
57
 
56
58
  def teardown
@@ -310,7 +310,7 @@ module Tire
310
310
  one = ActiveModelArticle.new 'title' => 'One'; one.id = '1'
311
311
  two = ActiveModelArticle.new 'title' => 'Two'; two.id = '2'
312
312
 
313
- ActiveModelArticle.elasticsearch_index.bulk_store [ one, two ]
313
+ ActiveModelArticle.index.bulk_store [ one, two ]
314
314
 
315
315
  end
316
316
 
@@ -327,7 +327,7 @@ module Tire
327
327
  STDERR.expects(:puts).once
328
328
 
329
329
  documents = [ { :title => 'Bogus' }, { :title => 'Real', :id => 1 } ]
330
- ActiveModelArticle.elasticsearch_index.bulk_store documents
330
+ ActiveModelArticle.index.bulk_store documents
331
331
  end
332
332
 
333
333
  end
@@ -1,6 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class ModelOne
4
+ extend ActiveModel::Naming
4
5
  include Tire::Model::Search
5
6
  include Tire::Model::Callbacks
6
7
 
@@ -9,6 +10,7 @@ class ModelOne
9
10
  end
10
11
 
11
12
  class ModelTwo
13
+ extend ActiveModel::Naming
12
14
  extend ActiveModel::Callbacks
13
15
  define_model_callbacks :save, :destroy
14
16
 
@@ -27,6 +29,7 @@ class ModelTwo
27
29
  end
28
30
 
29
31
  class ModelThree
32
+ extend ActiveModel::Naming
30
33
  extend ActiveModel::Callbacks
31
34
  define_model_callbacks :save, :destroy
32
35
 
@@ -50,10 +53,11 @@ module Tire
50
53
  context "Model without ActiveModel callbacks" do
51
54
 
52
55
  should "not execute any callbacks" do
53
- ModelOne.any_instance.expects(:update_elastic_search_index).never
56
+ m = ModelOne.new
57
+ m.tire.expects(:update_index).never
54
58
 
55
- ModelOne.new.save
56
- ModelOne.new.destroy
59
+ m.save
60
+ m.destroy
57
61
  end
58
62
 
59
63
  end
@@ -61,10 +65,11 @@ module Tire
61
65
  context "Model with ActiveModel callbacks and implemented destroyed? method" do
62
66
 
63
67
  should "execute the callbacks" do
64
- ModelTwo.any_instance.expects(:update_elastic_search_index).twice
68
+ m = ModelTwo.new
69
+ m.tire.expects(:update_index).twice
65
70
 
66
- ModelTwo.new.save
67
- ModelTwo.new.destroy
71
+ m.save
72
+ m.destroy
68
73
  end
69
74
 
70
75
  end
@@ -76,10 +81,11 @@ module Tire
76
81
  end
77
82
 
78
83
  should "execute the callbacks" do
79
- ModelThree.any_instance.expects(:update_elastic_search_index).twice
84
+ m = ModelThree.new
85
+ m.tire.expects(:update_index).twice
80
86
 
81
- ModelThree.new.save
82
- ModelThree.new.destroy
87
+ m.save
88
+ m.destroy
83
89
  end
84
90
 
85
91
  end
@@ -340,7 +340,6 @@ module Tire
340
340
  Configuration.client.expects(:post).
341
341
  with do |url, payload|
342
342
  doc = MultiJson.decode(payload)
343
- p doc
344
343
  url == "#{Configuration.url}/persistent_articles/persistent_article/1" &&
345
344
  doc['id'] == '1' &&
346
345
  doc['title'] == 'Updated'
@@ -28,28 +28,12 @@ module Tire
28
28
  end
29
29
 
30
30
  should "have the search method" do
31
- assert_respond_to Model::Search, :search
32
31
  assert_respond_to ActiveModelArticle, :search
33
32
  end
34
33
 
35
- should "have the `update_elastic_search_index` callback methods defined" do
36
- assert_respond_to ::ModelWithIndexCallbacks, :before_update_elastic_search_index
37
- assert_respond_to ::ModelWithIndexCallbacks, :after_update_elastic_search_index
38
- end
39
-
40
- should_eventually "contain all Tire class/instance methods in a proxy object" do
41
- end
42
-
43
- should_eventually "include Tire class methods in class top-level namespace when they do not exist" do
44
- end
45
-
46
- should_eventually "include Tire instance methods in instance top-level namespace when they do not exist" do
47
- end
48
-
49
- should_eventually "NOT overload existing top-level class methods" do
50
- end
51
-
52
- should_eventually "NOT overload existing top-level instance methods" do
34
+ should "have the callback methods for update index defined" do
35
+ assert_respond_to ::ModelWithIndexCallbacks, :before_update_elasticsearch_index
36
+ assert_respond_to ::ModelWithIndexCallbacks, :after_update_elasticsearch_index
53
37
  end
54
38
 
55
39
  should "limit searching in index for documents matching the model 'document_type'" do
@@ -111,7 +95,7 @@ module Tire
111
95
  should "allow to refresh index" do
112
96
  Index.any_instance.expects(:refresh)
113
97
 
114
- ActiveModelArticle.elasticsearch_index.refresh
98
+ ActiveModelArticle.index.refresh
115
99
  end
116
100
 
117
101
  should "wrap results in instances of the wrapper class" do
@@ -225,7 +209,7 @@ module Tire
225
209
 
226
210
  should "not set callback when hooks are missing" do
227
211
  @model = ActiveModelArticle.new
228
- @model.expects(:update_elastic_search_index).never
212
+ @model.expects(:update_elasticsearch_index).never
229
213
 
230
214
  @model.save
231
215
  end
@@ -240,26 +224,26 @@ module Tire
240
224
 
241
225
  should "fire :after_save callbacks" do
242
226
  @model = ActiveModelArticleWithCallbacks.new
243
- @model.expects(:update_elastic_search_index)
227
+ @model.tire.expects(:update_index)
244
228
 
245
229
  @model.save
246
230
  end
247
231
 
248
232
  should "fire :after_destroy callbacks" do
249
233
  @model = ActiveModelArticleWithCallbacks.new
250
- @model.expects(:update_elastic_search_index)
234
+ @model.tire.expects(:update_index)
251
235
 
252
236
  @model.destroy
253
237
  end
254
238
 
255
- should "store the record in index on :update_elastic_search_index when saved" do
239
+ should "store the record in index on :update_elasticsearch_index when saved" do
256
240
  @model = ActiveModelArticleWithCallbacks.new
257
241
  Tire::Index.any_instance.expects(:store).returns({})
258
242
 
259
243
  @model.save
260
244
  end
261
245
 
262
- should "remove the record from index on :update_elastic_search_index when destroyed" do
246
+ should "remove the record from index on :update_elasticsearch_index when destroyed" do
263
247
  @model = ActiveModelArticleWithCallbacks.new
264
248
  i = mock('index') { expects(:remove) }
265
249
  Tire::Index.expects(:new).with('active_model_article_with_callbacks').returns(i)
@@ -373,7 +357,7 @@ module Tire
373
357
  context "with index update callbacks" do
374
358
  setup do
375
359
  class ::ModelWithIndexCallbacks
376
- _update_elastic_search_index_callbacks.clear
360
+ _update_elasticsearch_index_callbacks.clear
377
361
  def notify; end
378
362
  end
379
363
 
@@ -385,18 +369,18 @@ module Tire
385
369
 
386
370
  should "run the callback defined as block" do
387
371
  class ::ModelWithIndexCallbacks
388
- after_update_elastic_search_index { self.go! }
372
+ after_update_elasticsearch_index { self.go! }
389
373
  end
390
374
 
391
375
  @model = ::ModelWithIndexCallbacks.new
392
376
  @model.expects(:go!)
393
377
 
394
- @model.update_elastic_search_index
378
+ @model.update_elasticsearch_index
395
379
  end
396
380
 
397
381
  should "run the callback defined as symbol" do
398
382
  class ::ModelWithIndexCallbacks
399
- after_update_elastic_search_index :notify
383
+ after_update_elasticsearch_index :notify
400
384
 
401
385
  def notify; self.go!; end
402
386
  end
@@ -404,12 +388,12 @@ module Tire
404
388
  @model = ::ModelWithIndexCallbacks.new
405
389
  @model.expects(:go!)
406
390
 
407
- @model.update_elastic_search_index
391
+ @model.update_elasticsearch_index
408
392
  end
409
393
 
410
394
  should "set the 'matches' property from percolated response" do
411
395
  @model = ::ModelWithIndexCallbacks.new
412
- @model.update_elastic_search_index
396
+ @model.update_elasticsearch_index
413
397
 
414
398
  assert_equal ['foo'], @model.matches
415
399
  end
@@ -533,7 +517,7 @@ module Tire
533
517
  options[:percolate] == true
534
518
  end.returns(MultiJson.decode('{"ok":true,"_id":"test","matches":["alerts"]}'))
535
519
 
536
- @article.update_elastic_search_index
520
+ @article.update_elasticsearch_index
537
521
  end
538
522
 
539
523
  should "not percolate document on index update when not set for percolation" do
@@ -542,7 +526,7 @@ module Tire
542
526
  options[:percolate] == nil
543
527
  end.returns(MultiJson.decode('{"ok":true,"_id":"test"}'))
544
528
 
545
- @article.update_elastic_search_index
529
+ @article.update_elasticsearch_index
546
530
  end
547
531
 
548
532
  should "set the default percolator pattern" do
@@ -572,7 +556,7 @@ module Tire
572
556
  end.returns(MultiJson.decode('{"ok":true,"_id":"test","matches":["alerts"]}'))
573
557
 
574
558
  percolated = ActiveModelArticleWithPercolation.new :title => 'Percolate me!'
575
- percolated.update_elastic_search_index
559
+ percolated.update_elasticsearch_index
576
560
  end
577
561
 
578
562
  should "execute the 'on_percolate' callback" do
@@ -589,7 +573,7 @@ module Tire
589
573
  end.
590
574
  returns(MultiJson.decode('{"ok":true,"_id":"test","matches":["alerts"]}'))
591
575
 
592
- percolated.update_elastic_search_index
576
+ percolated.update_elasticsearch_index
593
577
 
594
578
  assert_equal ['alerts'], $test__matches
595
579
  end
@@ -608,13 +592,39 @@ module Tire
608
592
  end.
609
593
  returns(MultiJson.decode('{"ok":true,"_id":"test","matches":["alerts"]}'))
610
594
 
611
- percolated.update_elastic_search_index
595
+ percolated.update_elasticsearch_index
612
596
 
613
597
  assert_equal ['alerts'], $test__matches
614
598
  end
615
599
 
616
600
  end
617
601
 
602
+ context "proxy" do
603
+
604
+ should "have the proxy to class methods" do
605
+ assert_respond_to ActiveModelArticle, :tire
606
+ assert_instance_of Tire::Model::Search::ClassMethodsProxy, ActiveModelArticle.tire
607
+ end
608
+
609
+ should "have the proxy to instance methods" do
610
+ assert_respond_to ActiveModelArticle.new, :tire
611
+ assert_instance_of Tire::Model::Search::InstanceMethodsProxy, ActiveModelArticle.new.tire
612
+ end
613
+
614
+ should "NOT overload existing top-level class methods" do
615
+ assert_equal "THIS IS MY MAPPING!", ActiveRecordClassWithTireMethods.mapping
616
+ assert_equal 'snowball', ActiveRecordClassWithTireMethods.tire.mapping[:title][:analyzer]
617
+ end
618
+
619
+ should "NOT overload existing top-level instance methods" do
620
+ ActiveRecordClassWithTireMethods.stubs(:columns).returns([])
621
+ assert_equal "THIS IS MY INDEX!", ActiveRecordClassWithTireMethods.new.index
622
+ assert_equal 'active_record_class_with_tire_methods',
623
+ ActiveRecordClassWithTireMethods.new.tire.index.name
624
+ end
625
+
626
+ end
627
+
618
628
  end
619
629
 
620
630
  context "Results::Item" do
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 1
9
- version: 0.2.1
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Karel Minarik
@@ -331,7 +331,14 @@ post_install_message: |
331
331
  # Fixed errors when importing data with will_paginate vs Kaminari (MongoDB)
332
332
  # Added support for histogram facets [Paco Guzman]
333
333
 
334
- See the full changelog at <http://github.com/karmi/tire/commits/v0.2.1>.
334
+ 0.2.1
335
+ ---------------------------------------------------------
336
+ # Isolated Tire ActiveModel integration into `tire` class and instance method.
337
+
338
+ When there's no conflict with existing methods, Tire methods are added
339
+ to the class namespace, as well, so the change is 100% backwards-compatible.
340
+
341
+ See the full changelog at <http://github.com/karmi/tire/commits/v0.3.0>.
335
342
 
336
343
  --------------------------------------------------------------------------------
337
344