I've got some troubles with Ruby about callbacks (and inheritance). Here is my code:
class Lmao
def initialize
#str = "HAHAHAHAHAHHAHAHAH"
#before_laughing = []
end
def self.inherited(base)
base.extend(Callbacks)
end
def laughing
#before_laughing.each {|method| send(method) }
#str
end
end
module Callbacks
def before_laughing(*methods)
#before_laughing = methods
end
end
class Lol < Lmao
before_laughing :downcase_please
def downcase_please
#str.downcase!
end
end
a = Lol.new
a.laughing # => "HAHAHAHAHAHHAHAHAH"
And as you can see, my before laughing callback don't work... because the array #before_laughing is empty. I believe this can be fixed by editing the way I save *methods into an Lol's instance method (from inside Callbacks). But I don't really see how...
If you know the solution, thanks for your light!
Thanks to Mon_Ouie, the solution is:
class Lmao
def initialize
#str = "HAHAHAHAHAHHAHAHAH"
end
def self.inherited(base)
base.extend(Callbacks)
end
def laughing
self.class.callbacks_before_laughing.each {|method| send(method) }
#str
end
end
module Callbacks
def before_laughing(*methods)
#before_laughing = methods
end
def callbacks_before_laughing
#before_laughing
end
end
class Lol < Lmao
before_laughing :downcase_please
def downcase_please
#str.downcase!
end
end
Pretty awesome.
There are two different instance variables called #before_laughing in your code: one is an instance variable of instances of the Lmao class, which gets initialized to [] (i.e. an empty Array) in Lmao's initialize instance methods and gets read in Lmao's laughing instance method. However, since the only place this instance variable gets written to is the initializer, it will always be an empty Array.
The other instance variable is an instance variable of the Lol class object itself, which gets set to the Array [:downcase_please] inside of the before_laughing method. This one, however, never gets read.
Related
In Rails we can define a class like:
class Test < ActiveRecord::Base
before_initialize :method
end
and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.
Here's what I have so far:
class LameAR
def self.before_initialize(*args, &block)
# somehow store the symbols or block to be called on init
end
def new(*args)
## Call methods/blocks here
super(*args)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like #before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.
How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)
For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)
The quick version is:
module MyCallbacks
def self.included(klass)
klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
end
module ClassMethods
def initialize_callbacks
#callbacks ||= []
end
def before_initialize(&block)
initialize_callbacks << block
end
end
def initialize(*)
self.class.initialize_callbacks.each do |callback|
instance_eval(&callback)
end
super
end
end
class Tester
include MyCallbacks
before_initialize { puts "hello world" }
end
Tester.new
Left to the reader:
arguments
calling methods by name
inheritance
callbacks aborting a call and supplying the return value
"around" callbacks that wrap the original invocation
conditional callbacks (:if / :unless)
subclasses selectively overriding/skipping callbacks
inserting new callbacks elsewhere in the sequence
... but eliding all of those is what [hopefully] makes this implementation more approachable.
One way would be by overriding Class#new:
class LameAR
def self.before_initialize(*symbols_or_callables, &block)
#before_init_methods ||= []
#before_init_methods.concat(symbols_or_callables)
#before_init_methods << block if block
nil
end
def self.new(*args, &block)
obj = allocate
#before_init_methods.each do |symbol_or_callable|
if symbol_or_callable.is_a?(Symbol)
obj.public_send(symbol_or_callable)
else
symbol_or_callable.(obj)
end
end
obj.__send__(:initialize, *args, &block)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).
I think I'm going a bit crazy when trying to understand instance variables in Ruby. My only aim here is to make sure that every object created for a given class has a variable with a predetermined value without writing an initialize method for that class. Something like:
class Test
#my = []
attr_accessor :my
end
t = Test.new
t.my # I want [] but this shows nil
Is it possible to achieve this without touching initialize ? Thanks.
EDIT: To clarify, I'm writing some piece of code which will be executed similar to attr_accessor in the sense that it'll add an instance variable to the class in which it is executed. If I write my own initialize, I will end up clobbering the one written by the user.
What you are doing is defining an instance variable on the class level (Since classes are instances of the Class class, this works just fine).
And no, there is no way around initialize.
Edit: You have a little misconception in your edit. attr_accessor doesn't add an instance variable to the class. What it does, literally, is this (using your example of my):
def my; #my; end
def my=(value); #my = value; end
It doesn't actively create/initialize any instance variable, it just defines two methods. And you could very well write your own class method that does similar things, by using define_method.
Edit 2:
To further illustrate how one would write such a method:
class Module
def array_attr_accessor(name)
define_method(name) do
if instance_variable_defined?("##{name}")
instance_variable_get("##{name}")
else
instance_variable_set("##{name}", [])
end
end
define_method("#{name}=") do |val|
instance_variable_set("##{name}", val)
end
end
end
class Test
array_attr_accessor :my
end
t = Test.new
t.my # => []
t.my = [1,2,3]
t.my # => [1, 2, 3]
# as instance variable without initialize
class Test1
def my; #my ||= [] end
attr_writer :my
end
t = Test1.new
t.my
# as class instance variable
class Test2
#my = []
class << self; attr_accessor :my end
end
Test2.my
I don't think it is, why are you so hesitant to just write a quick initialize method?
module EventSubscriber
def method_missing(method_name, *args)
if method_name[/^subscribe_to_(.*)/]
class << self
define_method(method_name) do |*arguments|
# ....
end
end
send(method_name, *args)
else
super
end
end
end
EventSubscriber is a module that gets mixed into classes to subscribe to events. If a method called subscribe_to_* gets called, it gets defined (to avoid method_missing overhead again) and then called. How can I bring that variable into scope?
The problem is inside of the singleton class, method_name does not seem to be accessible. Ruby complains about an "undefined local variable or method".
I know I could do this using self.class.send(:define_method ..), but I'd rather not unless I have to. I prefer this format.
What about using:
module EventSubscriber
extend self
def method_missing(method_name, *args)
if method_name[/^subscribe_to_(.*)/]
class << self
define_method(method_name) do |*arguments|
# ....
end
end
send(method_name, *args)
else
super
end
end
def singleton(obj)
class << obj; self; end
end
end
Your current problem is that you are using the class << object syntax for accessing the singleton class and trying to access the method_name variable. Any class statement changes the scope; method_name hence does not exist there if it was not defined there.
I am still unsure of what exactly you are trying to accomplish, but here is my attempt to fix your problem:
module EventSubscriber
def method_missing(method_name, *args)
if method_name =~ /^subscribe_to_(.+)/
class << self
self
end.define_method(method_name) do |*arguments|
# ....
end
send method_name, *args
else
super
end
end
end
Note that I also changed the regular expression to something easier to read.
How can a class method (inside a module) update an instance variable? Consider the code bellow:
module Test
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def update_instance_variable
#temp = "It won't work, bc we are calling this on the class, not on the instance."
puts "How can I update the instance variable from here??"
end
end
end
class MyClass
include Test
attr_accessor :temp
update_instance_variable
end
m = MyClass.new # => How can I update the instance variable from here??
puts m.temp # => nil
You'd have to pass your object instance to the class method as a parameter, and then return the updated object from the method.
That does nto quite make sense.
You use the initialize method to set default values.
class MyClass
attr_accessor :temp
def initialize
#temp = "initial value"
end
end
The initialize method is automatically run for you when you create a new object.
When your class declaration is run, there are no, and cannot be any, instances of the class yet.
If you want to be able to change the default values later you can do something like this:
class MyClass
attr_accessor :temp
##default_temp = "initial value"
def initialize
#temp = ##default_temp
end
def self.update_temp_default value
##default_temp = value
end
end
a = MyClass.new
puts a.temp
MyClass.update_temp_default "hej"
b = MyClass.new
puts b.temp
prints
initial value
hej
If you also want that to change already created instances' variables you need additional magic. Please explain exactly what you wish to accomplish. You are probably doing it wrong :)