Override a private method on an ActiveRecord submodule

Hi.

I'm currently writing a plugin, which "could" be a patch in that it
must override a private method of ActiveRecord API, in
ActiveRecord::Dirty, to work smoothly. To make a long story short,
this plugin aims at enabling serialization with Marshal instead of
YAML (it actually works fine :)) -- the private update_with_dirty()
method, defined by an "alias_method_chain :update, :dirty", has to be
edited. Not really stable, but hey, it fits my needs :wink:

In my vendor/plugins/marshalize/lib/marshalize.rb, I defined a
Marshalization module, with some submodules which are included by
ActiveRecord::Base in vendor/plugins/marshalize/rails/init.rb using
send(). In the submodules I overrided several public class and
instance methods from several ActiveRecord submodules, all is fine.

But I did not manage to override the private method update_with_dirty
() from ActiveRecord::Dirty submodule directly from the plugin. Notice
thatthis method eventually becomes an instance private method of
ActiveRecord::Base since Dirty is "instance_evaled" by Base (at the
bottom of base.rb). For the sake of the tests, I created another
private method in my Marshalization::Dirty submodule and it eventually
gets included in ActiveRecord::Base as an instance private method; but
when it comes to using update_with_dirty(), Rails keeps using the old
one from the system active_record/lib/dirty.rb file and not the new
one defined in marshalize.rb -- and I don't figure out why :slight_smile:

Here's a gist for this implementation: http://gist.github.com/155990
Feel free to push if needed :wink:

Thank you for your help !
jd

Hi.

I'm currently writing a plugin, which "could" be a patch in that it
must override a private method of ActiveRecord API, in
ActiveRecord::Dirty, to work smoothly. To make a long story short,
this plugin aims at enabling serialization with Marshal instead of
YAML (it actually works fine :)) -- the private update_with_dirty()

(you 'll want to make sure that the column is a blob one rather than
text/string. Depending on database and column settings the database
might just truncate your data (eg if the column is marked as being
utf8 text but you try and store a byte sequence which is not legal
utf8.

method, defined by an "alias_method_chain :update, :dirty", has to be
edited. Not really stable, but hey, it fits my needs :wink:

In my vendor/plugins/marshalize/lib/marshalize.rb, I defined a
Marshalization module, with some submodules which are included by
ActiveRecord::Base in vendor/plugins/marshalize/rails/init.rb using
send(). In the submodules I overrided several public class and
instance methods from several ActiveRecord submodules, all is fine.

But I did not manage to override the private method update_with_dirty
() from ActiveRecord::Dirty submodule directly from the plugin. Notice
thatthis method eventually becomes an instance private method of
ActiveRecord::Base since Dirty is "instance_evaled" by Base (at the
bottom of base.rb). For the sake of the tests, I created another
private method in my Marshalization::Dirty submodule and it eventually
gets included in ActiveRecord::Base as an instance private method; but
when it comes to using update_with_dirty(), Rails keeps using the old
one from the system active_record/lib/dirty.rb file and not the new
one defined in marshalize.rb -- and I don't figure out why :slight_smile:

Are you sure the problem is the privateness ? My hunch is that the
following happens

- update_with_dirty is defined
- that method is aliased to update
- you define a new update_with_dirty, but by then it's too late: the
aliasing has already happen

for example:

class Foo
  def original
    puts "Original"
  end

  alias_method :newname, :original
end

Foo.new.newname #=> "Original"
Foo.new.original #=> "Original"

class Foo
  def original
    puts "new code"
  end
end

Foo.new.newname #=> "Original"
Foo.new.original #=> "new code"

Another way of looking at it is that alias_method isn't just storing a
new name for a method, it is actually copying it.

Fred

Hiha! 'did it, thanks to your hint. You were right, that was not
related to the privacy of the method. Ok, it was tricky, aliasing mess
inside. I added a file (test-overriding.rb) to my gist linked above to
demonstrate the behavior with a simple example that mimics
ActiveRecord structure. I also edited the plugin files, it eventually
looks like this in marshalize.rb:

module Marshalization
  module Dirty #:nodoc:

    private

    def update_with_marshalization
      if partial_updates?
        # Serialized *and* marshalized attributes should always be
written in case they've been changed in place.
        update_without_dirty(changed |
self.class.serialized_attributes.keys |
self.class.marshalized_attributes.keys)
      else
        update_without_dirty
      end
    end

    def self.included(receiver)
      receiver.alias_method_chain :update, :marshalization
    end
  end

  # stuff...
end

So as you can see, it all relates to alias_method_chain whose behavior
is a bit tricky to grasp when it comes to submodules inheritance (at
least, to me). Hope that'll be useful to someone. One has to
alias_chain_method again the main (original) method. You cannot just
call update nor alias update_with_dirty, because alias_chain_method
has kind of broke the relationship into pieces (I mean, if you follow
the trace, it keeps referencing the original method through the
aliasing chain, but when it comes to coding your alias, that's the
original name you get access to).

As a matter of fact, it would be better to actually add the behavior
for marshalized_attributes only, then call super, but it's a private
method here, so one has to use send. Since send will be "broken" in
this kind of context with Ruby 1.9, I'd rather create a publicize
method to temporary make the super method public, yielding to it in
this context. Anyway, the main issue is solved ^^