How do I create a Custom Hook Method in a Subclass?
No need to duplicate Rails, of course -- the simpler, the better.
My goal is to convert:
class SubClass
def do_this_method
first_validate_something
end
def do_that_method
first_validate_something
end
private
def first_validate_something; end
end
To:
class ActiveClass; end
class SubClass < ActiveClass
before_operations :first_validate_something, :do_this_method, :do_that_method
def do_this_method; end
def do_that_method; end
private
def first_validate_something; end
end
Example in Module: https://github.com/PragTob/after_do/blob/master/lib/after_do.rb
Rails #before_action: http://apidock.com/rails/v4.0.2/AbstractController/Callbacks/ClassMethods/before_action
Here's a solution that uses prepend. When you call before_operations for the first time it creates a new (empty) module and prepends it to your class. This means that when you call method foo on your class, it will look first for that method in the module.
The before_operations method then defines simple methods in this module that first invoke your 'before' method, and then use super to invoke the real implementation in your class.
class ActiveClass
def self.before_operations(before_method,*methods)
prepend( #active_wrapper=Module.new ) unless #active_wrapper
methods.each do |method_name|
#active_wrapper.send(:define_method,method_name) do |*args,&block|
send before_method
super(*args,&block)
end
end
end
end
class SubClass < ActiveClass
before_operations :first_validate_something, :do_this_method, :do_that_method
def do_this_method(*args,&block)
p doing:'this', with:args, and:block
end
def do_that_method; end
private
def first_validate_something
p :validating
end
end
SubClass.new.do_this_method(3,4){ |x| p x }
#=> :validating
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:0x007fdb1301fa18#/tmp.rb:31>}
If you want to make the idea by #SteveTurczyn work you must:
receive the args params in the block of define_method, not as arguments to it.
call before_operations AFTER your methods have been defined if you want to be able to alias them.
class ActiveClass
def self.before_operations(before_method, *methods)
methods.each do |meth|
raise "No method `#{meth}` defined in #{self}" unless method_defined?(meth)
orig_method = "_original_#{meth}"
alias_method orig_method, meth
define_method(meth) do |*args,&block|
send before_method
send orig_method, *args, &block
end
end
end
end
class SubClass < ActiveClass
def do_this_method(*args,&block)
p doing:'this', with:args, and:block
end
def do_that_method; end
before_operations :first_validate_something, :do_this_method, :do_that_method
private
def first_validate_something
p :validating
end
end
SubClass.new.do_this_method(3,4){ |x| p x }
#=> :validating
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:0x007fdb1301fa18#/tmp.rb:31>}
You can alias the original method to a different name (so :do_this_something becomes :original_do_this_something) and then define a new :do_this_something method that calls :first_validate_something and then the original version of the method Something like this...
class ActiveClass
def self.before_operations(before_method, *methods)
methods.each do |method|
alias_method "original_#{method.to_s}".to_sym, method
define_method(method, *args, &block) do
send before_method
send "original_#{method.to_s}", *args, &block
end
end
end
end
This is a way of writing the code that does not make use of aliases. It includes a class method validate that specifies the validator method and the methods that are to call the validator method. This method validate can be invoked multiple times to change the validator and validatees dynamically.
class ActiveClass
end
Place all the methods other than the validators in a subclass of ActiveClass named (say) MidClass.
class MidClass < ActiveClass
def do_this_method(v,a,b)
puts "this: v=#{v}, a=#{a}, b=#{b}"
end
def do_that_method(v,a,b)
puts "that: v=#{v}, a=#{a}, b=#{b}"
end
def yet_another_method(v,a,b)
puts "yet_another: v=#{v}, a=#{a}, b=#{b}"
end
end
MidClass.instance_methods(false)
#=> [:do_this_method, :do_that_method, :yet_another_method]
Place the validators, together with a class method validate, in a subclass of MidClass named (say) SubClass.
class SubClass < MidClass
def self.validate(validator, *validatees)
superclass.instance_methods(false).each do |m|
if validatees.include?(m)
define_method(m) do |v, *args|
send(validator, v)
super(v, *args)
end
else
define_method(m) do |v, *args|
super(v, *args)
end
end
end
end
private
def validator1(v)
puts "valid1, v=#{v}"
end
def validator2(v)
puts "valid2, v=#{v}"
end
end
SubClass.methods(false)
#=> [:validate]
SubClass.private_instance_methods(false)
#=> [:validator1, :validator2]
The class method validate passes symbols for the validation method to use and the methods to be validated. Let's try it.
sc = SubClass.new
SubClass.validate(:validator1, :do_this_method, :do_that_method)
sc.do_this_method(1,2,3)
# valid1, v=1
# this: v=1, a=2, b=3
sc.do_that_method(1,2,3)
# valid1, v=1
# that: v=1, a=2, b=3
sc.yet_another_method(1,2,3)
# yet_another: v=1, a=2, b=3
Now change the validation.
SubClass.validate(:validator2, :do_that_method, :yet_another_method)
sc.do_this_method(1,2,3)
# this: v=1, a=2, b=3
sc.do_that_method(1,2,3)
# valid2, v=1
# that: v=1, a=2, b=3
sc.yet_another_method(1,2,3)
# valid2, v=1
# yet_another: v=1, a=2, b=3
When super is called without arguments from a normal method, all arguments and a block, if there is one, are passed to super. If the method was created with define_method, however, no arguments (and no block) are passed to super. In the latter case the arguments must be explicit.
I wanted to pass a block or proc on to super if there is one, but have been using the wrong secret sauce. I would welcome advice for doing that.
Is it okay to instantiate a class A and assign it to a class variable in class B to call A's methods in B?
class A
...
end
class B
##a = A.new
def method_B
##a.method_A
end
end
You can wrap methods in a module and include them in your class(For instance methods only)
module Foo
def method_A
p 'hello'
end
end
class B
include Foo
def method_B
method_A
end
end
B.new.method_B
I'm trying to create a method that passes the caller as the default last argument. According to this, I only need:
class A
def initialize(object = self)
# work with object
end
end
so that in:
class B
def initialize
A.new # self is a B instance here
end
end
self will be B rather than A;
However, this doesn't seem to work. Here's some test code:
class A
def self.test test, t=self
puts t
end
end
class B
def test test,t=self
puts t
end
end
class T
def a
A.test 'hey'
end
def b
B.new.test 'hey'
end
def self.a
A.test 'hey'
end
def self.b
B.new.test'hey'
end
end
and I get:
T.new.a # => A
T.new.b # => #<B:0x000000015fef00>
T.a # => A
T.b # => #<B:0x000000015fed98>
whereas I expect it to be T or #<T:0x000000015fdf08>. Is there a way to set the default last argument to the caller?
EDIT:
class Registry
class << self
def add(component, base=self)
self.send(component).update( base.to_s.split('::').last => base)
end
end
end
The idea is pretty simple, you would use it like this
class Asset_Manager
Registry.add :utilities
end
and you access it like:
include Registry.utilities 'Debugger'
I'm trying to de-couple classes by having a middle-man management type class that takes care of inter-class communications, auto-loading of missing classes and erroring when it doesn't exist, it works but I just want to be able to use the above rather than:
class Asset_Manager
Registry.add :utilities, self
end
It just feels cleaner, that and I wanted to know if such a thing was possible.
You can't escape the explicit self. But you can hide it with some ruby magic.
class Registry
def self.add(group, klass)
puts "registering #{klass} in #{group}"
end
end
module Registrable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def register_in(group)
Registry.add(group, self)
end
end
end
class AssetManager
include Registrable
register_in :utilities
end
# >> registering AssetManager in utilities
In short, you can't.
Ruby resolves the default arguments in the context of the receiver. That is, the object before the . in a method call. What you called the receiver should be the caller, actually.
class A
def test1(value = a)
puts a
end
def test2(value = b)
puts b
end
def a
"a"
end
end
a = A.new
a.test1 #=> a
def a.b; "b" end
a.test2 #=> b
If I were you, I would use the extended (or included) hook, where both the extending class and the extended module can be accessed. You can program what ever logic you want based on the information.
module Registry
module Utilities
def self.extended(cls)
#puts cls
::Registry.send(component).update( cls.to_s.split('::').last => cls)
end
end
end
class Asset_Manager
extend Registry::Utilities
end
I'm using Ruby 1.9.3 and trying to make some tests with RSpec.
I have a class:
class A
def method1
"test"
end
end
class B < SimpleDelegator
def initialize(events)
#events = events
end
end
Now I'm trying to test delegation behaviour:
require 'spec_helper'
RSpec.describe B do
let(:a) { A.new }
let(:b) { B.new(a) }
it "Should delegate unknown calls to A object" do
expect(b.method1).not_to eq(nil)
end
end
I get the following error:
NoMethodError:
undefined method `method1' for nil:B
Seems that the test would pass if add method_missing manually:
class B < SimpleDelegator
def initialize(events)
#events = events
end
def method_missing(meth, *args, &blk)
#events.send(meth, *args, &blk)
end
end
What I'm doing wrong here?
Thanks
The problem is that you added a initializer to the class B without calling super and passing the instance you want to decorate. Your code should look like this:
class A
def method1
"test"
end
end
class B < SimpleDelegator
def initialize(events)
#events = events
super(events)
end
end
You don't need to define an initialize method on B. SimpleDelegator defines one for you. When you defined your own initialize method, you overrode the initialize method you inherited from the SimpleDelegator class.
Try this:
class A
def method1
"test"
end
end
class B < SimpleDelegator
end
This is from irb: B.new(A.new).method1 #=> "test"
You could define your own initialize method and call super, but I wouldn't unless you really had to.
I am a NOOB trying to understand some code.
What does this self.class.current_section do?
class MyClass
class << self
def current_section(*section)
if section.empty?
#current_section
else
#current_section = section[0]
end
end
end
def current_section()
self.class.current_section
end
def current_section=(section)
self.class.current_section(section)
end
end
self returns the the current object.
self.class returns the class of current object.
self.class.current_section invokes the method of the class of current object (that method called current_section).
def current_section()
p self
p self.class
end
current_section()
It forwards the message (method call request) received by the object to the corresponding class.
Say you have a class
class MyClass
def MyClass.current_section
puts "I'm the class method."
end
def current_section
self.class.current_section
end
end
h = MyClass.new
h.current_section # outputs "I'm the class method."
Calling h's method, it looks up h's class (MyClass) and invokes the method current_section of that class.
So, by the definitions above, every object of the class MyClass has a method current_section which is routed to the central current_section of the class.
The definition of the class methods in your question is just using a different syntax for doing the same: adding a method to the class object.
class << self
def current_section(*section)
if section.empty?
#current_section
else
#current_section = section[0]
end
end
end
This code part is being evaluated in the Class Object scope because of the class << self statement. Thus current_section is being defined as a class method, invokable as Myclass.current_section.
def current_section()
self.class.current_section
end
This part is just the definition of an instance method, and thus self is an instance of the Myclass object.
self.class gets the class of such instance, thus, Myclass, and the current_section method of the class is invoked.