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 +46 -2
- data/lib/tire.rb +1 -1
- data/lib/tire/model/callbacks.rb +24 -3
- data/lib/tire/model/import.rb +1 -1
- data/lib/tire/model/indexing.rb +2 -2
- data/lib/tire/model/naming.rb +4 -4
- data/lib/tire/model/percolate.rb +3 -3
- data/lib/tire/model/persistence/storage.rb +1 -1
- data/lib/tire/model/search.rb +181 -65
- data/lib/tire/tasks.rb +4 -4
- data/lib/tire/version.rb +8 -1
- data/test/integration/active_model_searchable_test.rb +2 -2
- data/test/integration/active_record_searchable_test.rb +37 -7
- data/test/integration/persistent_model_test.rb +1 -1
- data/test/models/active_record_models.rb +40 -6
- data/test/test_helper.rb +2 -0
- data/test/unit/index_test.rb +2 -2
- data/test/unit/model_callbacks_test.rb +15 -9
- data/test/unit/model_persistence_test.rb +0 -1
- data/test/unit/model_search_test.rb +46 -36
- metadata +11 -4
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
|
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.
|
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'
|
data/lib/tire/model/callbacks.rb
CHANGED
@@ -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,
|
9
|
-
base.send :after_destroy,
|
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(:
|
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
|
|
data/lib/tire/model/import.rb
CHANGED
data/lib/tire/model/indexing.rb
CHANGED
@@ -45,8 +45,8 @@ module Tire
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def create_elasticsearch_index
|
48
|
-
unless
|
49
|
-
|
48
|
+
unless index.exists?
|
49
|
+
index.create :mappings => mapping_to_hash, :settings => settings
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
data/lib/tire/model/naming.rb
CHANGED
@@ -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
|
-
|
19
|
+
instance.class.tire.index_name
|
20
20
|
end
|
21
21
|
|
22
22
|
def document_type
|
23
|
-
|
23
|
+
instance.class.tire.document_type
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
data/lib/tire/model/percolate.rb
CHANGED
@@ -11,7 +11,7 @@ module Tire
|
|
11
11
|
|
12
12
|
def on_percolate(pattern=true,&block)
|
13
13
|
percolate!(pattern)
|
14
|
-
|
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
|
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 ||
|
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 +
|
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
|
data/lib/tire/model/search.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
#
|
46
|
-
#
|
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
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
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
|
-
#
|
58
|
-
#
|
59
|
-
#
|
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 =>
|
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
|
-
#
|
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
|
-
#
|
93
|
+
# Example usage: `Article.index.refresh`.
|
101
94
|
#
|
102
|
-
def
|
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
|
-
|
108
|
+
instance.class.tire.index
|
112
109
|
end
|
113
110
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
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
|
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| !
|
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
|
-
|
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.
|
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
|
+
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.
|
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.
|
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.
|
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.
|
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,
|
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.
|
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.
|
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
|
@@ -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
|
-
|
12
|
-
|
13
|
-
|
19
|
+
tire do
|
20
|
+
mapping do
|
21
|
+
indexes :title, :type => 'string', :boost => 10, :analyzer => 'snowball'
|
22
|
+
indexes :created_at, :type => 'date'
|
14
23
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
data/test/unit/index_test.rb
CHANGED
@@ -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.
|
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.
|
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.
|
56
|
+
m = ModelOne.new
|
57
|
+
m.tire.expects(:update_index).never
|
54
58
|
|
55
|
-
|
56
|
-
|
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.
|
68
|
+
m = ModelTwo.new
|
69
|
+
m.tire.expects(:update_index).twice
|
65
70
|
|
66
|
-
|
67
|
-
|
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.
|
84
|
+
m = ModelThree.new
|
85
|
+
m.tire.expects(:update_index).twice
|
80
86
|
|
81
|
-
|
82
|
-
|
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
|
36
|
-
assert_respond_to ::ModelWithIndexCallbacks, :
|
37
|
-
assert_respond_to ::ModelWithIndexCallbacks, :
|
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.
|
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(:
|
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(:
|
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(:
|
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 :
|
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 :
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
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
|
-
|
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
|
|