Skip to content

Commit a41d9a2

Browse files
committed
[MODEL] Fixed the handling of changed attributes in Indexing to work with older Rails versions
This patch builds on work in elastic#738 by @jkeam and elastic#758 by @Geesu to prevent deprecation warnings on Rails 5.1 due to changes in the handling of "changed attributes", originally reported by @davedash in elastic#714. It's primary focus is to restore the compatibility with older Rails versions (so we don't break compatibility without proper version bump and in a single isolated commit), and to make the naming a bit more descriptive. First, the condition has been changed to work with the `changes_to_save` and `changes` methods, as opposed to the original `changed_attributes` / `attributes_in_database` naming. This communicates much more clearly the intent, and the original usage of `changed_attributes` has been misleading. Also, the "internal instance variable" which keeps track of the changes has been renamed to `@__changed_model_attributes`, in order to cleary differentiate it from the `changed_attributes` method name in older ActiveRecord versions. Closes elastic#758
1 parent 900899c commit a41d9a2

File tree

4 files changed

+62
-26
lines changed

4 files changed

+62
-26
lines changed

elasticsearch-model/lib/elasticsearch/model/indexing.rb

+12-6
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,23 @@ module InstanceMethods
307307

308308
def self.included(base)
309309
# Register callback for storing changed attributes for models
310-
# which implement `before_save` and `attributes_in_database` methods
310+
# which implement `before_save` and return changed attributes
311+
# (ie. when `Elasticsearch::Model` is included)
311312
#
312313
# @note This is typically triggered only when the module would be
313314
# included in the model directly, not within the proxy.
314315
#
315316
# @see #update_document
316317
#
317-
base.before_save do |instance|
318-
instance.instance_variable_set(:@__attributes_in_database,
319-
Hash[ instance.changes_to_save.map { |key, value| [key, value.last] } ])
320-
end if base.respond_to?(:before_save) && base.instance_methods.include?(:attributes_in_database)
318+
base.before_save do |i|
319+
if i.class.instance_methods.include?(:changes_to_save) # Rails 5.1
320+
i.instance_variable_set(:@__changed_model_attributes,
321+
Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ])
322+
elsif i.class.instance_methods.include?(:changes)
323+
i.instance_variable_set(:@__changed_model_attributes,
324+
Hash[ i.changes.map { |key, value| [key, value.last] } ])
325+
end
326+
end if base.respond_to?(:before_save)
321327
end
322328

323329
# Serializes the model instance into JSON (by calling `as_indexed_json`),
@@ -391,7 +397,7 @@ def delete_document(options={})
391397
# @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:update
392398
#
393399
def update_document(options={})
394-
if attributes_in_database = self.instance_variable_get(:@__attributes_in_database)
400+
if attributes_in_database = self.instance_variable_get(:@__changed_model_attributes)
395401
attributes = if respond_to?(:as_indexed_json)
396402
self.as_indexed_json.select { |k,v| attributes_in_database.keys.map(&:to_s).include? k.to_s }
397403
else

elasticsearch-model/lib/elasticsearch/model/proxy.rb

+11-5
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,21 @@ def __elasticsearch__ &block
5454
end
5555

5656
# Register a callback for storing changed attributes for models which implement
57-
# `before_save` and `attributes_in_database` methods (when `Elasticsearch::Model` is included)
57+
# `before_save` method and return changed attributes (ie. when `Elasticsearch::Model` is included)
5858
#
5959
# @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
6060
#
6161
before_save do |i|
62-
changed_attr = i.__elasticsearch__.instance_variable_get(:@__attributes_in_database) || {}
63-
i.__elasticsearch__.instance_variable_set(:@__attributes_in_database,
64-
changed_attr.merge(Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ]))
65-
end if respond_to?(:before_save) && instance_methods.include?(:attributes_in_database)
62+
if i.class.instance_methods.include?(:changes_to_save) # Rails 5.1
63+
a = i.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {}
64+
i.__elasticsearch__.instance_variable_set(:@__changed_model_attributes,
65+
a.merge(Hash[ i.changes_to_save.map { |key, value| [key, value.last] } ]))
66+
elsif i.class.instance_methods.include?(:changes)
67+
a = i.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {}
68+
i.__elasticsearch__.instance_variable_set(:@__changed_model_attributes,
69+
a.merge(Hash[ i.changes.map { |key, value| [key, value.last] } ]))
70+
end
71+
end if respond_to?(:before_save)
6672
end
6773
end
6874

elasticsearch-model/test/unit/indexing_test.rb

+37-11
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,6 @@ def self.before_save(&block)
171171
(@callbacks ||= {})[block.hash] = block
172172
end
173173

174-
def attributes_in_database; [:foo]; end
175-
176174
def changes_to_save
177175
{:foo => ['One', 'Two']}
178176
end
@@ -186,8 +184,6 @@ def self.before_save(&block)
186184
(@callbacks ||= {})[block.hash] = block
187185
end
188186

189-
def attributes_in_database; [:foo, :bar]; end
190-
191187
def changes_to_save
192188
{:foo => ['A', 'B'], :bar => ['C', 'D']}
193189
end
@@ -197,15 +193,28 @@ def as_indexed_json(options={})
197193
end
198194
end
199195

