views:

146

answers:

1

Grettings!

In an app that was working flawlessly in Rails 2.3.8 i have the following class method:

def self.encode(*attr_names)
  encoder = Encoder.new(attr_names)
  before_save encoder
  after_save encoder            
  after_find encoder
  define_method(:after_find) { } # defining here, since there's only alias in the Encoder class itself            
end

This method references the Encoder class. Here is it:

class Encoder
  include Encodings 

  def initialize(attrs_to_manage) # We're passed a list of attributes that should be stored encoded in the database
    @attrs_to_manage = attrs_to_manage
  end

  def before_save(model) # Before saving or updating, encode the attributes to their original encoding
    @attrs_to_manage.each do |field|
      model[field] = to_orig_encod(model[field])
    end
  end

  def after_save(model) # After saving, encode them back to utf8
    @attrs_to_manage.each do |field|
      model[field] = to_utf8(model[field])
    end
  end

  alias_method :after_find, :after_save # Do the same after finding an existing record
end

Before the upgrade to rails3 all the callbacks (before_save, after_save, after_find) worked fine. After the upgrade *before_save* and *after_save* still work, but *after_find* does not and I get the following deprecation warning in my log:

DEPRECATION WARNING: Base#after_find has been deprecated, please use Base.after_find :method instead

I'm uncertain how to change my code in order to re-enable the functionality of the after_find callback. I tried a few simple alternations with no success and the rails API documentation on this callback is very limited and without examples of implementation.

Any help appreciated, thanks in advance!

Edit:

Here's the solution:

Okay, so it seems that the problem was more subtle than it initially appeared. After additional testing I found out that in fact, as Jeppe pointed out, the after_find callback is working regardless of the deprecation warning, the "to_utf8" method was in fact suceesfully called and executed on the model attributes. The reason the result didn't match the expectations, was the "to_utf8" method itself. What it does is use the ruby module Iconv to convert strings from non-utf8 encoding like cp1251 for example to utf. This was done for the attributes of a model fetched with Active Record from a remote legacy database with non-utf encoding. However, as it turned out, unlike previous versions of rails, AR in rails 3 automagically and silently handles the conversion to ut8 of all objects, even those fetched from DB's that are not unicode. So essentially after the upgrade my code ended up re-converting to utf8 strings that were already converted to utf8 by AR and the result was a mess of gibberish characters. The problem was resolved by completely removing the after_find and after_save callbacks, since they are no longer needed in this case :)

+1  A: 

Hi, I've tried to reproduce your problem, but I can only reproduce the deprecation warning, which you can get rid of by deleting your

define_method(:after_find) { }

statement.

All seems to work as expected besides that, both with and without the define_method statement.

My code:

class Testmodel < ActiveRecord::Base

  def self.encode(*attr_names)
    encoder = Encoder.new(attr_names)
    before_save encoder
    after_save encoder            
    after_find encoder
  end
end

class Encoder
  def initialize(attrs_to_manage) # We're passed a list of attributes that should be stored encoded in the database
    @attrs_to_manage = attrs_to_manage
  end

  def before_save(model) # Before saving or updating, encode the attributes to their original encoding
    @attrs_to_manage.each do |field|
      model[field] = to_orig_encod(model[field])
    end
  end

  def after_save(model) # After saving, encode them back to utf8
    @attrs_to_manage.each do |field|
      model[field] = to_utf8(model[field])
    end
  end

  alias_method :after_find, :after_save # Do the same after finding an existing record

  private
  def to_orig_encod(var)
    "foo"
  end

  def to_utf8(var)
    "bar"
  end
end

Console test:

ruby-1.9.2-p0 > Testmodel.create
 => #<Testmodel id: 3, name: nil, created_at: "2010-09-08 14:02:06", updated_at: "2010-09-08 14:02:06"> 
ruby-1.9.2-p0 > Testmodel.last
 => #<Testmodel id: 3, name: nil, created_at: "2010-09-08 14:02:06", updated_at: "2010-09-08 14:02:06"> 
ruby-1.9.2-p0 > Testmodel.encode('name')
 => [Testmodel(id: integer, name: string, created_at: datetime, updated_at: datetime)] 
ruby-1.9.2-p0 > Testmodel.last
 => #<Testmodel id: 3, name: "bar", created_at: "2010-09-08 14:02:06", updated_at: "2010-09-08 14:02:06"> 

I've been consulting the documentation at http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html to understand your question :-)

Jeppe Liisberg
That's a lot of effort of you, thanks a lot for reproducing the code, I'll play with your and mine code for a while now to figure out why your code is working and mine seems to be not and I'll be back with the conclusions shortly! Thanks once again for taking the time and effort to dig into this!
svilenv
ok, so I figured what the problem was. I explained it all as an edit to the question, since there's not enough space to put it all in a comment. Thanks again, Jeppe, for double checking the code and pointing me in the right direction!
svilenv