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 ;) 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 :) Here''s a gist for this implementation: http://gist.github.com/155990 Feel free to push if needed ;) Thank you for your help ! jd
Frederick Cheung
2009-Jul-27 03:10 UTC
Re: Override a private method on an ActiveRecord submodule
On Jul 27, 2:23 am, jd <j...-YUCGor2yeoodnm+yROfE0A@public.gmane.org> wrote:> 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 ;) > > 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 :)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> > Here''s a gist for this implementation:http://gist.github.com/155990 > Feel free to push if needed ;) > > Thank you for your help ! > jd
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 ^^ On 27 juil, 05:10, Frederick Cheung <frederick.che...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> On Jul 27, 2:23 am, jd <j...-YUCGor2yeoodnm+yROfE0A@public.gmane.org> wrote: > > > 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 ;) > > > 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 :) > > 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 > > > > > Here''s a gist for this implementation:http://gist.github.com/155990 > > Feel free to push if needed ;) > > > Thank you for your help ! > > jd > >