As a follow up to How can I reverse ruby's include function, which was well answered but turned out my simplification of the real problem mean't that the solution was not applicable.
I'm now faced with this (names changed to protect identities!):
module OldFormHelpers
def foo
puts "foo"
end
def bar
puts "bar"
end
end
module Helpers
include OldFormHelpers
end
This gives me:
Helpers.instance_methods
=> ["bar", "foo"]
Helpers.ancestors
=> [Helpers, OldFormHelpers]
This is code that I don't really have access to modify, without forking.
What I want to do is create a new module;
module BetterFormHelpers
def foo
puts "better foo"
end
end
This needs to remove the behaviours from OldFormHelpers, and then add in the new stuff from BetterFormHelpers
The previous solution was to use undef_method like so:
Helpers.module_eval do
OldFormHelpers.instance_methods do |m|
undef_method(m)
end
end
However, after including BetterFormHelpers, Helpers.instance_methods doesn't contain "foo". The reason for this is explained at http://ruby-doc.org/core/classes/Module.src/M001652.html
Using remove_method tells me that Helpers doesn't have the "foo" method, so I guess I need some way of removing the first inclusion from the ancestors chain...
This was getting a bit long so I stopped putting so many snippets in towards the end, but I add an irb session showing the behaviour of undef/remove and then an include.
Can't you undefine only the methods that will not be overwritten?
Helpers.module_eval do
(OldFormHelpers.instance_methods - BetterFormHelpers.instance_methods).each do |m|
undef_method(m)
end
end
(The latter included module will be searched first so that no OldFormHelpers method will be executed if BetterFormHelpers also defines it.)
If you want to dynamically overwrite further methods of the OldFormHelpers module, however, the problem remains the same.
Related
Why can the following part # def games # #games = games # end come at the very end (bottom) of the code and still work? I thought Ruby reads the code from top to bottom. If I do not define games at the top, shouldn't it give an error?
class Library
# def games
# #games
# end
def initialize(games)
#games = games
end
def add_game(game)
games << game
end
# The following lines should come at the top of this code.
def games
#games
end
end
games = ['WoW','SC2','D3']
lib = Library.new(games)
lib.games #=> WoW,SC2,D3
lib.add_game('Titan')
lib.games #=> WoW,SC2,D3,Titan
When the method is defined, ruby is not running it. It's just available for the instance to use after you've invoked the class.
I generally put my methods in alphabetical order to make it easier to navigate my code as it grows. This is a personal preference.
Ruby allows you to structure and organize your classes/modules however is logical/beneficial to you.
To clarify, Ruby classes are executed when they're defined, but methods are not.
example.rb
class Example
puts "hello"
def my_method
puts "world"
end
end
Run it
$ ruby example.rb
hello
Because Ruby executes classes, that's how things like macros work in Ruby classes.
class Example2
attr_accessor :foo
end
attr_accessor is a method that gets called when the class is executed. In this case attr_acessor will setup get and set functions for the #foo instance variable.
If Ruby didn't execute your classes, this code would have to be called manually in some sort of initializer.
All you need to do is learn to differentiate between calling a method and defining a method. Defined methods will not be automatically executed.
The reason it is so is because of the way a class is built by Ruby: Every instance method definition inside a Ruby class gets defined first, during the top-down parsing. Then when you invoke each method it just matters whether its defined or not and not how its ordered.
Having said that order is important if you are redefining a method below. Then precedence will be given to lower definition.
I'm having lots of fun with ActiveModel's serialization, specifically the tangled web of as_json and serializable_hash.
My app has a large collection of models that share behavior by including a module, we'll call it SharedBehavior.
My team has decided we have a default format we want all these classes to follow when being cast to JSON (for rendering in a Rails app), but some of them should behave a little differently. Due to odd behavior in these two methods from the ActiveModel library, adding whitelisted or blacklisted attributes in the models themselves gets overridden by the method definition in this module, and then passed on to the super declarations in ActiveModel.
For this reason, I'd like this module to only apply its definition of these methods to models if they are not explicitly overridden in those models (in essence, take the module out of the ancestor chain for a few method calls), but I still need the shared behavior from this module.
I tried solving this by conditionally, dynamically applying the method on module inclusion in IRB:
class A
def foo
puts 'in A'
end
end
module D
def self.included(base)
unless base.instance_methods(false).include?(:foo)
define_method(:foo) do
puts 'in D'
super()
end
end
end
end
class B < A
include D
end
class C < A
include D
def foo
puts 'in C'
super
end
end
With this declaration, I expected the output of C.new.foo to be
in C
in A
but it was instead
in C
in D
in A
My only other thought is to move this logic out into another module and include that module in every class (there are about 54 of them) that does not explicitly override this method, but there are a couple downsides to that:
It introduces a bit of implicit coupling in the project that a new model include this module iff it does not want to override this method implementation
The current implementation of these serialization methods in the module have to do with behavior and attributes established by that module, so I feel like it would be unintuitive to have a second module that knows about and depends on those implementation details of SharedBehavior, though the second module would have almost nothing to do with the first.
Can anyone else think of another solution, or maybe spot an oversight of mine in the code example above that would allow me to make a call in the included hook? (I also tried switching the order in which the C class defined the foo method and included the D module, but saw exactly the same behavior).
There are two tricky bugs here.
Ruby evaluates classes, so the order of expressions matters. You include D before defining foo in C, so when the included hook is called, foo won't be defined in base. You need to include D at the end of the class.
You're defining foo in D. So after including D in B, D#foo is defined, meaning it's still included in C even if you fix the previous bug. You need base to be the receiver of define_method.
But there's an interesting twist: fixing the second bug makes the first bug irrelevant. By defining foo in base directly, it will be overwritten by any later definitions. It would be like doing
class C < A
def foo
puts 'in D'
super()
end
# overwrites previous definition!
def foo
puts 'in C'
super
end
end
So to summarize, you just need
# in D.included
base.class_eval do
define_method(:foo) do
puts 'in D'
super()
end
end
I want to be able to do this:
class IncludingClass
include IncludedModule
end
module IncludedModule
self.parent_class # => IncludingClass, I wish
end
Any ideas? Sorry for the brevity. Typing this on a phone. At any rate, I've looked around and haven't been able to find this, which seems surprising for such a met aprogrammable language.
I don't believe modules keep track of what included them. But they do fire a MyModule.included(SomeClass) method as a callback when they get included, so you can keep track yourself.
module IncludedModule
# Array to store included classes
##included_classes = []
# Called when this module is included.
# The including class is passed as an argument.
def self.included(base)
##included_classes << base
end
# Getter for class variable
def self.included_classes
##included_classes
end
end
# Include the module
class IncludingClass
include IncludedModule
end
# Ask the module what included it.
puts IncludedModule.included_classes #=> [IncludingClass]
There's probably also a way to crawl all Classes declared and ask them what they included via SomeClass.included_modules but that's kind of hairy, and would be much slower.
I love the autoload functionality of Ruby; however, it's going away in future versions of Ruby since it was never thread-safe.
So right now I would like to pretend it's already gone and write my code without it, by implementing the lazy-loading mechanism myself. I'd like to implement it in the simplest way possible (I don't care about thread-safety right now). Ruby should allow us to do this.
Let's start by augmenting a class' const_missing:
class Dummy
def self.const_missing(const)
puts "const_missing(#{const.inspect})"
super(const)
end
end
Ruby will call this special method when we try to reference a constant under "Dummy" that's missing, for instance if we try to reference "Dummy::Hello", it will call const_missing with the Symbol :Hello. This is exactly what we need, so let's take it further:
class Dummy
def self.const_missing(const)
if :OAuth == const
require 'dummy/oauth'
const_get(const) # warning: possible endless loop!
else
super(const)
end
end
end
Now if we reference "Dummy::OAuth", it will require the "dummy/oauth.rb" file which is expected to define the "Dummy::OAuth" constant. There's a possibility of an endless loop when we call const_get (since it can call const_missing internally), but guarding against that is outside the scope of this question.
The big problem is, this whole solution breaks down if there exists a module named "OAuth" in the top-level namespace. Referencing "Dummy::OAuth" will skip its const_missing and just return the "OAuth" from the top-level. Most Ruby implementations will also make a warning about this:
warning: toplevel constant OAuth referenced by Dummy::OAuth
This was reported as a problem way back in 2003 but I couldn't find evidence that the Ruby core team was ever concerned about this. Today, most popular Ruby implementations carry the same behavior.
The problem is that const_missing is silently skipped in favor of a constant in the top-level namespace. This wouldn't happen if "Dummy::OAuth" was declared with Ruby's autoload functionality. Any ideas how to work around this?
This was raised in a Rails ticket some time ago and when I investigated it there appeared to be no way round it. The problem is that Ruby will search the ancestors before calling const_missing and since all classes have Object as an ancestor then any top-level constants will always be found. If you can restrict yourself to only using modules for namespacing then it will work since they do not have Object as an ancestor, e.g:
>> class A; end
>> class B; end
>> B::A
(irb):3: warning: toplevel constant A referenced by B::A
>> B.ancestors
=> [B, Object, Kernel, BasicObject]
>> module C; end
>> module D; end
>> D::C
NameError: uninitialized constant D::C
>> D.ancestors
=> [D]
I get your problem on ree 1.8.7 (you don't mention a specific version) if I use const_get inside const_missing, but not if I use ::. I don't love using eval, but it does work here:
class Dummy
def self.const_missing(const)
if :OAuth == const
require 'dummy/oauth'
eval "self::#{const}"
else
super(const)
end
end
end
module Hello
end
Dummy.const_get :Hello # => ::Hello
Dummy::Hello # => Dummy::Hello
I wish Module had a :: method so you could do self.send :"::", const.
Lazy loading is a very common design pattern, you can implementing it in many ways. like :
class Object
def bind(key, &block)
#hooks ||= Hash.new{|h,k|h[k]=[]}
#hooks[key.to_sym] << [self,block]
end
def trigger(key)
#hooks[key.to_sym].each { |context,block| block.call(context) }
end
end
Then you can
bind :json do
require 'json'
end
begin
JSON.parse("[1,2]")
rescue
trigger :json
retry
end
Do you use the alias method in order to add more ways to call methods (like length and size) or is there another use for it?
The alias_method call is also useful for re-implementing something but preserving the original version. There's also alias_method_chain from Rails which makes that kind of thing even easier.
alias_method also comes in handy when you have a number of behaviors that are initially identical but might diverge in the future, where you can at least rough them in to start.
def handle_default_situation
nil
end
%w[ poll push foo ].each do |type|
alias_method :"handle_#{type}_situation", :handle_default_situation
end
Yes.
It is often used to preserve a handle to existing methods before overriding them. (contrived example)
Given a class like this:
class Foo
def do_something
puts "something"
end
end
You could see code that adds new behaviour like so:
class Foo
def do_something_with_logging
puts "started doing something"
do_something_without_logging # call original implementation
puts "stopped doing something"
end
alias_method :do_something_without_logging, :do_something
alias_method :do_something, :do_something_with_logging
end
(this is exactly how alias_method_chain works)
However, for this use case it't often more appropriate to use inheritance and modules to your advantage.
Still, alias_method is a useful tool to have, if you absolutely need to redefine behaviour in an existing class (or if you wanted to implement something like alias_method_chain)