With the following code, in what way can I access #arr from Child?
class Parent
class << self
def create_singleton_variable
#arr = [1,2,3]
end
def arr
#arr
end
end
end
class Child < Parent
def get
puts self.arr
end
def self.get
puts self.arr
end
end
p "class method call #{Child.get}"
#=> ➜ ruby child.rb
#=> "class method call "
c = Child.new
p "instance call #{c.get}"
#=> ➜ ruby child.rb
#=> Traceback (most recent call last):
#=> 1: from child.rb:24:in `<main>'
#=> child.rb:15:in `get': undefined method `arr' for #<Child:0x00007fe0eb02e7d8> (NoMethodError)
I've tried many other ways as well, but don't feel the need to post them here.
edit to the question, since it appears I do need a bit more context:
I'm attempting to prepend a module into the Thor framework. I want to then access this bit of code
module ThorExtensions
module Thor
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "Start Completion"
p self
p self.superclass
p self.class.superclass.subcommands
puts "End Completion"
end
end
end
end
end
results in
Start Completion
Debug
Thor
bundler: failed to load command: exe/pt (exe/pt)
NoMethodError: undefined method `subcommands' for Module:Class
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/thor_extensions/completion_generation.rb:13:in `completion'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:24:in `<class:Debug>'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:4:in `<top (required)>'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `require'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `<top (required)>'
exe/pt:13:in `require'
exe/pt:13:in `<top (required)>'
which of course is not what I want. It appears that maybe my issue is with prepending?
Edit 2
I seem to have done a terrible job of explaining my issue with prepending. Here is a fully working example showing my issue. I believe this is due to how prepending something to a class essentially creates another Class in the call stack that is called first. My hope is that I'm actually still able to access this method somehow.
class Parent
class << self
def create_singleton_variable
#arr = [1,2,3]
puts "arr is initialized #{#arr}"
end
# ... lots of code here.
def arr
puts "arr is #{#arr.inspect}"
#arr
end
end
end
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "self.superclass.arr == #{self.superclass.arr.inspect}" # unable to access superclass arr
puts "self.class.superclass.arr == #{self.class.superclass.arr}" # likewise, unable to access Child's metaclass superclass
rescue Exception => e
# do nothing, this is just so you can see arr is actually initialized in the context of the Child
p e
end
end
end
Parent.prepend CompletionGeneration
class Child < Parent
create_singleton_variable
completion
arr
end
Child.new
results in the output
➜ ruby child.rb
arr is initialized [1, 2, 3]
arr is nil
self.superclass.arr == nil
#<NoMethodError: undefined method `arr' for Module:Class>
arr is [1, 2, 3]
This code should be simply copy and pastable as is.
Here is your code, slightly modified.
class Parent
def self.create_singleton_variable
#arr = [1,2,3]
end
def self.arr
puts "self = #{self} in the getter for #arr"
#arr
end
end
class Child < Parent
def get
puts self.arr
end
def self.get
puts self.arr
end
end
I have written Parent in the more conventional way. Except for the addition of the puts statement, it is equivalent to that contained in the question.
First, a head-slapper: Kernel#puts-anything returns nil. You need to remove puts from both methods:
class Child < Parent
def get
self.arr
end
def self.get
self.arr
end
end
Parent.create_singleton_variable
#=> [1, 2, 3]
Child.get.nil?
self = Child in the getter for #arr
#=> true
We see that within the getter arr, invoked by Child's class method get, self equals Child, so the method looks for a class instance variable #arr of Child not of Parent. As no such instance variable has been initialized, nil is returned.
You need the following.
class Parent
class << self
def create_singleton_variable
#arr = [1,2,3]
end
def arr
puts "self = #{self} in the getter for #arr"
#arr
end
end
end
class Child < Parent
def get
self.class.superclass.arr
end
def self.get
superclass.arr
end
end
The crucial difference to that given in the question is that Class#superclass changes the scope (i.e., self) to Parent.
We see the desired result is obtained.
Child.get
self = Parent in the getter for #arr
#=> [1, 2, 3]
Child.new.class.superclass.arr
self = Parent in the getter for #arr
#=> [1, 2, 3]
A common misconception is that the Child class method defined def self.get; self.arr; end invokes the getter Parent::arr, and therefore returns the value of Parent's instance variable #arr. It is Child::arr that is invoked, however, that method having been inherited from Parent, and it is Child's class instance variable #arr that is being retrieved, a subtle, but important, distinction.
Edit 2
The first observation is that Parent can be written in the more conventional (and completely equivalent) way.
class Parent
def self.create_singleton_variable
#arr = [1,2,3]
puts "arr is initialized #{#arr}"
end
def self.arr
puts "arr is #{#arr.inspect}"
#arr
end
end
Regardless of how its written, self will equal Parent when either class method is involked on parent. The first, therefore, will create the class instance variables #arr.
Parent.methods(false)
#=> [:create_singleton_variable, :arr]
Parent.instance_variables
#=> []
Parent.ancestors
#=> [Parent, Object, Kernel, BasicObject]
Now let's create a class variable for Parent.
Parent.create_singleton_variable
# arr is initialized [1, 2, 3]
Parent.instance_variables
#=> [:#arr]
Now let me change the value of #arr.
Parent.instance_variable_set(:#arr, ['dog', 'cat'])
#=> ["dog", "cat"]
Parent.arr
# arr is ["dog", "cat"]
#=> ["dog", "cat"]
Next, create the class Child, but do not yet prepend the module.
class Child < Parent
create_singleton_variable
arr
end
arr is initialized [1, 2, 3]
arr is [1, 2, 3]
Child.ancestors
#=> [Child, Parent, Object, Kernel, BasicObject]
Child.instance_variables
#=> [:#arr]
Child.instance_variable_get(:#arr)
#=> [1, 2, 3]
There are no surprises. Next load the module.
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "self=#{self}"
puts "superclass=#{superclass}"
puts "self.class=#{self.class}"
puts "self.class.superclass == #{self.class.superclass}"
puts "superclass.arr == #{superclass.arr.inspect}"
puts "self.class.superclass.arr == #{self.class.superclass.arr}"
rescue Exception => e
# do nothing, this is just so you can see arr is actually
# initialized in the context of the Child
puts "Exception => e=#{e}"
end
end
end
(Note self. is not needed in "superclass.arr == #{superclass.arr.inspect}") Now prepend this module to Parent.
Parent.prepend CompletionGeneration
Parent.ancestors
#=> [CompletionGeneration, Parent, Object, Kernel, BasicObject]
Parent.methods.include?(:completion)
#=> true
Child.ancestors
#=> [Child, CompletionGeneration, Parent, Object, Kernel, BasicObject]
Child.methods.include?(:completion)
#=> true
The callback modules method CompletionGeneration::prepended is fired with base equal to Parent, causing Parent's singleton class to prepend ClassMethods, thereby adding the class method Parent::completion. Since Parent did not previously have a method by that name using prepend or include would have the same effect. Further, instead of Parent.singleton_class.include ClassMethods, one could have used the included(base) callback instead, and executed Parent.extend ClassMethods. Perhaps prepend is being used here for a general case where Parent may have a class method by that name.1
Now execute the following.
Child.completion
self=Child
superclass=Parent
self.class=Class
self.class.superclass == Module
arr is ["dog", "cat"]
superclass.arr == ["dog", "cat"]
Exception => e=undefined method `arr' for Module:Class
The exception was raised when
puts "self.class.superclass.arr == #{self.class.superclass.arr}"
was being executed. As that amounts to
puts "self.class.superclass.arr == #{Module.arr}"
but of course Module has no module method arr.
1 In view of Child.ancestors, prepending Parent with the module only causes Parent's children to include (rather than prepend) the module; that is, if a child already has a method completion before the prepending, that method will not be preempted by the the module's method by the same name.
I am trying to access class methods within a define_singleton_method block, but it doesn't seem to be working.
Here is an example.
class Test
attr_accessor :tags
def initialize
#tags = []
#tags.define_singleton_method(:<<) do |val|
val = tosymbol(val)
push(val)
end
end
def tosymbol(value)
value = value.to_s
value = value.gsub!(/\s+/,'_') || value
value = value.downcase! || value
return value.to_sym
end
end
But when I use it I get an error.
test = Test.new
test.tags<<"Hello World"
NoMethodError: undefined method `tosymbol' for []:Array
from /home/joebloggs/GitHub/repo/file.rb:183:in `block in initialize'
from (irb):9
from /home/joebloggs/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
I tried changing val = tosymbol(val) to val = Test::tosymbol(val) but that didn't work either, I get undefined method 'tosymbol' for Test:Class
I could re-write what tosymbol is doing, but it wouldn't be very DRY. Where am I going wrong? Thanks.
Where am I going wrong?
You're (re)defining a << method for instance of Array class, not Test class.
While doing so you are trying to access tosymbol method, that is not defined in Array class, but in Test class.
What you want, probably (read judging by your code sample), is to define << method for instances of Test class:
def initialize
#tags = []
end
def <<(val)
tags << tosymbol(val)
end
test = Test.new
test << "Hello World"
#=> [:hello_world]
EDIT
To make your example to work you just need to assign the instance to a variable and call the tosymbol method with correct receiver:
def initialize
#tags = []
test = self # <============
#tags.define_singleton_method(:<<) do |val|
val = test.tosymbol(val)
push(val)
end
end
Now:
test.tags << 'Hello World'
#=> [:hello_world]
I have this class:
class Game
attr_accessor :player_fleet, :opponent_fleet
#player_fleet = []
#opponent_fleet = []
...
end
and create an instance like this:
my_game = Game.new
then use it like this:
my_game.opponent_fleet << opponent
which gives me this error:
undefined method `<<' for nil:NilClass (NoMethodError)
Why can't I treat an array like this? Do I have to create a method to push objects into the array?
You initialize #opponent_fleet at class level, so it's an instance variable of the class, not of the generated objects. Remember that in Ruby, even classes are objects :)
irb(main):001:0> class Game
irb(main):002:1> #foo = 3
irb(main):003:1> end
irb(main):004:0> Game.instance_eval { #foo }
=> 3
irb(main):005:0> Game.new.instance_eval { #foo }
=> nil
You want to initialize it in a constructor instead:
class Game
attr_accessor :player_fleet, :opponent_fleet
def initialize
#player_fleet = []
#opponent_fleet = []
end
end
I want to define a set of methods that can be added to a class (C in the example) using a Mixin. These methods can be defined by any class that inherits from another class (A in the example) and should be able to call methods in the receiver instance (C instance).
I have this snippet of code:
M = Module.new
class A
class << self
def init(method)
if block_given?
M.send(:define_method, method) do
instance_exec &Proc.new
end
else
block = self.method(method).to_proc
M.send(:define_method, method) do
yield block.call
end
end
end
end
end
class B < A
init(:foo) do
"foo+".concat(c_method)
end
def self.bar
"bar+".concat(c_method)
end
init(:bar)
end
C = Class.new do
def c_method
"c_method"
end
end
c = C.new
c.extend(M)
puts c.foo
puts c.bar
Adding methods using blocks works, but last line fails :(
foo+c_method
test.rb:28:in `bar': undefined local variable or method `c_method' for B:Class (NameError)
from test.rb:15:in `call'
from test.rb:15:in `block in init'
from test.rb:46:in `<main>'
What I'm doing wrong? Or this makes no sense?
Thanks
Juan
When you prepare instance_exec &Proc.new inside if statement, this statement is executed within instance of C class as context. You can verify this by adding puts self inside block for init(:foo).
On the other hand, when you call yield block.call you yield thread execution into context of B class object (not to instance of this class, of course :) ). This place of your code doesn't know anything about C::c_method and this is cause of error.
It seems that what I'm trying to do is to unbind the method :bar from B and bind to C, what it's not allowed. You can find more info in this great post
M = Module.new
class A
class << self
def init(method)
if block_given?
M.send(:define_method, method) do
instance_exec &Proc.new
end
else
block = self.method(method).unbind
M.send(:define_method, method) do
m = block.bind(self)
puts m
end
end
end
end
end
class B < A
init(:foo) do
"foo+".concat(c_method)
end
def self.bar
"bar+".concat(c_method)
end
init(:bar)
end
C = Class.new do
def c_method
"c_method"
end
end
c = C.new
c.extend(M)
puts c.foo
puts c.bar
foo+c_method
test.rb:16:in `bind': singleton method called for a different object (TypeError)
from test.rb:16:in `block in init'
from test.rb:48:in `<main>'
I'm trying to learn ruby more in depth before I move on to rails dev, but I'm having some issues learning classes. I can't seem to understand why the following doesn't work.
#point.rb
class Point
attr_accessor :x, :y
def initialize(p = [0,0])
#x = p[0]
#y = p[1]
end
end
#shape.rb
require_relative 'point.rb'
class Shape
attr_accessor :points
def initialize *the_points
for p in the_points
#points.append Point.new(p)
end
end
end
s = Shape.new([3,2])
puts s.points
When I call the function I get a no method error for NilClass, which I'm assuming is referring to #point.append.
First, try this:
def initialize *the_points
#points = []
for p in the_points
#points << Point.new(p)
end
end
You get NilClass error because #points instance variable is Nil, and NilClass, which does not have append() method.
Better than creating an array and populating it in a loop would be to initialize it like so:
class Shape
attr_accessor :points
def initialize *the_points
#points = the_points.map{ |p| Point.new(p) }
end
end
If you had warnings on (ruby -w or $VERBOSE = true), it'd warn you that #points didn't exist.
See some other debugging tips in How do I debug Ruby scripts?
You need to initialize #points to be a new array. It starts off as nil.
def initialize *the_points
#points = [];
for p in the_points
#points.append Point.new(p)
end
end