NameError: uninitialized constant Song ...Programming Ruby - ruby

trying to pick up ruby through this programming ruby site and i'm stuck on this syntax
class SongList
def initialize
#songs = Array.new
end
def append(aSong)
#songs.push(aSong)
self
end
def deleteFirst
#songs.shift
end
def deleteLast
#songs.pop
end
end
When i go to add a song...
list = SongList.new
list.append(Song.new('title1', 'artist1', 1))
I get this error message:
NameError: uninitialized constant Song ...Programming Ruby
I saw that i need to require the variable Song, but I'm not sure where to do it within the SongList class....

You can use Ruby Struct class :
A Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.
class SongList
def initialize
#songs = [] # use [] instead of Array.new
end
def append(aSong)
#songs.push(aSong)
self
end
def delete_first
#songs.shift
end
def delete_last
#songs.pop
end
end
Song = Struct.new(:song_name, :singer, :var)
list = SongList.new
list.append(Song.new('title1', 'artist1', 1))
# => #<SongList:0x9763870
# #songs=[#<struct Song song_name="title1", singer="artist1", var=1>]> var=1>]>

Related

Ruby method chaining with an Enumerable class

I'm attempting to adapt the method-chaining example cited in this posting (Method chaining and lazy evaluation in Ruby) to work with an object that implements the Enumerable class (Implement a custom Enumerable collection class in Ruby )
Coffee class:
class Coffee
attr_accessor :name
attr_accessor :strength
def initialize(name, strength)
#name = name
#strength = strength
end
def <=>(other_coffee)
self.strength <=> other_coffee.strength
end
def to_s
"<name: #{name}, strength: #{strength}>"
end
end
Criteria class:
class Criteria
def initialize(klass)
#klass = klass
end
def criteria
#criteria ||= {:conditions => {}}
end
# only show coffee w/ this strength
def strength(strength)
criteria[:strength] = strength
self
end
# if there are multiple coffees, choose the first n=limit
def limit(limit)
criteria[:limit] = limit
self
end
# allow collection enumeration
def each(&block)
#klass.collection.select { |c| c[:strength] == criteria[:strength] }.each(&block)
end
end
CoffeeShop class:
class CoffeeShop
include Enumerable
def self.collection
#collection=[]
#collection << Coffee.new("Laos", 10)
#collection << Coffee.new("Angkor", 7)
#collection << Coffee.new("Nescafe", 1)
end
def self.limit(*args)
Criteria.new(self).limit(*args)
end
def self.strength(*args)
Criteria.new(self).strength(*args)
end
end
When I run this code:
CoffeeShop.strength(10).each { |c| puts c.inspect }
I get an error:
criteria.rb:32:in block in each': undefined method '[]' for #<Coffee:0x007fd25c8ec520 #name="Laos", #strength=10>
I'm certain that I haven't defined the Criteria.each method correctly, but I'm not sure how to correct it. How do I correct this?
Moreover, the each method doesn't support the limit as currently written. Is there a better way to filter the array such that it is easier to support both the strength and limit?
Other coding suggestions are appreciated.
Your Coffee class defines method accessors for name and strength. For a single coffee object, you can thus get the attributes with
coffee.name
# => "Laos"
coffee.strength
# => 10
In your Criteria#each method, you try to access the attributes using the subscript operator, i.e. c[:strength] (with c being an Instance of Coffee in this case). Now, on your Coffee class, you have not implemented the subscript accessor which resulting in the NoMethodError you see there.
You could thus either adapt your Criteria#each method as follows:
def each(&block)
#klass.collection.select { |c| c.strength == criteria[:strength] }.each(&block)
end
or you could implement the subscript operators on your Coffee class:
class Coffee
attr_accessor :name
attr_accessor :strength
# ...
def [](key)
public_send(key)
end
def []=(key, value)
public_send(:"#{key}=", value)
end
end
Noe, as an addendum, you might want to extend your each method in any case. A common (and often implicitly expected) pattern is that methods like each return an Enumerator if no block was given. This allows patterns like CoffeeShop.strength(10).each.group_by(&:strength).
You can implement this b a simple on-liner in your method:
def each(&block)
return enum_for(__method__) unless block_given?
#klass.collection.select { |c| c.strength == criteria[:strength] }.each(&block)
end

Undefined method `<<' for nil:NilClass (NoMethodError)

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.

Avoiding initialize method in ruby

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

Ruby Mixin Undefined Method

So, I know there is a simple error, but I just can't seem to spot it. I'm using Modules/Mixins for the first time and any help would be much appreciated. I keep getting this error:
undefined method `this_is' for Value:Module (NoMethodError)
But it looks like the method is there...Here are is my module and classes...
module Value
def this_is
puts "#{self.players_hand} is the players hand"
end
end
require './value.rb'
class Player
include Value
attr_accessor :players_hand
def initialize
#players_hand = 0
end
def value_is
Value.this_is
end
end
require './player.rb'
class Game
def initialize
#player = Player.new
end
def start
puts #player.players_hand
puts #player.value_is
end
end
game = Game.new
game.start
When you include Value inside of the Player class, you are making the Value module's methods a part of the Player class, so the this_is method is not namespaced. Knowing that, we need to change this method:
def value_is
Value.this_is
end
To:
def value_is
this_is
end

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/

Resources