Bruno Cardoso
2010-Aug-04 16:41 UTC
[rspec-users] [RSpec] Dynamic reloading with Object.send(:remove_const)
Hi, I have a function that adds dynamic validations to my models. I use a combination of ":remove_const" and "load" methods to reload the code everytime I want to re-apply my dynamic validations. It seams RSpec has some issues with this. I posted here a simpler example code that shows what I mean: http://pastie.org/private/ruvgxgipxfge6yzuudbq Is it possible to test this with RSpec? Regards -- Posted via http://www.ruby-forum.com/.
Rick DeNatale
2010-Aug-05 00:34 UTC
[rspec-users] [RSpec] Dynamic reloading with Object.send(:remove_const)
On Wed, Aug 4, 2010 at 12:41 PM, Bruno Cardoso <lists at ruby-forum.com> wrote:> Hi, > > I have a function that adds dynamic validations to my models. I use a > combination of ":remove_const" and "load" methods to reload the code > everytime I want to re-apply my dynamic validations. It seams RSpec has > some issues with this. > > I posted here a simpler example code that shows what I mean: > > http://pastie.org/private/ruvgxgipxfge6yzuudbq > > Is it possible to test this with RSpec?Your pastie says # this should have made "Account" go back to its original state but it''s not working. # this works outside of RSpec. but I don''t understand in what context. In general this won''t work in Ruby. Removing the constant only removes the constant from the internal hash which binds the name to the object value. It doesn''t change the class object (pointed to by the klass field) of existing instances. Here''s a little experiment. The defineFoo method should be the moral equivalent of the load, and the existence of the m2 method is an analogue to whether the class has the validation callback. def defineFoo eval("class Foo;def m1;end;end") end defineFoo foo = Foo.new foo.methods - Object.instance_methods # => ["m1"] Foo.class_eval("def m2;end") foo.methods - Object.instance_methods # => ["m1", "m2"] Object.send(:remove_const, ''Foo'') defineFoo foo2 = Foo.new foo2.methods - Object.instance_methods # => ["m1"] foo.methods - Object.instance_methods # => ["m1", "m2"] foo.class == foo2.class # => false -- Rick DeNatale Blog: http://talklikeaduck.denhaven2.com/ Github: http://github.com/rubyredrick Twitter: @RickDeNatale WWR: http://www.workingwithrails.com/person/9021-rick-denatale LinkedIn: http://www.linkedin.com/in/rickdenatale
Bruno Cardoso
2010-Aug-05 09:16 UTC
[rspec-users] [RSpec] Dynamic reloading with Object.send(:remove_const)
Rick Denatale wrote:> Your pastie says > # this should have made "Account" go back to its original state but > it''s not working. > # this works outside of RSpec. > > but I don''t understand in what context. In general this won''t work in > Ruby. > > Removing the constant only removes the constant from the internal hash > which binds the name to the object value. It doesn''t change the class > object (pointed to by the klass field) of existing instances. > > Here''s a little experiment. The defineFoo method should be the moral > equivalent of the load, and the existence of the m2 method is an > analogue to whether the class has the validation callback. > > def defineFoo > eval("class Foo;def m1;end;end") > end > > defineFoo > > foo = Foo.new > > foo.methods - Object.instance_methods # => ["m1"] > > Foo.class_eval("def m2;end") > foo.methods - Object.instance_methods # => ["m1", "m2"] > > Object.send(:remove_const, ''Foo'') > defineFoo > > foo2 = Foo.new > > foo2.methods - Object.instance_methods # => ["m1"] > foo.methods - Object.instance_methods # => ["m1", "m2"] > > foo.class == foo2.class # => falseEureka! You are absolutely right. I completly forgot that I need to instantiate the object again to have the updated definition of the class. Old instances remain the same even after the class definition is changed. Thanks Rick. -- Posted via http://www.ruby-forum.com/.
Rick DeNatale
2010-Aug-05 12:51 UTC
[rspec-users] [RSpec] Dynamic reloading with Object.send(:remove_const)
On Thu, Aug 5, 2010 at 5:16 AM, Bruno Cardoso <lists at ruby-forum.com> wrote:> Rick Denatale wrote: >> Your pastie says >> # this should have made "Account" go back to its original state but >> it''s not working. >> # this works outside of RSpec. >> >> but I don''t understand in what context. ?In general this won''t work in >> Ruby. >> >> Removing the constant only removes the constant from the internal hash >> which binds the name to the object value. ?It doesn''t change the class >> object (pointed to by the klass field) of existing instances. >> >> Here''s a little experiment. The defineFoo method should be the moral >> equivalent of the load, and the existence of the m2 method is an >> analogue to whether the class has the validation callback. >> >> def defineFoo >> ? eval("class Foo;def m1;end;end") >> end >> >> defineFoo >> >> foo = Foo.new >> >> foo.methods - Object.instance_methods # => ["m1"] >> >> Foo.class_eval("def m2;end") >> foo.methods - Object.instance_methods # => ["m1", "m2"] >> >> Object.send(:remove_const, ''Foo'') >> defineFoo >> >> foo2 = Foo.new >> >> foo2.methods - Object.instance_methods # => ["m1"] >> foo.methods - Object.instance_methods # => ["m1", "m2"] >> >> foo.class == foo2.class # => false > > Eureka! > > You are absolutely right. I completly forgot that I need to instantiate > the object again to have the updated definition of the class. Old > instances remain the same even after the class definition is changed. > > Thanks Rick.That''s all well and good, but I have to step back and say that I detect a code smell called "stupid ruby tricks" here. This type of dynamic class alteration can lead to all kinds of trouble trying to understand, and debug the code. Debugging Rails callbacks is tough enough even without such unorthodox code. If you pursue this technique I predict several frustrating debugging sessions in your future. Why not just use the AR API and start with something like class Account < ActiveRecord::Base class << self attribute_accessor :validating_description end validates_numericality_of :description :if => lambda { |account| Account.validating_description} end Then just use Account.validating_description = true to turn it off and Account.validating_description = false to turn it off? Even then, I''m not sure I understand the use case for turning this off at the class label, usually such conditional validations are done on the basis of instance state, which is why the proc associated with the :if and :unless options takes the instance as an argument, of course the proc is free to ignore that as I''ve done here. But it''s your use case, and other than poking you to think about whether you want to do it on a class or instance basis, I''ll defer to your superior domain knowledge. -- Rick DeNatale Blog: http://talklikeaduck.denhaven2.com/ Github: http://github.com/rubyredrick Twitter: @RickDeNatale WWR: http://www.workingwithrails.com/person/9021-rick-denatale LinkedIn: http://www.linkedin.com/in/rickdenatale