In the following code include module is used. The way I see it if include module is removed then also an instance method would be created. Then why user include module ?
http://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations.rb#L1416
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy # def destroy
super # super
#{reflection.name}.clear # posts.clear
end # end
RUBY
}
First of all let's make one thing clear. When they call super inside the class_eval — it has absolutely nothing to do with why they used include Module.new {} thing. In fact the super which was called inside the destroy method is completely irrelevant to answering your question. There could be any arbitrary code inside that destroy method.
Now that we got it out of the way, here's what's going on.
In ruby, if you simply define a class method, and then define it again in the same class, you will not be able to call super to access the previous method.
For example:
class Foo
def foo
'foo'
end
def foo
super + 'bar'
end
end
Foo.new.foo # => NoMethodError: super: no superclass method `foo' for #<Foo:0x101358098>
This makes sense, because the first foo was not defined in some superclass, or anywhere up the lookup chain (which is where super points). However, you can define the first foo in such a way that when you later overwrite it — it will be available by calling super. This is exactly what they wanted to achieve with doing module include.
class Foo
include Module.new { class_eval "def foo; 'foo' end" }
def foo
super + 'bar'
end
end
Foo.new.foo # => "foobar"
This works, because when you include a module, ruby inserts it into the lookup chain. This way you can subsequently call super in the second method, and expect the included method to be called. Great.
However, you may wonder, why not simply include a module without all the tricks? Why are they using block syntax? We know that my above example is exactly equivalent to the following:
module A
def foo
'foo'
end
end
class Foo
include A
def foo
super + 'bar'
end
end
Foo.new.foo # => "foobar"
So why didn't they do that? The answer is — the call to reflection. They needed to capture the variable (or method) which was available in the current context, which is reflection.
Since they are defining the new module using block syntax, all the variables outside of the block are available for usage inside the block. Convenient.
Just to illustrate.
class Foo
def self.add_foo_to_lookup_chain_which_returns(something)
# notice how I can use variable something in the class_eval string
include Module.new { class_eval "def foo; '#{something}' end" }
end
end
# so somewhere else I can do
class Foo
add_foo_to_lookup_chain_which_returns("hello")
def foo
super + " world"
end
end
Foo.new.foo # => "hello world"
Neat, huh?
Now let me stress it again. The call to super inside of the destroy method in your example has nothing to do with any of the above. They called it for their own reasons, because maybe the class where this is happening is subclassing another class which already defined destroy.
I hope this made it clear.
I'm guessing but... they don't want to overwrite the "destroy" method, and want to leave it available to be overloaded by some end-user (you or me), without it removing this "reflection.clear" functionality.
So - by including it as a module, they can call "super" which will call the original destroy or the overloaded version (written by the end-user).
Thanks to include, destroy method is not overwritten. It lands in ghost class that actual class derives from. That way, when one will call destroy on AR object, original one will be called, and super will call one from anonymous module (which will later call original destroy from class that it derived from).
A bit tricky, indeed.
Related
I would like to print the class and method any time a method gets redefined in a Rails app, even if it happens in an included gem. I understand there is a hook called method_added that gets called back on redefining a method, but I do not know how to use it to catch anything that gets redefined.
How do I use method_added?
I added this to boot.rb:
class Module
def method_added(name)
puts "adding #{self.name.underscore.capitalize} #{name}\n"
end
end
But that seems to be catching every single method in every single class?
You can use the Module#method_added hook to keep a record of all methods that are being defined, and check whether you have already seen the same method before:
require 'set'
module LoggingMethodAdded
def method_added(meth)
#methods ||= Set.new
puts "Monkey patching #{meth} in #{self}!" if #methods.include?(meth)
#methods << meth
super
end
end
class Module
prepend LoggingMethodAdded
end
class Foo; def foo; end end
class Foo; def foo; end end
# Monkey patching foo in Foo!
module Bar; def foo; end end
module Bar; def foo; end end
# Monkey patching foo in Bar!
This, however, will only work for methods that are added after you have loaded your hook method. The obvious alternative would be to check the already defined methods instead of recording them yourself:
def method_added(meth)
puts "Monkey patching #{meth} in #{self}!" if (instance_methods(false) | private_instance_methods(false)).include?(meth)
super
end
But this doesn't work: it is not exactly specified when the method_added hook gets executed; it might get executed after the method has been defined, which means the check will always be true. (At least that's what happened in my tests.)
In a Rails controller you can pass a symbol to the layout method that corresponds to a method in you controller that will return the layout name like this:
layout :my_method
def my_method
'layout_1'
end
I want to have a similar functionality to likewise pass a symbol to my classes method and that class should call the corresponding function and use its return value, like this
myClass.foo :my_method
def my_method
'layout_1'
end
I've read posts[1] that tell me I need to pass
myClass.foo(method(:my_method))
which I find ugly and inconvenient. How is rails here different allowing to pass just the symbol without any wrapper? Can this be achieved like Rails does it?
[1] How to implement a "callback" in Ruby?
If you want to only pass a :symbol into the method, then you have to make assumptions about which method named :symbol is the one you want called for you. Probably it's either defined in the class of the caller, or some outer scope. Using the binding_of_caller gem, we can snag that information easily and evaluate the code in that context.
This surely has security implications, but those issues are up to you! :)
require 'binding_of_caller'
class Test
def foo(sym)
binding.of_caller(1).eval("method(:#{sym})").call
end
end
class Other
def blork
t = Test.new
p t.foo(:bar)
p t.foo(:quxx)
end
def bar
'baz'
end
end
def quxx
'quxx'
end
o = Other.new
o.blork
> "baz"
> "quxx"
I still don't understand, what is author asking about. He's saying about "callbacks", but only wrote how he wants to pass parameter to some method. What that method(foo) should do - i have no idea.
So I tried to predict it's implementation. On class initialising it gets the name of method and create private method, that should be called somewhere under the hood. It possible not to create new method, but store method name in class variable and then call it somewhere.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo(method_name)
define_method :_foo do
send method_name
end
end
end
end
class BaseClass
include Foo
end
class MyClass < BaseClass
foo :my_method
private
def my_method
"Hello world"
end
end
MyClass.new.send(:_foo)
#=> "Hello world"
And really, everything is much clearer when you're not just wondering how it works in rails, but viewing the source code: layout.rb
I would like to access my subclass using only the name of my module.
module MyModule
class UselessName
include OtherModel
# only self method
def self.x
end
end
# No other class
end
And I would like to write MyModule.x and not MyModule::UselessName.x
I could transform my module in class, but I use RoR Helpers, and I would prefer that MyModule remains a module and not a class.
Is there a way to do this ?
Thanks ;)
OK, let's split problem into two - getting list of such methods and making proxies in the module.
Getting list might be a little tricky:
MyModule::UselessName.public_methods(false) - MyModule::UselessName.superclass.public_methods(false)
Here we start with list of all public class methods and subtract list of all superclass's public class methods from it.
Now, assuming we know method's name, we need to make proxy method.
metaclass = class << MyModule; self; end
metaclass.send(:define_method, :x) do |*args, &block|
MyModule::UselessName.send(:x, *args, &block)
end
This code will just make equivalent of following definition at runtime.
module MyModule
def x(*args, &block)
MyModule::UselessName.send(:x, *args, &block)
end
end
So let's put it together in simple function.
def make_proxies(mod, cls)
methods = cls.public_methods(false) - cls.superclass.public_methods(false)
metaclass = class << mod; self; end
methods.each do |method|
metaclass.send(:define_method, method) do |*args, &block|
cls.send(method, *args, &block)
end
end
end
So now you'll just need to call it for needed modules and classes. Note that "destination" module can be different from "source" module owning the class, so you can slurp all methods (given they have different names or you'll prefix them using class name) to one module. E.g. for your case just make following call.
make_proxies(MyModule, MyModule::UselessName)
Ok, I've found a VERY DIRTY way to accomplish what I THINK you mean:
module MyModule
class UselessName
include OtherModule
# have whatever you want here
end
def self.x
# whatever
end
end
So somewhere in your code you can do, and I repeat THIS IS VERY, VERY DIRTY!
MyModule.methods(false).each do |m|
# m = method
# now you can redefine it in your class
# as an instance method. Not sure if this
# is what you want though
MyModule::UselessName.send(:define_method, :m) do
# in this NEW (it's not the same) method you can
# call the method from the module to get the same
# behaviour
MyModule.send(m)
end
end
I don't know if this overwrites an instance method with the same name if it's in the class before or if it throws an exception, you have to try that.
In my opinion you should overthink your app design, because this is not the way it should be, but here you go...
I'm working with ActiveAttr which gives you that nice initialize via block option:
person = Person.new() do |p|
p.first_name = 'test'
p.last_name = 'man'
end
However, in a specific class that include ActiveAttr::Model, I want to bypass this functionality since I want to use the block for something else. So here we go:
class Imperator::Command
include ActiveAttr::Model
end
class MyCommand < Imperator::Command
def initialize(*args, &block)
#my_block = block
super(*args)
end
end
This fails miserably, because the block still gets passed up the chain, and eventually inside of ActiveAttr, this code gets run:
def initialize(*)
super
yield self if block_given?
end
So if my call looks like so:
MyCommand.new() { |date| date.advance(month: 1) }
it fails as follows:
NoMethodError: undefined method `advance' for #<MyCommand:0x007fe432c4fb80>
since MyCommand has no method :advance it the call to MyCommand obviously fails.
So my question is this, is there a way that I can remove the block from the method signature before I call super again, so that the block travels no further than my overridden initializer?
Try
super(*args,&nil)
The & makes ruby use nil as the block and ruby seems smart enough to realise this means no block.
That is certainly a neat trick, but a better approach would be to not use ActiveAttr::Model module directly and instead include only the modules you need.
Rather than
class Imperator::Command
include ActiveAttr::Model
end
Do
class Imperator::Command
include BasicModel
# include BlockInitialization
include Logger
include MassAssignmentSecurity
include AttributeDefaults
include QueryAttributes
include TypecastedAttributes
def initialize(*args, &block)
#my_block = block
super(*args)
end
end
Once you see the exploded view of ActiveAttr::Model is doing there may be other things you really don't want. In that case just simply omit the includes. The intention was to provide an à la carte approach to model constructing.
I wanted to write a little "Deprecate-It" lib and used the "method_added" callback a lot.
But now I noticed that this callback is not triggered, when including a module.
Are there any callbacks or workarounds, to get class "Foobar" informed when somewhing is included to itself?
Small Demo to demonstrate:
# Including Moduls won't trigger method_added callback
module InvisibleMethod
def invisible
"You won't get a callback from me"
end
end
class Foobar
def self.method_added(m)
puts "InstanceMethod: '#{m}' added to '#{self}'"
end
def visible
"You will get a callback from me"
end
include InvisibleMethod
end
[:invisible, :visible, :wont_exist].each do |meth|
puts "#{meth}: #{Foobar.public_method_defined? meth}"
end
That's the result:
InstanceMethod: 'visible' added to 'Foobar'
invisible: true
visible: true
wont_exist: false
Additional Information:
I really need to use a hook like method_added.
ActiveModel is adding public_instance_methods to Class during runtime though anonymous Modules.
The problem is that including modules doesn't add methods to the classes - it only changes the method call chain. This chain defines which classes/module will be searched for a method, that is not defined for the class in question. What happens when you include a module is an addition of an entry in that chain.
This is exactly the same as when you add a method in a superclass - this doesn't call method_added since it is not defined in the superclass. It would be very strange if a subclass could change the behavior of a superclass.
You could fix that by manually calling method added for an included module, by redefining include for your class:
class Foobar
def self.include(included_module)
included_module.instance_methods.each{|m| self.method_added(m)}
super
end
end
And it is much safer than redefining the included method in Module - the change is narrowed only to the classes, that you have defined yourself.
As suggested by one of the comments, you could use some other hook to get the behavior you want. For example, try to add this at the beginning of your code:
class Module
def included(klass)
if klass.respond_to?(:method_added)
self.instance_methods.each do |method|
klass.method_added(method)
end
end
end
end
Whenever a module is included in a class, all instance methods of that module will be notified to the class, as long as it defines the method method_added. By running your code with the change above I get this result:
InstanceMethod: 'visible' added to 'Foobar'
InstanceMethod: 'invisible' added to 'Foobar'
invisible: true
visible: true
wont_exist: false
Which I think is the behavior you want.
I think deprecation is not big isue to require a library. it is implemented like this in datamapper. About method_added hook; it is working as expected because methods already added to module not class. Only you can get your expected result monkey patching included hook.
# got from https://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/deprecate.rb
module Deprecate
def deprecate(old_method, new_method)
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{old_method}(*args, &block)
warn "\#{self.class}##{old_method} is deprecated, use \#{self.class}##{new_method} instead (\#{caller.first})"
send(#{new_method.inspect}, *args, &block)
end
RUBY
end
end # module Deprecate
class MyClass
extend Deprecate
def old_method
p "I am old"
end
deprecate :old_method, :new_method
def new_method
p "I am new"
end
end
m = MyClass.new
m.old_method
# MyClass#old_method is deprecated, use MyClass#new_method instead (pinger.rb:27:in `<main>')
# "I am new"