redefining a single ruby method on a single instance with a lambda
def define_singleton_method_by_proc(obj, name, block)
metaclass = class << obj; self; end
metaclass.send(:define_method, name, block)
end
p = proc { "foobar!" }
define_singleton_method_by_proc(y, :bar, p)
or, if you want to monkey-patch Object to make it easy
class Object
# note that this method is already defined in Ruby 1.9
def define_singleton_method(name, callable = nil, &block)
block ||= callable
metaclass = class << self; self; end
metaclass.send(:define_method, name, block)
end
end
p = proc { "foobar!" }
y.define_singleton_method(:bar, p)
#or
y.define_singleton_method(:bar) do
"foobar!"
end
or, if you want to define your proc inline, this may be more readable
class << y
define_method(:bar, proc { "foobar!" })
end
or,
class << y
define_method(:bar) { "foobar!" }
end
this is the most readable, but probably doesn't fit your needs
def y.bar
"goodbye"
end
This question is highly related
How to temporarily redefine a method in Ruby?
You can use instance_method
to get a UnboundMethod
object from any instance method:
class Foo
def bar
"Hello"
end
end
old_method = Foo.instance_method(:bar)
# Redifine the method
Foo.define_method(:bar) do
puts "Goodbye"
end
puts Foo.new.bar # Goodbye
# restore the old method:
Foo.define_method(old_method.name, old_method)
Unbound methods are a reference to the method at the time it was objectified: subsequent changes to the underlying class will not affect the unbound method.
The equivilent for class methods is:
class Foo
def self.baz
"Hello"
end
end
old_method = Foo.method(:baz).unbind
If you want to make the worlds smallest (and perhaps the most useless) stubbing library you could do it with:
class Stubby
def initialize(klass, method_name, &block)
@klass = klass
@old_method = klass.instance_method(method_name)
@klass.define_method(method_name, &block)
end
def restore
@klass.define_method(@old_method.name, @old_method)
end
def run_and_restore
yield
ensure
restore
end
end
puts Foo.new.bar # Hello
Stubby.new(Foo, :bar) do
"Goodbye"
end.run_and_restore do
puts Foo.new.bar # Goodbye
end
puts Foo.new.bar # Hello
Redefining Rails' methods
Create config/initializers/super_to_sentence.rb
. All files in this directory are loaded after Rails has been loaded, so you'll have a chance to override Rails' definition of Array#to_sentence
.
For code you want to load before Rails gets loaded, add it to config/environment.rb
.
Is there a way to redefine a method within a block?
On second thoughts, here's a better answer:
def always_upcase_strings(&block)
anon_class = Class.new do
def puts(str)
super(str.upcase)
end
end
anon_class.new.instance_eval(&block)
end
always_upcase_strings do
puts "abc" #=> "ABC"
puts "def" #=> "DEF"
puts "ghi" #=> "GHI"
end
puts "xyz" #=> "xyz"
This creates a temporary class (anonymous, because it has no need for a name) with the desired method override.
The yielded block is then invoked, within the context of this class instance.
As well as being less confusing than my "redefine-undefine-redefine the method" solution, this approach has the added advantage that it's thread-safe. So you won't get weird behaviour if, for example, running parallel tests whilst invoking the method.
...But I still stand by my original statement that redefining methods within a block is super surprising behaviour. Coworkers probably won't like you for choosing that design pattern (unless it's done in a limited manner, and for good reason!)
Is it possible in Ruby to redefine a initialize method?
I'm afraid that Foo
is a lazily autoload
ed class, and you are "redefining" initialize
before Foo
is loaded.
Try this
Foo.class_eval do
def initialize
@hello = "welt"
end
end
This forces Foo
to be loaded before redefining anything.
Redefine a single method on an instance to call superclass method
I'd try to refactor the behavior to be more test-friendly. E.g. you could allow an optional parameter to destroy e.g. i_know_what_im_doing
that has to be set to true to carry out the destroy. Alternatively you could cancel the destroy
with a before_destroy
hook like
class Administrator < Person
def before_destroy(record)
# You can't destroy me
false
end
end
In your tests, you can then call Administrator.skip_callback :before_destroy
to ignore it and to have a proper destroy.
Finally, you could overwrite / stub the method in your tests. While you say you don't want to modify the class's behavior, you still have to do that (and implicitly do that with your destroy!
method today).
Calling original method from a redefined one
If some_object.some_method
is not a singleton method, then you can just call super
in your redefined method.
def some_object.some_method
super
do_something_else
end
If some_object.some_method
is a singleton method, then
Ruby >= 2.0.0
You can define that method in a module
module SomeModule
def some_method
super
do_something_else
end
end
And then prepend it to the singleton class of the object
some_object.singleton_class.prepend(SomeModule)
Ruby < 2.0.0
You have to make an alias then redefine, since there is no Module#prepend
.
class << some_object # open the singleton class of some_object
alias some_method_original some_method
def some_method
some_method_original
do_something_else
end
end
Alter method visibility without redefining method
Though @МалъСкрылевъ's approach is more sensible, IMO, you could alternatively create a public alias of the method:
Product.class_eval do
alias_method :public_build_variants, :build_variants_from_option_values_hash
public :public_build_variants
end
which could now be used as
p = Product.new
p.public_build_variants
Redefining a method
Indeed super
wont work. You need to somehow keep a reference to the old method and you do this by creating an alias.
class LibraryToExtend
alias :FunctionToExtend :original_function
def FunctionToExtend(argument)
if argument == something
do_something_new
else
original_function()
end
end
end
As a side note, the convention is that ruby methods are in lowecase and underscores (_) not camelcase (but that's just me being bitchy)
Ruby - How to redefine class methods?
alias_method
is meant for instance methods. But File.basename
is a class method.
class File
class << self
alias_method :basename_without_hello, :basename
def basename(*args)
puts "hello world!"
basename_without_hello(*args)
end
end
end
The class << self
evaluates everything on the "class level" (Eigenklass) - so you don't need to write self.
(def self.basename
) and alias_method
applies to class methods.
Related Topics
Are There Better Ways to Prevent 'Yield' When No Block Is Passed In
Differencebetween #Encode and #Force_Encoding in Ruby
How to Replace the Last Occurrence of a Substring in Ruby
Missing a Template for This Request Format and Variant
Normalizing Line Endings in Ruby
Rails Before_Filter for Specific Actions in Controller
How to Escape Strings for Terminal in Ruby
What Are the Advantages of Mocha Over Rspec's Built in Mocking Framework
Accepts_Nested_Attributes_For Rails 4 Is Not Deleting
Using Yield Inside Define_Method in Ruby
What's the Difference Between Arrays and Hashes
Verb-Agnostic Matching in Sinatra
Carrierwave - Resizing Images to Fixed Width
"Don't Run Bundler as Root" - What Is the Exact Difference Made by Using Root