Can you override an aliased method in Ruby? - ruby-on-rails

In Ruby, when a method is aliased, the alias points to the body of the original method. So even if you redefine the original method, the alias will continue to use the original definition.
class Foo
def bar
"bar"
end
alias :saloon :bar
end
class Foo
def bar
"BAR"
end
end
puts Foo.new.saloon
will return 'bar' and not 'BAR'. Is there any way to get saloon to use the new definition of bar?
EDIT: I should have been more clear. The example was just an illustration of the issue - it's not the actual problem I need to solve. The issue is more complex when you have chained aliases, for example, in rails' core. E.g. perform_action is aliased by benchmarking module, and then also by flash module. So now a call to perform_action is actually calling perform_action_with_flash which does it's thing, then effectively calls perform_action_with_benchmarking which then calls the original perform_action. If I want to override perform_action_with_benchmarking (even though I agree it's a bad idea - please let's not get into a discussion of that as it's besides the point), I can't because it has been aliased, and as far as I can tell the alias is pointing to what is essentially a copy of the original perform_action_with_benchmarking, so even if I redefine it, there's no effect.

Just re-establish the alias:
class Foo
def bar
"bar"
end
alias :saloon :bar
end
class Foo
def bar
"BAR"
end
alias :saloon :bar
end
puts Foo.new.saloon # => "BAR"

class Foo
def bar
"bar"
end
def saloon
bar
end
end
This is not an alias at all, but it works as you want.

Yes and no. Either coreyward or Sony Santos's solutions work fine. What you need to know is why your coded didn't work the way you though.
alias makes a new name for the function as is appears when the method is invoked. This is not a pointer, but a new way of referring to something. It allows us to do something like this:
class Foo
def bar
"bar"
end
alias :speakeasy :bar
end
class Foo
def bar(secret_code = false)
return speakeasy if secret_code == "A friend of Al"
"Closed because of prohibition!"
end
end
puts Foo.new.bar #=> "Closed because of prohibition!"
puts Foo.new.bar "A friend of Al" #=> "bar"
The old bar still exists, it just a little harder to access now.

Here is another answer, but you have to do some additional steps: collect the aliases before overriding, and realias after:
class Class
def get_aliases method_name
original_proc = instance_method method_name
aliases = []
instance_methods.each do |meth|
# if the methods have different names but they're the same, they're aliased
if meth != method_name.to_s && original_proc == instance_method(meth)
aliases << meth
end
end
aliases
end
end
class Foo
def bar
"bar"
end
alias :saloon :bar
end
class Foo
aliases = get_aliases :bar
def bar
"BAR"
end
aliases.each { |a| alias_method a, :bar }
end
puts Foo.new.saloon #=> BAR
BTW, if anyone can strip off one of that steps, may I know it! :)

Related

Override module_function inside ruby class with access to original

I'm trying to override a Rails helper method that's defined like this:
class Foo
module Bar
def orig
# orig code
end
alias o orig
module_function :o
module_function :orig
end
end
So that I can override and add functionality to orig and o something like this:
def orig
# new code
# super (run orig code)
end
alias o orig
I've looked through several different monkey patching methods but they don't seem to work. I believe the module_function is what's throwing it off.
Anyone know how I can achieve this?
Here's a workaround. You can re-open the module, make an unbound reference to the original instance method, then redefine it to call the original method (with some altered behavior).
First, the original definition:
module Foo
def bar(arg)
"self=#{self}, arg=#{arg}"
end
module_function :bar
end
Next, reopening and redefining the method:
module Foo
OrigBarMethod = instance_method(:bar)
def bar(arg)
Foo::OrigBarMethod.bind(self).call(arg + 1)
end
module_function :bar
end
puts Foo.bar(1) # => "self=Foo, arg=2"
I use bind(self) so that the original method can still make use of self, for example:
class MyClass
include Foo
end
MyClass.new.send(:bar, 1) # => "self=#<MyClass:0x00007fb66a86cbf8>, arg=2"
Pretty much any circumstance where you would in the past have used monkey-patching can nowadays be solved with inheritance and Module#prepend:
module Foo
def bar(arg)
"self=#{self}, arg=#{arg}"
end
module_function :bar
end
module FooExtension
def bar(arg)
super(arg + 1)
end
end
[Foo, Foo.singleton_class].each do |mod|
mod.prepend FooExtension
end
Foo.bar(1) #=> "self=Foo, arg=2"
class MyClass
include Foo
end
MyClass.new.bar(1) #=> "self=#<MyClass:0x00007fb66a86cbf8>, arg=2"

Dynamically defining a Object#initialize for a Ruby class

