Undefined method even after method_missing handling - ruby

I'm studying Ruby and try to implement method_missing, but it's not working. For example, I want to print method name after find_ but ruby raises "undefined method 'find_hello'" when i call it on Book instance.
TEST_05.RB
module Searchable
def self.method_missing(m, *args)
method = m.to_s
if method.start_with?("find_")
attr = method[5..-1]
puts attr
else
super
end
end
end
class Book
include Searchable
BOOKS = []
attr_accessor :author, :title, :year
def initialize(name = "Undefined", author = "Undefined", year = 1970)
#name = name
#author = author
#year = year
end
end
book = Book.new
book.find_hello

You are calling method on object which looks for instance_level method. So you need to define instance_level method_missing method:
module Searchable
def method_missing(m, *args)
method = m.to_s
if method.start_with?("find_")
attr = method[5..-1]
puts attr
else
super
end
end
end
class Book
include Searchable
BOOKS = []
attr_accessor :author, :title, :year
def initialize(name = "Undefined", author = "Undefined", year = 1970)
#name = name
#author = author
#year = year
end
end
book = Book.new
book.find_hello #=> hello
When you use self with method definition. It is defined as class level method. In your case Book.find_hello will output hello.

You've defined method_missing as a class method on Searchable, but you're trying to invoke it as an instance method. To invoke the method as it is, run it against the class:
Book.find_hello
If your intention is to find something from the entire collection of books, this is the canonical way it's done. ActiveRecord uses this approach.
You could similarly have a find_* instance method that would search the current book instance for something. If that's your intention, then change def self.method_missing to def method_missing.

Related

Undefined local variable error with simple program that adds items to array

class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names

Ruby - Class method that returns an instance & modifies that instance

Looking to set up a class method that can return an array of instances. I'm running into some trouble about the point where I try to modify the instances' variables NoMethodError: undefined method 'name=' for #<Class:0x007fe65c8560c0>.
class User
attr_accessor :name
def self.sample_users
megan = self.class.new
megan.name = "Megan"
jack = self.class.new
jack.name = "Jack"
[megan, jack]
end
end
I feel like this should be possible in Ruby. Any guidance?
Use just new instead of self.class.new
class User
attr_accessor :name
def self.sample_users
megan = new
megan.name = "Megan"
jack = new
jack.name = "Jack"
[megan, jack]
end
end
The value of self in this context is User (sample_users is a class method); so self.class is going to return Class.
I think you just want self.new.

Trying to wrap my head around setting instance variables in Ruby

I'm learning Ruby by reading Programming Ruby, the Pragmatic Programmers Guide. I really like how terse the syntax is.
I can't understand the role of the = in setter method names:
def price=(new_price)
#price = new_price
end
How is that function definition any different than this:
def price(new_price)
What difference does = make? The book says it makes direct assignment possible. But it's already possible with a normal setter method that does not have a =... ?
Here's the rest of the class:
class BookInStock
attr_reader :isbn
attr_accessor :price
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
book.price = book.price * 0.75
It gives you 'syntactical sugar' to write code as follows:
class Book
price=(new_price)
#price = new_price
# do something else
end
end
book = Book.new
book.price = 1
This code will be translated to
book.price=(1)
Actually attr_writer and attr_accessor methods generate setter (price=) methods for your class (attr_reader and attr_accessor generates getter methods as well). So your BookInStock class is similar to:
class BookInStock
def isbn val
#isbn = val
end
def price val
#price
end
def price= val
#price = val
end
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
You need to write methods with = only if you are going to add some logic into it (like validation). In other cases just use attr_writer or attr_accessor.

Ruby - Calling a method from a child object

I'm new to Ruby and trying to determine how I can call a class from a child object. Something like the below; however when I try it, I get an error saying "undefined local variable or method `me'"
class my_object < Object
attr_accessor :me
def initialize(attributes ={})
end
def setvalue(passed_value)
#passed_value = passed_value.to_s
end
def search(passed_value)
#passed_value.include?(passed_value)
end
end
def getMe
me_too = my_object.new
me_too.me = "test"
me_too.me.search("test")
end
end
instance.class
will give you a reference to the class
This works:
But your code had multiple errors.
class MY
attr_accessor :me
def initialize(attributes ={})
end
def setvalue(passed_value)
passed_value = passed_value.to_s
end
def search(passed_value)
passed_value.include?(passed_value)
end
def getMe
me_too = MY.new
me_too.me = "test"
me_too.search("test")
end
end
my = MY.new
my.getMe
You don't need to explicity extend Object, everything extends Object in ruby.
Your class name needs to start with a capital letter.
class MyObject
attr_accessor :me
end
me_too = MyObject.new
me_too.me = "test"
in console
me_too => #<MyObject:0x106b2e420 #me="test">
Check out some introductory ruby tutorials maybe http://ruby.learncodethehardway.org/

How do I write a writer method for a class variable in Ruby?

I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?

Resources