I have a function which defines and returns a new class, with some pre-built methods. E.g.:
def define_class(name, options={}, &block)
klass = Class.new(Class) do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
end
parent_class.const_set(form_class, klass)
klass
end
So, for example, this works:
define_class("testing").new.say_hello #=> "Hello!"
But I would like to be able to pass in custom methods through a block, which would then be added to my class, like so:
define_class "testing" do
# ... custom methods
end
Such that this would work:
klass = define_class "testing" do
def interject
puts "Excuse me?"
end
end
klass.new.interject #=> "Excuse me?"
I can't figure out how to make that work though; I've tried instance_eval, class_eval, and yield, and none are producing the desired result.
Try simply:
def define_class(name, options={}, &block)
klass = Class.new(&block)
parent_class.const_set(form_class, klass)
klass
end
If you want to call the block and your own block, you should use class_eval:
def define_class(name, options={}, &block)
klass = Class.new do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
class_eval(&block)
end
parent_class.const_set(form_class, klass)
klass
end
Related
I have a class that was built for subclassing.
class A
def initialize(name)
end
def some
# to define in subclass
end
end
# usage
p A.new('foo').some
#=> nil
In my use case, I don't want to create a subclass since I need just one instance. Therefore, I'll change the initialize method to support the following usage.
p A.new('foo') { 'YEAH' }.some
#=> YEAH
How could I support the usage above?
BTW: I found the following solutions for a Ruby 1.8.7 project, but they look awkward to me.
class A
def singleton_class
class << self; self; end
end
def initialize(name, &block)
#name = name
self.singleton_class.send(:define_method, :some) { block.call } if block_given?
end
def some
# to define in subclass
end
end
You can store the block argument in an instance variable and call it later on:
class A
def initialize(name, &block)
#name = name
#block = block
end
def some
#block.call
end
end
A.new('foo') { 'YEAH' }.some
#=> "YEAH"
You can also pass arguments into the block:
class A
# ...
def some
#block.call(#name)
end
end
A.new('foo') { |s| s.upcase }.some
#=> "FOO"
Or instance_exec the block in the context of the receiver:
class A
# ...
def some
instance_exec(&#block)
end
end
Which allows you to bypass encapsulation:
A.new('foo') { #name.upcase }.some
#=> "FOO"
I am new to ruby
Trying to write an around aspect. My code is as follows
My code looks as follows
module Utils
module Aspects
def self.included(base)
base.extend(self)
end
def around_aspect(method_name, before_proc, after_proc)
code = %Q[
def #{method_name} *args, &block
#{before_proc.call}
old_#{method_name} *args, &block
#{after_proc.call}
end
]
class_eval %Q[
alias_method :old_#{method_name}, :#{method_name}
]
class_eval code
end
# def before_aspect method_name, before_proc
# around_aspect method_name, before_proc, ->(){}
# end
#
# def after_aspect method_name, after_proc
# around_aspect method_name, ->(){}, after_proc
# end
end
end
class Test
include Utils::Aspects
def test
puts 'test'
end
before = ->(){puts 'before'}
after = ->(){puts 'after'}
around_aspect :test,before,after
end
Test.new.test
The problem is that when i do Test.new.test I expect it to print
before, test and after" in order. But right now it prints "before,after and test"
The problem is that when i do Test.new.test I expect it to print
before, test and after" in order. But right now it prints "before,after and test"
No, it doesn't. When calling Test.new.test it only prints test. before and after are printed when defining the wrapped method, i.e. when calling around_advice.
Try to put a puts in between the call to around_advice and the call to Test.new.test (and try to call test several times) to observe this:
puts '______________________'
Test.new.test
Test.new.test
# before
# after
# ______________________
# test
# test
You are calling the lambdas only once, when defining the method:
code = %Q[
def #{method_name} *args, &block
#{before_proc.call}
# ^^^^^^^^^^^^^^^^^^^
old_#{method_name} *args, &block
#{after_proc.call}
# ^^^^^^^^^^^^^^^^^^
end
]
You need to call them every time when calling the method:
code = %Q[
def #{method_name} *args, &block
before_proc.call
old_#{method_name} *args, &block
after_proc.call
end
]
However, it would be much easier to just use Module#prepend, after all, that's what it's there for:
module Aspects
refine Module do
def around_aspect(method_name, before_proc, after_proc)
prepend(Module.new do
define_method(method_name) do |*args, &block|
before_proc.()
super(*args, &block)
after_proc.()
end
end)
end
end
end
class Test
using Aspects
def test
puts 'test'
end
before = -> {puts 'before'}
after = -> {puts 'after'}
around_aspect :test, before, after
end
Just putting my code up here. This is how i ended up achieving what i was trying to do, module.prepend as suggested above is another way
module Utils
module Aspects
def self.included(base)
base.extend(self)
end
def around_aspect(method_name, before_proc, after_proc)
new_method_name = Random.new_seed.to_s
alias_method :"#{new_method_name}", :"#{method_name}"
define_method "#{method_name}" do |*args, &block|
before_proc.call
send(:"#{new_method_name}", *args, &block)
after_proc.call
end
end
def before_aspect method_name, before_proc
around_aspect method_name, before_proc, ->(){}
end
def after_aspect method_name, after_proc
around_aspect method_name, ->(){}, after_proc
end
end
end
class Test
include Utils::Aspects
def test
puts 'test'
end
before = ->(){puts 'before'}
after = ->(){puts 'after'}
before_aspect :test, before
after_aspect :test, after
end
Test.new.test
I'm writing aspect-ish code more or less lifted from the chosen solution for this question that looks like the following:
class Module
def add_logging klass, *method_names
method_names.each do |method_name|
original_method = instance_method method_name
define_method method_name do |*args, &blk|
log.debug("#{klass}.#{method_name} called")
original_method.bind(klass).call(*args, &blk)
end
end
end
end
The solution in the other post doesn't require the klass parameter, but it only works for instance methods, whereas I hope to call my code like this:
module MyModule
def MyModule.module_method
p "hello"
end
class << self
add_logging self, :module_method1
end
end
Unfortunately when I run this code I get in 'bind': singleton method called for a different object (TypeError). Seeing as I pass self in the context of the class << self block, I don't understand why the bind call in the above code thinks it isn't binding to the exact same metaclass.
This should work for you:
class Module
def add_logging(*method_names)
method_names.each do |method_name|
original_method = method(method_name).unbind
define_singleton_method(method_name) do |*args, &blk|
puts "#{self}.#{method_name} called"
original_method.bind(self).call(*args, &blk)
end
end
end
end
# class method example
module MyModule
def self.module_method1
puts "hello"
end
add_logging :module_method1
end
MyModule.module_method1
# output:
#
# MyModule.module_method1 called
# hello
I currently have a superclass which has a function that I want all the subclass to call within each of its function. The function is supposed to behave like a before_filter function in rails but I am not sure on how to go about implementing before_filter. Here is an example
class Superclass
def before_each_method
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
This is one way to do it:
class Superclass
def before_each_method name
p [:before_method, name]
end
def self.method_added name
return if #__last_methods_added && #__last_methods_added.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
#__last_methods_added = [name, with, without]
define_method with do |*args, &block|
before_each_method name
send without, *args, &block
end
alias_method without, name
alias_method name, with
#__last_methods_added = nil
end
end
class SubclassA < Superclass
def my_new_method
p :my_new_method
end
def my_new_other_method
p :my_new_other_method
end
end
SubclassA.new.my_new_method
SubclassA.new.my_new_other_method
This will create a wrapper method using the alias_method_chaining method as soon as the method you'd like to wrap is defined in the subclass.
This is my solution:
require 'active_support/all'
module BeforeEach
extend ActiveSupport::Concern
module InstanceMethods
def before_each
raise NotImplementedError('Please define before_each method')
end
end
module ClassMethods
def method_added(method)
method = method.to_s.gsub(/_with(out)?_before$/, '')
with_method, without_method = "#{method}_with_before", "#{method}_without_before"
return if method == 'before_each' or method_defined?(with_method)
define_method(with_method) do |*args, &block|
before_each
send(without_method, *args, &block)
end
alias_method_chain(method, :before)
end
end
end
To use it, just include BeforeEach into your class like so:
class Superclass
include BeforeEach
def before_each
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
Subclass.new.my_method
# => Before Method
Hopefully this will work for you!
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_data, :months_used, :debits_amount
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class