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 +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
|
|