tire 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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