196+
class ::DummyIndexingModelWithOldDirty
197+
extend Elasticsearch::Model::Indexing::ClassMethods
198+
include Elasticsearch::Model::Indexing::InstanceMethods
199+
200+
def self.before_save(&block)
201+
(@callbacks ||= {})[block.hash] = block
202+
end
203+
204+
def changes
205+
{:foo => ['One', 'Two']}
206+
end
207+
end
208+
200209
should "register before_save callback when included" do
201210
::DummyIndexingModelWithCallbacks.expects(:before_save).returns(true)
202211
::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods
203212
end
204213

205-
should "set the @__attributes_in_database variable before save" do
214+
should "set the @__changed_model_attributes variable before save" do
206215
instance = ::DummyIndexingModelWithCallbacks.new
207216
instance.expects(:instance_variable_set).with do |name, value|
208-
assert_equal :@__attributes_in_database, name
217+
assert_equal :@__changed_model_attributes, name
209218
assert_equal({foo: 'Two'}, value)
210219
true
211220
end
@@ -217,6 +226,23 @@ def as_indexed_json(options={})
217226
end
218227
end
219228

229+
# https://github.com/elastic/elasticsearch-rails/issues/714
230+
# https://github.com/rails/rails/pull/25337#issuecomment-225166796
231+
should "set the @__changed_model_attributes variable before save for old ActiveModel::Dirty" do
232+
instance = ::DummyIndexingModelWithOldDirty.new
233+
instance.expects(:instance_variable_set).with do |name, value|
234+
assert_equal :@__changed_model_attributes, name
235+
assert_equal({foo: 'Two'}, value)
236+
true
237+
end
238+
239+
::DummyIndexingModelWithOldDirty.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods
240+
241+
::DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks).each do |n,b|
242+
instance.instance_eval(&b)
243+
end
244+
end
245+
220246
should "have the index_document method" do
221247
client = mock('client')
222248
instance = ::DummyIndexingModelWithCallbacks.new
@@ -297,7 +323,7 @@ def as_indexed_json(options={})
297323
instance = ::DummyIndexingModelWithCallbacks.new
298324

299325
# Reset the fake `changes`
300-
instance.instance_variable_set(:@__attributes_in_database, nil)
326+
instance.instance_variable_set(:@__changed_model_attributes, nil)
301327

302328
instance.expects(:index_document)
303329
instance.update_document
@@ -308,7 +334,7 @@ def as_indexed_json(options={})
308334
instance = ::DummyIndexingModelWithCallbacks.new
309335

310336
# Set the fake `changes` hash
311-
instance.instance_variable_set(:@__attributes_in_database, {foo: 'bar'})
337+
instance.instance_variable_set(:@__changed_model_attributes, {foo: 'bar'})
312338

313339
client.expects(:update).with do |payload|
314340
assert_equal 'foo', payload[:index]
@@ -331,7 +357,7 @@ def as_indexed_json(options={})
331357
instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
332358

333359
# Set the fake `changes` hash
334-
instance.instance_variable_set(:@__attributes_in_database, {'foo' => 'B', 'bar' => 'D' })
360+
instance.instance_variable_set(:@__changed_model_attributes, {'foo' => 'B', 'bar' => 'D' })
335361

336362
client.expects(:update).with do |payload|
337363
assert_equal({:foo => 'B'}, payload[:body][:doc])
@@ -350,7 +376,7 @@ def as_indexed_json(options={})
350376
client = mock('client')
351377
instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
352378

353-
instance.instance_variable_set(:@__attributes_in_database, { 'foo' => { 'bar' => 'BAR'} })
379+
instance.instance_variable_set(:@__changed_model_attributes, { 'foo' => { 'bar' => 'BAR'} })
354380
# Overload as_indexed_json
355381
instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' })
356382

@@ -372,7 +398,7 @@ def as_indexed_json(options={})
372398
instance = ::DummyIndexingModelWithCallbacks.new
373399

374400
# Set the fake `changes` hash
375-
instance.instance_variable_set(:@__attributes_in_database, {author: 'john'})
401+
instance.instance_variable_set(:@__changed_model_attributes, {author: 'john'})
376402

377403
client.expects(:update).with do |payload|
378404
assert_equal 'foo', payload[:index]

elasticsearch-model/test/unit/proxy_test.rb

+2-4
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ def self.before_save(&block)
2323
(@callbacks ||= {})[block.hash] = block
2424
end
2525

26-
def attributes_in_database; [:foo]; end
27-
2826
def changes_to_save
2927
{:foo => ['One', 'Two']}
3028
end
@@ -43,10 +41,10 @@ def changes_to_save
4341
DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy
4442
end
4543

46-
should "set the @__attributes_in_database variable before save" do
44+
should "set the @__changed_model_attributes variable before save" do
4745
instance = ::DummyProxyModelWithCallbacks.new
4846
instance.__elasticsearch__.expects(:instance_variable_set).with do |name, value|
49-
assert_equal :@__attributes_in_database, name
47+
assert_equal :@__changed_model_attributes, name
5048
assert_equal({foo: 'Two'}, value)
5149
true
5250
end

0 commit comments

Comments
 (0)