I have a module that defines a method if it is not already defined. This is the case of ActiveRecord's attributes as theirs getters and setters are not defined as methods.
module B
def create_say_hello_if_not_exists
puts respond_to?(:say_hello)
define_method :say_hello do
puts 'hello'
end unless respond_to?(:say_hello)
end
end
class A
def say_hello
puts 'hi'
end
puts respond_to?(:say_hello, true)
extend B
create_say_hello_if_not_exists
end
A.new.say_hello
The expected result is hi, but ruby prints hello. Why?
Maybe related to Confused about "respond_to?" method
Try this.
module B
def create_say_hello_if_not_exists
puts method_defined?(:say_hello)
define_method :say_hello do
puts 'hello'
end unless method_defined?(:say_hello)
end
end
class A
def say_hello
puts 'hi'
end
puts method_defined?( :say_hello )
extend B
create_say_hello_if_not_exists
end
A.new.say_hello
The reason why respond_to?(:say_hello) is returning false is due to the fact class A has say_hello as instance method and since you are extending class B the create_say_hello_if_not_exists is declared as class method and it does not find say_hello.
Changing the code to the following would do the trick. I'm declaring say_hello in class A as class method and am calling it in a static manner.
module B
def create_say_hello_if_not_exists
puts respond_to?(:say_hello)
define_method :say_hello do
puts 'hello'
end unless respond_to?(:say_hello)
end
end
class A
def self.say_hello
puts 'hi'
end
extend B
create_say_hello_if_not_exists
end
A.say_hello
Related
could you please explain me why the class variable cannot be accessed by attribute_accessors?
As i am trying here to have the list of all methods of all subclasses in one array it works a little different. It created array #method_names in every subclass with specific methods for every class ... so i do need to do a loop through subclasses.
What kind of variable/attribute is #method_names?
Thanks!
module First
class First_class
class << self
def info
puts "First_class method info."
puts #subclasses
puts #method_names
end
def inherited(subclass)
puts "#{subclass} located ..."
subclasses << subclass
end
def subclasses
#subclasses ||= []
end
def method_added(method_name)
puts "Method located #{method_name} ..."
method_names << method_name
end
def method_names
#method_names ||= []
end
end
def initialize
puts "Instance of First_class is created."
end
def first_method
end
end
class Second_class < First_class
def self.info
puts "Second_class method info."
puts #subclasses
puts #method_names
end
def second_method
end
def initialize
puts "Instance of Second_class is created."
end
end
class Third_class < First_class
def third_method
end
def initialize
puts "Instance of Third_class is created."
end
end
end
First::First_class.subclasses.each {
|subclass| puts subclass
subclass.method_names.each {
|methodn| puts methodn
}
}
#################UPDATE#########
Ok, maybe I put the question incorrectly.
Basically what is the difference for ##method_names(class variable) and #method_names (instance variable) if i do not create the instance of object? After inserting more inputs into #method_names it still inserts into the same object_id. So what is benefit of ##method_names?
updated to answer updated question.
Classes in ruby can have class variables. However if you modify the class level variable, ALL instances will be modified. This is not recommended but will illustrate the point. But also see this answer
class Foo
##bar = 'bar'
attr_accessor :bar
def initialize
#bar = 'bar'
end
def class_bar
##bar
end
def change_class_bar string
raise ArgumentError unless string.is_a?(String)
##bar = string
end
end
a = Foo.new
b = Foo.new
# change the class variable ##bar
b.change_class_bar 'wtf?'
# see both instances are changed because objects are passed by referrence
print 'a.class_bar is: '
puts a.class_bar
print 'b.class_bar is: '
puts b.class_bar
# change one instance only
a.bar = 'only a has changed'
print 'a.bar is: '
puts a.bar
print 'b.bar is still: '
puts b.bar
run this and you should get output:
a.class_bar is: wtf?
b.class_bar is: wtf?
a.bar is: only a has changed
b.bar is still: bar
original answer left here
#method_names is an instance variable of an instance of the class from which it was instantiated. However it cannot be accessed for read/write unless those attributes are defined with getter or setter methods defined.
ff = First::First_class.new
Instance of First_class is created.
=> #<First::First_class:0x00007fde5a6867b8>
ff.method_names
NoMethodError: undefined method `method_names' for #<First::First_class:0x00007fde5a6867b8>
Did you mean? methods
Now if you call ff.methods you will see all methods defined through standard Ruby inheritance.
As a side note, class names in Ruby conventionally use PascalCase see PascalCase. Mixed_case is discouraged.
I have a ruby module like this:
module SomeModule
module Account
def self.account_info
raise NotImplementedError
end
end
end
and this is my test:
describe ExchangeClientWrapper::Account do
let(:mock_class) do
class MockClass
extend SomeModule::Account
end
end
describe ".account_info" do
it "raises a NotImplementedError" do
expect { mock_class.account_info }.to raise_error(NotImplementedError)
end
end
end
end
I get this error:
expected NotImplementedError, got #<NoMethodError: undefined method `account_info' for MockClass:Class> with backtrace:
What is going on?
Does this not work:
module A
module B
def self.d
puts "hi there"
end
end
end
class C
extend A::B
end
C.d
But it appears this works:
module A
module B
def self.d
puts "hi there"
end
end
end
class C
include A
end
C::B.d
Reading the reference in this article which explains include, extend, and prepend and I found out that extends works with the Singleton class already so the self is unnecesary.
I made a small test with this code, which removes the self in the definition
module SomeModule
module Account
def account_info
raise NotImplementedError
end
end
end
class MockClass
extend SomeModule::Account
end
MockClass.account_info
And that raises NotImplementedError
I have this module:
module Api
module ObjectMapper
def self.const_missing const_name
anon_class = Class.new do
def self.foo
puts self.class.name
end
end
const_set const_name, anon_class
end
end
end
I want to be able to define an anonymous class at runtime with a method foo that can be called with Api::ObjectMapper::User::foo. That function should print User to the screen. Everything that I have tried has resulted in some sort of error or the function prints Class to the screen.
How do I fix my class and method definitions so that self.class.name resolves correctly?
The name of a class is just the first constant that refers to it. The only problem with your original code is that you are using self.class.name instead of self.name:
module Api
module ObjectMapper
def self.const_missing const_name
self.const_set const_name, Class.new{
def self.foo
name.split('::').last
end
}
end
end
end
p Api::ObjectMapper::User, #=> Api::ObjectMapper::User
Api::ObjectMapper::User.name, #=> "Api::ObjectMapper::User"
Api::ObjectMapper::User.foo #=> "User"
When you define a class method (singleton method on a class) the self is the class. The self.class is therefore always Class, whose name is "Class".
Original answer, which returns "User" instead of "Api::ObjectMapper::User"
One way (no better than your closure-based solution) is to use an instance variable on the class:
module Api
module ObjectMapper
def self.const_missing const_name
anon_class = Class.new do
def self.foo
puts #real_name
end
end
anon_class.instance_variable_set(:#real_name,const_name)
const_set const_name, anon_class
end
end
end
Api::ObjectMapper::User.foo
#=> User
A couple alternative, tighter syntaxes:
def self.const_missing const_name
const_set const_name, Class.new{
def self.foo; puts #real_name end
}.tap{ |c| c.instance_eval{ #real_name = const_name } }
end
def self.const_missing const_name
const_set const_name, Class.new{
def self.foo; puts #real_name end
}.instance_eval{ #real_name = const_name; self }
end
I found a solution. Since you are defining the anonymous class from within the scope of the const_missing method, you have const_name available to you which is the name of the class you're defining. I'm not sure if that's the best solution, but it does work. You have to redefine your class like this:
anon_class = Class.new do
define_singleton_method(:foo) do
puts const_name
end
end
You can also define your foo method as class method
def self.const_missing const_name
anon_class = Class.new do
def self.foo
puts self.class.name
end
end
end
I want to wrap all class methods for a module with logging, like this:
module SomeModule
def self.foo
puts "bar"
end
class << self
SomeModule.methods(false).each do |method|
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end
end
SomeModule.foo
#-> Called method: foo
#-> bar
That works perfectly. But what if I wanted the wrapping to only happen when I called a method? How could I make this happen when you call
module SomeModule
def self.foo
puts "bar"
end
def self.add_logging_to_all_methods
#???
end
end
SomeModule.add_logging_to_all_methods
SomeModule.foo
#-> Called method: foo
#-> bar
I’m not going to ask what you want this for, but here it is:
module SomeModule
def self.foo
puts "bar"
end
def self.add_logging_to_all_methods
eigenclass = class << self; self; end
methods(false).each do |method|
eigenclass.class_eval do
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end
end
end
SomeModule.add_logging_to_all_methods
SomeModule.foo
Be aware that this also adds “logging” to add_logging_to_all_methods, but only after invoking it, so if you only invoke it once, you should not see anything wrong.
What eigenclass means is the “instance” where you add this methods foo and add_logging_to_all_methods. By returning self inside the class << self; end block I’m getting that instance. Then I ask the block to be evaluated in the context of that instance, which does more or less the same as your previous method.
There may be an easier way to do it.
You can apply that on all classes:
ObjectSpace.each_object.select { |o| o.is_a? Class }.each do |klass|
klass.class_eval do
methods(false).each do |method|
alias_method "old_#{method}".to_sym, method
define_method method do |*args|
puts "Called method: #{method}"
send "old_#{method}", *args
end
end
end rescue nil
end
Ah nevermind, just placing the whole class << self block in the method works okay.
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