8

I would like to override the behavior of the update_attributes method on a single instance of a model class. Assuming the variable is called @alert what is the best way to go about this? To be clear, I do not want to modify the behavior of this method for the entire class.


DISCLAIMER:

I need to do this to force the method to return false when I want it to so that I can write unit tests for the error handling code following. I understand it may not be the best practice.

Steve
  • 836
  • 1
  • 9
  • 17
  • 1
    I was about to correct your use of the term "monkey patch"--thinking it applied only to modifications of Ruby's built-in methods--but after googling it appears there is no consensus as to its meaning. See, for example, [this SO question](http://stackoverflow.com/questions/394144/what-does-monkey-patching-exactly-mean-in-ruby). – Cary Swoveland Jun 04 '15 at 21:31
  • @CarySwoveland Historically "money patch" refers to stomping existing methods with your own implementations where these take over completely. In this case it's more of a "mock" operation, where it's only altering a single object, and other objects, both existing and those created in the future, are unaffected. – tadman Jun 04 '15 at 21:53
  • This smells a lot like a method stub, which any decent test framework will accommodate. – zetetic Jun 05 '15 at 01:01

2 Answers2

10

Just define a method on the object:

class Thing
  def greeting
    'yo, man'
  end
end

Thing.instance_methods(false)
  #=> [:greeting]

object = Thing.new
  #=> #<Thing:0x007fc4ba276c90> 

object.greeting
  #=> "yo, man" 

Define two methods on object (which will be instance methods in object's singleton class.

def object.greeting
  'hey, dude'
end

def object.farewell
  'see ya'
end

object.methods(false)
  #=> [:greeting, :farewell] 
object.singleton_class.instance_methods(false) #=> [:greeting, :farewell]

object.greeting
  #=> "hey, dude" 
object.farewell
  #=> "see ya"

new_obj = Thing.new
new_obj.greeting
  #=> "yo, man"
new_obj.farewell
  #NoMethodError: undefined method `farewell' for #<Thing:0x007fe5a406f590>

Remove object's singleton method greeting.

object.singleton_class.send(:remove_method, :greeting)

object.methods(false)
  #=> [:farewell] 
object.greeting
  #=> "yo, man"

Another way to remove :greeting from the object's singleton class is as follows.

class << object
  remove_method(:greeting)
end
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Sometimes this is more cleanly done by making a module and mixing that in to the individual object. – tadman Jun 04 '15 at 21:52
  • Typo: `Thing.new hi` should be `Thing.new.hi`. The exception will then be `NoMethodError: undefined method \`hi' for #` instead of `NameError: ...` – nitsas Mar 02 '17 at 12:37
1

After creating an object, call "define_method" on it:

object = Thing.new
object.define_method("update_attributes") { return false }

That done, object.update_attributes should now return false

David Hoelzer
  • 15,862
  • 4
  • 48
  • 67
  • The object inherits from `ActiveRecord::Base` and I am getting a `NoMethodError` attempting to call `define_method` on it. Any idea? It may be obvious but I am relatively new to the Ruby on Rails subtleties. – Steve Jun 04 '15 at 20:18
  • 1
    `define_method` is only defined on `Module`, but `object` is not a `Module`, it's a `Thing`. – Jörg W Mittag Jun 04 '15 at 22:03