Below is a program that implements a tree.
class Tree
attr_accessor :children, :node_name
def initialize(name_children=[])
#children = children
#node_name = name
end
def visit_all(&block)
visit &block
children.each {|c| c.visit_all &block}
end
def visit(&block)
block.call self
end
end
ruby_tree = Tree.new( "Ruby", [Tree.new("Reia"), Tree.new("MacRuby")] )
puts "Visiting a node"
ruby_tree.visit {|node| puts node.node_name}
puts
puts "visiting entire tree"
ruby_tree.visit_all {|node| puts node.node_name}
When I run this code it errors at this line
ruby_tree = Tree.new( "Ruby", [Tree.new("Reia"), Tree.new("MacRuby")] )
The error I'm receiving is
tree.rb:6:in `initialize': undefined local variable or method `name' for #<Tree:0x007f94020249f8 #children=nil> (NameError)
from tree.rb:19:in `new'
from tree.rb:19:in `<main>'
Any help would be awesome.
You have a typo in your initialize method accepts a single argument named name_children, but from the body of that method it looks like the underscore should have been a comma - name, children.
Here's the problem - line 6 #node_name = name. Where do you define that variable?
In your constructor, (initialize), why aren't you entering the variables children, and name as such instead of a children_name ? What it seems to me is that when you are trying to instantiate the class by creating the objects, when it goes to the initialize constructor, it does not find a name in there.
def initialize(name, children=[])
#children = children
#node_name = name
en
As you have define attr_accessor :name in your Model so there you have to define Children method in you model like as
def self.name
# here will be you code which you want
end
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 class called RubyCsvRow, which holds a row of a CSV file, in a hash. I am using method_missing to allow any column to be used as a function to return the value of the row at that column. However, I get a method_missing error when I run I attempt to use it.
I wasn't sure exactly what was happening, so I replaced the call to one with a call to class.
m = RubyCsv.new
m.each{|row| puts row.class}
I edited the method missing in RubyCsvRow so that I could see what happens when it prints and see the name of the missing method:
def self.method_missing(name, *args, &block)
puts "Called Method Missing"
puts name.to_s
end
The return only confused me more.
Called Method Missing
to_ary
RubyCsvRow
Called Method Missing
to_ary
RubyCsvRow
It calls method missing. I don't know why it prints name as to_ary, which when I searched I found this, but I am not sure when it is being implicitly converted or why.
I searched around and have looked at these links. The labels where why I thought they didn't fit.
I have my private variable defined as a :attr_accesssor
Mine is a method of a class and I am using it like one
I am calling my method after defining it
I am not sure about this one. I am already converting my symbol to_s, but I had trouble determining if this fit
Why I decided to format my each method in RubyCsv the way I did
class RubyCsvRow
attr_accessor :values
def initialize(start)
#values = start
end
def self.method_missing(name, *args, &block)
if #values.key?(name.to_s)
#values[name.to_s]
else
puts "No Column with name: ", name.to_s, " found!"
end
end
def to_s
self.inspect
end
end
r = RubyCsvRow.new({"one" => "dog", "two" => "cat" })
puts r.one
RubyCsvRow is used in RubyCsv's each, but I get the same error with just this code. I can post the RubyCsv code, but this is the minimum code to reproduce the error.
I get a NoMethodError for one instead of printing dog.
Try to use def method_missing instead of self. You call method on instance not class it self.
If you define method with self you define the method as class not instance. In your code you create new instance of RubyCsvRow Class and you need to define method_missing as instace method.
Modify code here:
class RubyCsvRow
attr_accessor :values
def initialize(start)
#values = start
end
# Define method missing on instance
def method_missing(name, *args, &block)
return #values[name.to_s] if #values[name.to_s]
# return my error message
"No Column with name: #{name} found!"
end
def to_s
inspect
end
end
r = RubyCsvRow.new({ "one" => "dog", "two" => "cat" })
puts r.one
# => dog
puts r.test
# => "No Column with name: test found!"
BTW: If you need the original error message use super in method_missing method
def method_missing(name, *args, &block)
return #values[name.to_s] if #values[name.to_s]
# call original method
super
end
I have an undefined method.
rb:31:in `add_song': undefined method `<<' for nil:NilClass (NoMethodError)
I do understand that #library[artist] gives nil, but I don't understand why and do not know how to fix it. Any advice?
module Promptable
def prompt(message = "What music would you like to add", symbol = ":>")
print message
print symbol
gets.chomp
end
end
class Library
attr_accessor :artist, :song
def initialize
#library = {}
end
def add_artist(artist)
#library[artist] = []
end
def add_song(song)
#library[artist] << song
end
def show
puts #library
end
end
class Artist
attr_accessor :name, :song
def initialize(artist)
#name = artist[:name]
#song = artist[:song]
end
def to_s
"#{name}, #{song}"
end
end
if __FILE__ == $PROGRAM_NAME
include Promptable
include Menu
my_library = Library.new
my_library.add_artist(Artist.new(:name => prompt("What it the artist name ?")))
my_library.add_song(Artist.new(:song => prompt("What is the song name ?")))
my_library.show
end
You're calling add_artist with one instance of Artist and add_song with another. When you look up the artist's list of songs in add_song with #library[artist] you're using a hash key (the second instance of Artist) which is not equivalent to the hash key under which you stored the list (the first instance of Artist), so you're not getting the list back, but nil.
To use two different instances of Artist as equivalent hash keys, you'll need to decide when two instances of Artist should be equal and implement eql? and hash appropriately.
I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end
I have the following class:
module APIWrapper
include HTTParty
BASE_URI = 'https://example.com/Api'
def self.const_missing(const_name)
anon_class = Class.new do
def self.method_missing method_name, *params
params = {
'Target' => const_name.to_s,
'Method' => method_name.to_s,
}
APIWrapper.call_get params
end
end
end
def self.call_get(params)
get(APIWrapper::BASE_URI, {:query => params})
end
def self.call_post(params)
post(APIWrapper::BASE_URI, params)
end
end
I want to be able to make a call to my wrapper like this:
APIWrapper::User::getAll
I'm getting a stack level too deep error:
1) Error:
test_User_getAll(APITest):
SystemStackError: stack level too deep
api_test.rb:16
What am I doing wrong?
After using the keyword def, a new scope is created, so the issue here is that the const_name variable is no longer in scope inside the body of the method_missing method.
You can keep the variable in scope by using blocks like so:
def self.const_missing(const_name)
anon_class = Class.new do
define_singleton_method(:method_missing) do |method_name, *params|
params = {
'Target' => const_name.to_s,
'Method' => method_name.to_s,
}
APIWrapper.call_get params
end
end
end
You might want to also set the constant to the anonymous class you just created:
anon_class = Class.new do
...
end
const_set const_name, anon_class
The problem is that const_name is recursively calling method_missing. When you pass a block to Class.new the block is evaluated in the scope of the class. (See the docs)
Methods do not see any local outside variables. In your case it is const_name, which is triggering method_missing recursively.
name = "Semyon"
def greet
puts "hello, #{name}!"
end
greet # undefined local variable or method ‘name’
You can name anonymous modules (and classes) in Ruby using const_set, and from there you can easily see the name. I'd also not recommend defining new methods for every class, this is what modules are for. Here is my short, self-contained example:
module Greeting
module Base
def method_missing(word)
Greeting.greet word, self.name.split("::").last
end
end
def self.greet(word, name)
puts "#{word}, #{name}!"
end
def self.const_missing(name)
const_set name, Module.new.extend(Base)
end
end
Greeting::Semyon.hello # hello, Semyon!