In my code base, I have a bunch of objects that all adhere to the same interface, which consists of something like this:
class MyTestClass
def self.perform(foo, bar)
new(foo, bar).perform
end
def initialize(foo, bar)
#foo = foo
#bar = bar
end
def perform
# DO SOMETHING AND CHANGE THE WORLD
end
end
The differentiating factor between the classes is the arity of the self.perform and initialize, plus the body of the #perform class.
So, I'd like to be able to create an ActiveSupport::Concern (or just a regular Module if that would work better) which allowed me to do something like this:
class MyTestClass
inputs :foo, :bar
end
which would then use some meta-programming to define self.perform and initialize of the above methods whose airty would depend on the airty specified by the self.inputs method.
Here is what I have so far:
module Commandable
extend ActiveSupport::Concern
class_methods do
def inputs(*args)
#inputs = args
class_eval %(
class << self
def perform(#{args.join(',')})
new(#{args.join(',')}).perform
end
end
def initialize(#{args.join(',')})
args.each do |arg|
instance_variable_set(##{arg.to_s}) = arg.to_s
end
end
)
#inputs
end
end
end
This seems to get the arity of the methods correct, but I'm having a tough time figuring out how to handle the body of the #initialize methods.
Can anybody help me figure out a way that I can successfully meta-program the body of #initialize so it behaves like the example I provided?
You could use this as body for #initialize:
#{args}.each { |arg| instance_variable_set("#\#{arg}", arg) }
However, I wouldn't string eval it. It usually leads to evil things. That said, here is an implementation which gives an incorrect Foo.method(:perform).arity, but still behaves as you would expect:
module Commandable
def inputs(*arguments)
define_method(:initialize) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
arguments.zip(parameters).each do |argument, parameter|
instance_variable_set("##{argument}", parameter)
end
end
define_singleton_method(:perform) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
new(*parameters).perform
end
end
end
class Foo
extend Commandable
inputs :foo, :bar
def perform
[#foo, #bar]
end
end
Foo.perform 1, 2 # => [1, 2]
You were so close! instance_variable_set takes two arguments, first is the instance variable and second is the value you want to set it to. You also need to get the value of the variable, which you can do using send.
instance_variable_set(##{arg.to_s}, send(arg.to_s))

Ruby: Mixin which adds dynamic instance methods whose names are created using a class method

I have the following:
module Thing
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
attr_reader :things
def has_things(*args)
options = args.extract_options! # Ruby on Rails: pops the last arg if it's a Hash
# Get a list of the things (Symbols only)
#things = args.select { |p| p.is_a?(Symbol) }
include InstanceMethods
end
end
module InstanceMethods
self.class.things.each do |thing_name| # !!! Problem is here, explaination below
define_method "foo_for_thing_#{thing_name}" do
"bar for thing #{thing_name}"
end
end
end
end
In another class which mixes-in the Thing module:
class Group
has_things :one, :two, :option => "something"
end
When calling has_things within a class, I would like to have the dynamic "foo_for_thing_one" and "foo_for_thing_two" instance methods available. For example:
#group = Group.new
#group.foo_for_thing_one # => "bar for thing one"
#group.foo_for_thing_two # => "bar for thing two"
However, I get the following error:
`<module:InstanceMethods>': undefined method `things' for Module:Class (NoMethodError)
I realize that "self" in the problem line pointed out above (first line of the InstanceMethods module) refers to the InstanceMethods module.
How do I reference the "things" class method (which returns [:one, :two] in this example) so I can loop through and create dynamic instance methods for each? Thanks. Or if you have other suggestions for accomplishing this, please let me know.
Quick answer:
Put the contents of InstanceMethods inside the has_things method definition and remove the InstanceMethods module.
Better answer:
Your use of the InstanceMethods-ClassMethods anti-pattern is especially unwarranted here and cargo-culting it has added to your confusion about scope and context. Do the simplest thing that could possibly work. Don't copy someone else's code without critical thinking.
The only module you need is ClassMethods, which should be given a useful name and should not be included but rather used to extend the class that you want to grant the has_things functionality. Here's the simplest thing that could possibly work:
module HasThings
def has_things(*args)
args.each do |thing|
define_method "thing_#{thing}" do
"this is thing #{thing}"
end
end
end
end
class ThingWithThings
extend HasThings
has_things :foo
end
ThingWithThings.new.thing_foo # => "this is thing foo"
Only add complexity (options extraction, input normalization, etc) when you need it. Code just in time, not just in case.

Creating new Ruby string helper without argument params

I really had a hard time figuring out how to word this question, but in essence, I want to do this:
model = MyModel.new
model.title = "foo bar"
model.title.to_id #=> "foo_bar"
I have an ActiveRecord class for MyModel
class MyModel < ActiveRecord::Base
def to_id(str)
str.downcase.gsub(" ", "_")
end
end
but, of course, it's looking for the to_id method on String, and I don't want to override string, because I don't require this behaviour on every string. Just strings associated with MyModel. I could keep it simple and do something like:
model.to_id(model.title)
But that's not very Ruby.
I know I've seen examples of this sort of method implemented before, I just can't track them down.
Halp anyone?
you can extend a specific object instance with a method, using modules.
module ToId
def to_id
self.downcase.gsub " ", "_"
end
end
class MyClass
def title=(value)
value.extend ToId
#title = value
end
def title
#title
end
end
m = MyClass.new
m.title = "foo bar"
puts m.title #=> foo bar
puts m.title.to_id #=> foo_bar
since the value passed into the .title= method is a string, when we extend the string string with the ToId module, "self" in the module's methods is a string. therefore, we have direct access to the string that was passed into the .title= method, and we can manipulate it directly.
this is all done without having to modify the String class directly. we are only extending the specific instance that represents the title.
I believe that true Ruby solution is based on meta-programming. I'd strongly recommend you this book http://pragprog.com/titles/ppmetr/metaprogramming-ruby ($20) if you are interested.
By the way - the solution proposed above probably will not work as overriding column accessors is not that simple.
So I would recommend to create a class method that you use in your model definition like this:
class MyModel < ActiveRecord::Base
# adds to_id to the following attributes
ideize :name, :title
end
Well, that was an easy part, now comes the tougher one - the module itself:
#
# extends the ActiveRecord with the class method ideize that
# adds to_id method to selected attributes
#
module Ideizer
module ClassMethods
def ideize(*args)
# generates accessors
args.each do |name|
define_method("#{name}") do
# read the original value
value = read_attribute(name)
# if value does not contain to_id method then add it
unless value.respond_to?(:to_id)
# use eigen class for the value
class << value
def to_id
self.downcase.gsub " ", "_"
end
end
end
# return the original value
value
end
end
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
# extend the active record to include ideize method
ActiveRecord::Base.send(:include, Ideizer)
I have to admit that I did not write the solution above just from my memory so I've prepared some tests that I'm sharing here:
require 'spec_helper'
describe MyModel do
before :each do
#mod = MyModel.new(:name => "Foo Bar",
:title => "Bar Bar",
:untouched => "Dont touch me")
end
it "should have to_id on name" do
#mod.name.respond_to?(:to_id).should be_true
#mod.name.to_id.should eql "foo_bar"
end
it "should have to_id on title" do
#mod.title.respond_to?(:to_id).should be_true
#mod.title.to_id.should eql "bar_bar"
end
it "should NOT have to_id on untouched" do
#mod.untouched.respond_to?(:to_id).should be_false
end
it "should work with real model" do
#mod.save!
#mod.name.to_id.should eql "foo_bar"
# reload from the database
#mod.reload
#mod.name.to_id.should eql "foo_bar"
end
end
Ruby rules!
You should take a look at the functions within ActiveSupport::CoreExtensions::String::Inflections, specifically in your case I would use the underscore method that exist in the (expanded) String class.
Then to override the accessor you could do something like:
def title_id
read_attribute(:title).underscore
end
I think that's what you want.

rails override default getter for a relationship (belongs_to)

So I know how to override the default getters for attributes of an ActiveRecord object using
def custom_getter
return self[:custom_getter] || some_default_value
end
I'm trying to achieve the same thing however for a belongs to association. For instance.
class Foo < AR
belongs_to :bar
def bar
return self[:bar] || Bar.last
end
end
class Bar < AR
has_one :foo
end
When I say:
f = Foo.last
I'd like to have the method f.bar return the last Bar, rather than nil if that association doesn't exist yet.
This doesn't work however. The reason is that self[:bar] is always undefined. It's actually self[:bar_id].
I can do something naive like:
def bar
if self[:bar_id]
return Bar.find(self[:bar_id])
else
return Bar.last
end
end
However this will always make a db call, even if Bar has already been fetched, which is certainly not ideal.
Does anyone have an insight as to how I might have a relationship such that the belongs_to attribute is only loaded once and has a default value if not set.
alias_method is your friend here.
alias_method :original_bar, :bar
def bar
self.original_bar || Bar.last
end
The way this works is that you alias the default "bar" method as "original bar" and then implement your own version of "bar". If the call to original_bar returns nil then you return the last Bar instance instead.
i found that using "super" is the best way
def bar
super || Bar.last
end
I hope this helps you :D
Randy's answer is spot on, but there's an easier way to write it, using alias_method_chain:
def bar_with_default_find
self.bar_without_default_find || Bar.last
end
alias_method_chain :bar, :default_find
That creates two methods - bar_with_default_find and bar_without_default_find and aliases bar to the with method. That way you can explicitly call one or the other, or just leave the defaults as is.
Building upon other answers here, you may also want to handle assignment operations as well.
Alias method:
alias_method :original_bar, :bar
alias_method :original_bar=, :bar=
def bar
self.original_bar ||= Bar.last
end
Super method:
def bar
super || bar = Bar.last
end
Where this was useful for me was when using Bar.find_or_initialize_by which meant the record wasn't always persisted, and also any non-persisted changes would reflect on the parent record as well.

Resources