Redefining Rails' Methods

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 autoloaded 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



Leave a reply



Submit