Michael Johnston
2006-Nov-28 13:04 UTC
What to do when two mixins need to hook into attribute= of an ActiveRecord
I can override an ActiveRecord attribute in an mixin as follows: module MyMixin def attribute=(new_value) write_attribute :attribute, new_value end end But what do I do if I have two mixins which may be included in arbitrary order and are somewhat orthogonal in purpose and who both need to customize attribute=? Cheers, Michael (specifically I am trying to figure out how to make act_as_attachment pass all its tests. It is broken right now because two mixins -- InstanceMethods and FileSystemMethods -- both override filename= ) --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Michael Johnston
2006-Nov-28 14:04 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
Ok, this actually easy, because they can both do: def attribute=(new_value) # stuff super end and all mixed in definitions will get called. I was led astray by the fact that all the examples of overriding an attribute writer have it calling write_attribute so I sillily thought that super would return a nomethoderror in the last mixin in the chain. But in actual fact you can just call super in an overridden attribute write, you don''t have to call write_attribute (unless someone knows a reason you do???). However, figuring out what to do if you couldn''t count on the method being mixed_in already having a definition in the superclass helped me to learn more about ruby. Here is my example, in case anyone is curious: class Superthing #this stands in for ActiveRecord::Base def dohook puts "whatever happens, this must happen" end end module Doer #this is in one file in a plugin module Mary def doit puts "Mary says doit" super end end end module Doer # this is in another file in a plugin, and is somewhat orthogonal to "Mary" module Bob def doit puts "Bob says doit" super end end end module Doer # this initializes the plugin def self.included(base) base.extend SetupMethods end module MakeSureTheresASuper # this is necessary because the superclass only defines "dohook" not "doit" def doit dohook end end module SetupMethods def setmeup include MakeSureTheresASuper if rand(2) == 1 puts "Bob first" include Bob include Mary else puts "Mary first" include Mary include Bob end end end end Superthing.send(:include, Doer::SetupMethods) class Thing < Superthing include Doer setmeup def initialize() end end #will always do Bob & will always do Mary, but order is random Thing.new.doit On 28-Nov-06, at 5:04 AM, Michael Johnston wrote:> > I can override an ActiveRecord attribute in an mixin as follows: > > module MyMixin > def attribute=(new_value) > write_attribute :attribute, new_value > end > end > > But what do I do if I have two mixins which may be included in > arbitrary order and are somewhat orthogonal in purpose and who both > need to customize attribute=? > > Cheers, > Michael > > (specifically I am trying to figure out how to make act_as_attachment > pass all its tests. It is broken right now because two mixins -- > InstanceMethods and FileSystemMethods -- both override filename= ) > > >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Eric Anderson
2006-Nov-28 14:05 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
Michael Johnston wrote:> I can override an ActiveRecord attribute in an mixin as follows: > > module MyMixin > def attribute=(new_value) > write_attribute :attribute, new_value > end > end > > But what do I do if I have two mixins which may be included in > arbitrary order and are somewhat orthogonal in purpose and who both > need to customize attribute=?Would a method chain work. The new release candidate has a method built to make this easy (I think called alias_method_chain) but to simple alias_method calls will work as well. Eric --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Eric Anderson
2006-Nov-28 14:07 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
Michael Johnston wrote:> But what do I do if I have two mixins which may be included in > arbitrary order and are somewhat orthogonal in purpose and who both > need to customize attribute=?Would a method chain work. The new release candidate has a method built to make this easy (I think called alias_method_chain) but two simple alias_method calls will work as well. Eric --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Rob Sanheim
2006-Nov-28 14:17 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
On 11/28/06, Michael Johnston <lastobelus-ee4meeAH724@public.gmane.org> wrote:> > I can override an ActiveRecord attribute in an mixin as follows: > > module MyMixin > def attribute=(new_value) > write_attribute :attribute, new_value > end > end > > But what do I do if I have two mixins which may be included in > arbitrary order and are somewhat orthogonal in purpose and who both > need to customize attribute=? > > Cheers, > Michael > > (specifically I am trying to figure out how to make act_as_attachment > pass all its tests. It is broken right now because two mixins -- > InstanceMethods and FileSystemMethods -- both override filename= )You''ll have to do a little metaprogramming and method chaining. This is assuming rails edge, so you can use alias_method_chain. So in mixin one: module Foo def self.included(klass) klass.alias_method_chain :my_method, :with_foo_feature end def my_method_with_foo_feature # do stuff end end module Bar def self.included(klass) klass.alias_method_chain :my_method, :with_bar_feature end def my_method_with_bar_feature # do stuff end end This is completely untested. If the order in which these things happens is important, you''ll have to be more careful and maybe explicitly require things. - rob -- http://www.robsanheim.com http://www.seekingalpha.com http://www.ajaxian.com --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Michael Johnston
2006-Nov-28 14:31 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
On 28-Nov-06, at 6:07 AM, Eric Anderson wrote:> Would a method chain work. The new release candidate has a method > built > to make this easy (I think called alias_method_chain) but two simple > alias_method calls will work as well.On 28-Nov-06, at 6:17 AM, Rob Sanheim wrote:> > You''ll have to do a little metaprogramming and method chaining. This > is assuming rails edge, so you can use alias_method_chain. > > So in mixin one: > > module Foo > def self.included(klass) > klass.alias_method_chain :my_method, :with_foo_feature > end > > def my_method_with_foo_feature > # do stuff > end > end > > module Bar > def self.included(klass) > klass.alias_method_chain :my_method, :with_bar_feature > end > > def my_method_with_bar_feature > # do stuff > end > end > > This is completely untested. If the order in which these things > happens is important, you''ll have to be more careful and maybe > explicitly require things. > > - robThanks Eric & Rob. This does seem like a more robust way of doing it, although I wouldn''t want to break the downward compatibility just yet. I''m curious to know if there is anything inherently wrong with my way of doing it, by calling super in each overloaded method, and letting the ancestors callchain take care of it? I found one wrinkle in my solution, which is that I do need to define a bare filename= method because sometimes the model objects do not persist the filename attribute to the database, in which case super is of course not defined for filename= (but write_attribute still works) Cheers, Michael --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Rob Sanheim
2006-Nov-28 14:43 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
> > Thanks Eric & Rob. This does seem like a more robust way of doing it, > although I wouldn''t want to break the downward compatibility just yet. > > I''m curious to know if there is anything inherently wrong with my way > of doing it, by calling super in each overloaded method, and letting > the ancestors callchain take care of it? > > I found one wrinkle in my solution, which is that I do need to define > a bare filename= method because sometimes the model objects do not > persist the filename attribute to the database, in which case super > is of course not defined for filename= (but write_attribute still works) > > Cheers, > MichaelI don''t think there is anything wrong with your implementation, strictly. Its just more code, which means more to test and to maintain. Using a Rails feature like alias_method_chain will be a more obvious idiom to other experienced Rails developers, and will also have the advantage of using a tested, supported api from Rails. OTOH, doing it yourself did help you learn more about what is going on underneath, which is a good thing. :) - rob -- http://www.robsanheim.com http://www.seekingalpha.com http://www.ajaxian.com --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Michael Johnston
2006-Nov-28 15:10 UTC
Re: What to do when two mixins need to hook into attribute= of an ActiveRecord
> > I don''t think there is anything wrong with your implementation, > strictly. Its just more code, which means more to test and to > maintain. Using a Rails feature like alias_method_chain will be a > more obvious idiom to other experienced Rails developers, and will > also have the advantage of using a tested, supported api from Rails. > > OTOH, doing it yourself did help you learn more about what is going on > underneath, which is a good thing. :) > > - robHmmm, I see one disadvantage with the aliases: the consumer class can''t override attribute=, unless they make sure the def stays physically before the inclusion of the modules. Using the callchain method, the consumer class can override attribute= as long as they call super instead of write_attribute. Unless I did something wrong. Here is my test: class Superthing #this stands in for ActiveRecord::Base def dohook puts "whatever happens, this must happen" end end module Doer #this is in one file in a plugin module Mary def self.included(base) base.class_eval do alias_method :doit_without_mary, :doit unless method_defined? (:doit_without_mary) alias_method :doit, :doit_with_mary end end def doit_with_mary puts "Mary says doit" doit_without_mary end end end module Doer # this is in another file in a plugin, and is somewhat orthogonal to "Mary" module Bob def self.included(base) base.class_eval do alias_method :doit_without_bob, :doit unless method_defined? (:doit_without_bob) alias_method :doit, :doit_with_bob end end def doit_with_bob puts "Bob says doit" doit_without_bob end end end module Doer # this initializes the plugin def self.included(base) puts "I just got included" base.extend SetupMethods end module MakeSureTheresASuper # this is necessary in my example def doit puts "I''m just standing in" dohook end end module SetupMethods def setmeup include MakeSureTheresASuper if rand(2) == 1 puts "Bob first" include Bob include Mary else puts "Mary first" include Mary include Bob end end end end Superthing.send(:include, Doer::SetupMethods) class Thing < Superthing include Doer setmeup end class CustomThing < Superthing include Doer setmeup def doit() puts "CustomThing says doit" super end # if we do setmeup here instead, it will work. but that is yucky end #will always do Bob & will always do Mary, but order is random puts "Thing:" Thing.new.doit puts puts "OOPS!!! CustomThing loses all the good love" puts "CustomThing:" CustomThing.new.doit --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---