This is a follow up from Creating a class which inherits from another class in Ruby and m.one + m.two + m.three doesn't work. We are applying for App Academy which is a Learn to Code course for people with little or no experience. As a result, these questions are similar, but I felt the answers in the other two posts did not address an answer, but diverged to explaining tangential concepts (which I did utilize)
To follow up on the first two mentioned links, I am having a problem with inherited classes. I have the Musician class which is inheriting three variables from the Person class. My problem is that when I run the
m.first_name + " " + m.last_name + ": " + m.age.to_s
I get an error: person.rb:31: undefined method `+' for nil:NilClass (NoMethodError). I understand that this error has the answer, but I am not yet adept at understanding what this means.
One point to mention is that the query above, must be the way it is. I can not put Puts in front of it.
I would appreciate any suggestions as to why I am getting this statement regarding + being an undefined method.
Thanks!
edit: Thanks for the quick response. I didnt realize the coding community was so active! This is really amazing. I am going to edit the code to reflect the newest issues, so I dont keep getting the same suggestions.
I get an error: person.rb:31: undefined method `+' for nil:NilClass (NoMethodError). I understand that this error has the answer, but I am not yet adept at understanding what this means.
It means that one of the three variables is nil, i.e., it lacks a value:
m.first_name
m.last_name
This is occurring because you are returning the result of calling the puts function in all of your accessors. You need to return the variables themselves, not print them and return the result of the print function.
Also, since you are already using attr_reader you have get methods created for you already. That's the whole point of using attr_reader; it creates a function which returns an underlying instance variable for you, you simply need to initialize it.
for example, this:
class Foo
def bar
#bar
end
end
is equivalent to
class Foo
attr_reader :bar
end
Your accessors are returning the results of puts, not the instance variable's values.
First, you're already using attr_reader, which generates those methods for you.
Second, first_name, for example, should just return #first_name:
def first_name
#first_name
end
Related
So, I'm currently learning about metaprogramming in Ruby and I want to fully understand what is happening behind the scenes.
I followed a tutorial where I included some of the methods in my own small project, an importer for CSV files and I have difficulties to wrap my hand around one of the methods used.
I know that the define_method method in Ruby exists to create methods "on the fly", which is great. Now, in the tutorial the method initialize to instantiate an object from a class is defined with this method, so basically it looks like this:
class Foo
def self.define_initialize(attributes)
define_method(:initialize) do |*args|
attributes.zip(args) do |attribute, value|
instance_variable_set("##{attribute}", value)
end
end
end
end
Next, in an initializer of the other class first this method is called with Foo.define_initialize(attributes), where attributes are the header row from the CSV file like ["attr_1", "attr_2", ...], so the *args are not provided yet.
Then in the next step a loop loops over the the data:
#foos = data[1..-1].map do |d|
Foo.new(*d)
end
So here the *d get passed as the *args to the initialize method respectively to the block.
So, is it right that when Foo.define_initialize gets called, the method is just "built" for later calls to the class?
So I theoretically get a class which now has this method like:
def initialize(*args)
... do stuff
end
Because otherwise, it had to throw an exception like "missing arguments" or something - so, in other words, it just defines the method like the name implies.
I hope that I made my question clear enough, cause as a Rails developer coming from the "Rails magic" I would really like to understand what is happening behind the scenes in some cases :).
Thanks for any helpful reply!
Short answer, yes, long answer:
First, let's start explaining in a really (REALLY) simple way, how metaprogramming works on Ruby. In Ruby, the definition of anything is never close, that means that you can add, update, or delete the behavior of anything (really, almost anything) at any moment. So, if you want to add a method to Object class, you are allowed, same for delete or update.
In your example, you are doing nothing more than update or create the initialize method of a given class. Note that initialize is not mandatory, because ruby builds a default "blank" one for you if you didn't create one. You may think, "what happens if the initialize method already exist?" and the answer is "nothing". I mean, ruby is going to rewrite the initialize method again, and new Foo.new calls are going to call the new initialize.
I found some unexpected (to me) behavior in Ruby. Is this behavior a bug or a feature? I stumbled on this situation while writing my project.
To put it as succinctly as I can: if a class' name ends with ::File and that class inherits from another class, then the usual File class isn't used with "File", but instead the current class is used.
(Yes, that's as succinct as I could figure out how to make it.)
Consider the following code, which produces the [feature|bug]
class MyClass
end
class MyClass::File < MyClass
def check
File.exist?('./whatever.txt')
end
end
myfile = MyClass::File.new
myfile.check
That code produces the following error:
./dev.rb:8:in `check': undefined method `exist?' for MyClass::File:Class (NoMethodError)
Did you mean? exit
exit!
from ./dev.rb:13:in `<main>'
What's weird (to me, anyway) is that the error doesn't happen if MyClass::File doesn't inherit from MyClass. That is, if you remove the "< MyClass" part then there's no error.
Now, I can just hear some programmers muttering over their latte: you shouldn't end a class name with ::File, that's just bad form. Well, I really want my class called ::File because that's the type of object it represents. I just have this thing that I like to give classes meaningful names.
I found a work around by calling Object::File instead of just File:
Object::File.exist?('./whatever.txt')
I don't even know how I figured that out... I couldn't google anything about this [feature|bug] but I figured it out within 20 guesses. Got lucky, I guess.
So...
1) Is this a feature or a bug?
2) Did I use the right workaround?
In Ruby I'd like to have a message printed to console whenever a given variable is changed at any time during execution.
How should I approach this? Should I monkey patch the method for assigning values to variables?
I could only find this related question Hook to be called when a variable changes where an answer is suggesting to redefine #freeze but this approach has limitations. Also it doesn't look right.
Isn't there a better and more consistent solution?
Add your own getter/setter.
Example:
class Person
def name
#name
end
def name=(s)
#name=s
puts 'name has changed!'
end
end
This is not possible.
Neither the set_trace_func nor the TracePoint API support tracing variable assignments, and …
Should I monkey patch the method for assigning values to variables?
… such a method does not exist.
Ruby just doesn't consider variables to part of the object-oriented fabric of the program, I guess. Only objects and methods.
If we call caller method, we get something like:
prog.rb:3:in `a'
prog.rb:6:in `b'
prog.rb:9:in `c'
This is helpful for humans, but if I wanted to analyze the stack programmatically, not really, as two methods called :a may be entirely unrelated.
Is there any way/method to extract information about the receiver of the methods (like its class or object id) as well? For example:
prog.rb:3:in `Klass#a'
prog.rb:6:in `Modoole#b'
prog.rb:9:in `OtherKlass#c'
Formatting is only an example; this info might be an Array or anything.
I'm trying to emulate this with TracePoint, but forming a separate stack is a bad solution. Is there any Ruby way I missed in the docs?
There's an alternative to Kernel#caller named Kernel#caller_locations, that returns an array of Thread::Backtrace::Location objects. According to the manual, these should in theory be able to give you this information through the #label method.
Returns the label of this frame.
Usually consists of method, class, module, etc names with decoration.
After trying this out however, I need to question the term usually in the docs, because it seems to only return the method name still. Unless usually means it works for you, there seems to be no way of accomplishing this as of now.
Edit:
As per comment, one case that satisfies the condition of usually is when the method call is coming from within a Class or Module body:
class A
def trace
puts caller_locations.first.label
end
end
class B
A.new.trace
end
#=> <class:B>
module C
A.new.trace
end
#=> <module:C>
I am confused how to call a function defined in a class inside another function defined in that same class. Here is what I have done:
class Test
def TestFunc(obj)
puts obj
end
def Test.StatFun(obj)
puts obj
TestFunc(obj)
end
end
Test.StatFun([[5,2], [4,3]])
When I run this in cmd.exe, I get the following error:
ruby LawtonTest.rb 5 2 4 3 LawtonTest.rb:10:in StatFun': undefined
methodTestFunc' for Test:Class (NoMet hodError)
from LawtonTest.rb:14:in `'
I can't figure it out. Any help would be greatly appreciated.
You have to call it on the object. I think you need a good reference of oop in Ruby, take a look at http://zetcode.com/lang/rubytutorial/oop/. But anyway, the thing is, methods (which is what you declare with def) have to be called on an object, not like a global function. So if you want to use TestFunc, try this:
def Test.StatFun(obj)
puts obj
Test.new.TestFunc(obj)
end
The Test.new part is used to create an object, on which you can use the TestFunc method.
I can't see a definition for TestFunc anywhere. Have you defined it?
Also, the general Ruby convention is to use lowercase names for methods (eg test_func)
edit should really read the question before answering...
The problem here is that TestFunc is only defined for instances of the class Test, whereas when you call Test.StatFun, the object executing the code is the actual class Test. This means that it doesn't know what TestFunc is. One way to get around this is to create a new instance (because that is one thing classes do know how to do):
def Test.StatFun(obj)
puts obj
new.TestFunc(obj)
end