When trying to call the closePrice method on the stock object, nothing is being printed out. However, the currently commented code works. How can I iterate through the StockList array and call the closePrice method of my Stock objects?
class Stock
attr_reader :date, :open, :high, :low, :close, :adjClose, :volume
def initialize(date, open, high, low, close, adjClose, volume)
#date = date
#open = open
#high = high
#low = low
#close = close
#adjClose = adjClose
#volume = volume
end
def closePrice
"Close price: #{#close}"
end
end
class StockList < Array
def initialize()
#stockList = []
end
def addStock(stock)
#stockList.push(stock)
end
end
stocks = Array.new
stockList = StockList.new()
CSV.foreach(fileName) do |stock|
entry = Stock.new(stock[0], stock[1], stock[2], stock[3], stock[4], stock[5], stock[6])
stocks.push(entry)
stockList.addStock(entry)
end
# stocks.each do |stock|
# puts stock.closePrice
# end
stockList.each do |stock|
puts stock.closePrice
end
Could it be that there are missing links? Only beginning with Ruby. Thanks.
So the problem is in your implementation of StockList. You have extended the class array but are setting up a new instance variable #stockList. When you call addStock you are adding an instance to the #stockList variable. But when you call each on the StockList instance, it is not iterating over the StockList.
Conceivably, you could add a method each to StockList like:
def each(&block)
#stockList.each(&block)
end
and it should work.
But really I would recommend rethinking your data structures. StockList should really not extend Array.
Related
I am trying to add an attribute to an object after it's been created using a method from within the object Class. I'd like to put this code in the def set_sell_by and def get_sell_by methods, if this is possible. So, in the end I'd like to do apple.set_sell_by(10) and then get that value later by doing apple.get_sell_by to check if the item has 5 days or less left to sell it.
class Grocery_Inventory
attr_accessor :product, :store_buy, :quantity, :serial_number, :customer_buy
def initialize(product, store_buy, quantity, serial_number, customer_buy)
#product = product
#store_buy = store_buy
#quantity = quantity + 5
#serial_number = serial_number
#customer_buy = customer_buy
end
def get_product_name
p product
self
end
def get_cost_customer
p "$#{customer_buy}"
self
end
def get_product_quantity
p "You have #{quantity} #{product}"
self
end
def set_sell_by
#some code...
self
end
def get_sell_by
if sell_by < 5
p "You need to sell this item within five days."
self
else
p "Item doesn't currently need to be sold."
self
end
end
end
apples = Grocery_Inventory.new("apples", 1.00, 5, 123, 0.25)
apples.get_product_name
apples.get_cost_customer
apples.get_product_quantity
Ruby is very lax in this regard. Simply access a variable with #and if it doesn't exist it will be created.
def set_sell_by
#sell_by = value
self
end
I want to create a special settings class Settings. The class should be able to handle cases when a user types something like Settings.new.method_1.method_2.method_3 and it's translated to something like:
result = nil
if ConfigurationSettings['method_1'].present?
result = ConfigurationSettings['method_1']
if result['method_2'].present?
result = result['method_2']
...
end
return result
Of course, I'll make it more flexible later so it can have more than 2/3 "methods".
I guess this is the issue you are facing:
class Settings
def abc
puts "abc"
end
def xyz
puts "xyz"
end
end
s = Settings.new
s.abc
#abc
# => nil
s.xyz
#xyz
# => nil
s.abc.xyz
#abc
#NoMethodError: undefined method `xyz' for nil:NilClass
The issue here is s.abc is returning nil and xyz is called over nil. What you are trying to achieve is called Method Chaining. Now, xyz needs an Settings object. Simplest thing to do here is:
class Settings2
def abc
puts "abc"
self
end
def xyz
puts "xyz"
self
end
end
s2 = Settings2.new
s2.abc.xyz
#abc
#xyz
method_missing is available for your use and can be used to help you solve this problem. Coupling this with method chaining and you're good to go. For example:
class Settings
def method_missing(meth)
puts "Missing #{meth}"
self
end
def test
puts "Test"
self
end
end
a = Settings.new
a.test
a.test.b
a.b.test
The trouble with the other answers is all the methods return "self" so if you want to access a nested value...
final_value = Settings.new.method_1.method_2.method_3
You're just going to get the whole settings hash instead.
Try this instead...
class Settings
class SubSettings
def initialize(sub_setting)
#sub_setting = sub_setting
end
def method_missing(method, *arguments, &block)
if #sub_setting[method].is_a?(Hash)
SubSettings.new #sub_setting[method]
else
#sub_setting[method]
end
end
def answer
#sub_setting
end
end
def initialize
#settings = ConfigurationSettings
end
def method_missing(method, *arguments, &block)
SubSettings.new #settings[method]
end
end
ConfigurationSettings = {level1a: {level2a: {level3a: "hello", level3b: "goodbye"}, level2b: {level3b: "howdy"}}}
result = Settings.new.level1a.level2a.level3b
p result
=> "goodbye"
What this does is take the initial method and takes the associated sub-hash of the ConfigurationSettings hash and stored it into a new object of class SubSettings. It applies the next method and if the result is another sub-hash it iterates to create another SubSettings, etc. It only returns the actual result when it no longer sees hashes.
I want to load a file, split its content into arrays, and have the class apply to the content.
class Student
def initialize( name, grade )
#name = name
#grade = grade
#grade = #grade.to_i
#newgrade = #grade*1.45
end
def show()
return "#{#name} ,#{#grade} , #{#newgrade}"
end
end
# Opening the file into an array
arr = File.open("exam_results.txt", "r+")
allStudents = Array.new
for a in arr
b = a.split(",")
name = b[0]
score = b[1]
allStudents << Student.new(#name, #grade)
end
for i in Student
puts show()
end
I'm getting
undefined method 'each' for Student:Class (NoMethodError)
on line 28, which is the puts show() line. Any clues on how I can get further on this?
I think you have a typo there (among other things). You're doing this:
for i in Student
puts show()
end
Clearly, the Student class is not a collection which you can iterate. I think, what you meant to write is this:
allStudents.each do |student|
puts student.show
end
That is because you are trying to iterate over "Student" class and not Array/Collection object at for i in Student
Basically you are doing it wrong. It rather should be something like
allStudents.each do |student|
puts student.show
end
Here's what I'd like to do:
class Directory
def doSomething
end
def subs
# => an array of Directory objects
end
def recursively (method)
self.subs.each do |sub|
sub.method
sub.recursively method
end
end
end
cd = Directory.new
cd.recursively 'doSomething'
# ...and extra points if theres a way to:
cd.recursively.doSomething
To put this into perspective I'm creating a small script that will make changes to files in a Directory as well as all its sub-directories. These sub-directories will just be extended Directory objects.
So is there a way to pass a method as a parameter of another method?
You could use Object#send, where method is a string or symbol representing the method name, as in your first example. Just change your #recursively to this:
def recursively(method)
subs.each do |sub|
sub.send method
sub.recursively method
end
end
UPDATE
For your "extra points" question, and picking up on megas' answer, here's a stab at an Enumerator-based approach. Drop this into your Directory:
def recursively
Enumerator.new do |y|
subs.each do |sub|
y.yield sub
sub.recursively.each do |e|
y.yield e
end
end
end
end
And call it like this:
cd.recursively.each { |d| d.doSomething }
Yes, you can do this -
class Directory
def doSomething
end
def subs
# => an array of Directory objects
end
def recursively (method)
self.subs.each do |sub|
sub.method.call
sub.recursively method
end
end
end
dir = Directory.new
ds = dir.method :doSomething
dir.recursively ds
I think here's should be specialized each method from Enumerable module. When you implement the each method, then Enumerable module will give a lot of handy methods like map, drop and so on.
class Directory
include Enumerable
def initialize
# here you should build #objects - a whole list of all objects in
# the current direcory and its subdirectories.
#objects = ....
end
def each
if block_given?
#objects.each { |e| yield(e) }
else
Enumerator.new(self, :each)
end
end
...
end
And then you can iterate all objects in elegant way:
#directory = Directory.new('start_directory')
#directory.each do |object|
puts object.size # this will prints the sizes for all objects in directory
object.do_some_job # this will call method on object for all your objects
end
This one will give an array of sizes for all objects in directory
#directory.map { |object| object.size } #=> [435435,64545,23434,45645, ...]
Aditional example:
For example you need to get the list with indexes and sizes of all objects
#directory.each_with_index.map { |object, index| [index, object.size] }
#=> [ [0,43543], [1,33534], [2,34543564], [3,345435], ...]
See if this gets you headed in the right direction:
module Recursion
def recursively(&block)
output = block.call(self)
output.recursively(&block) unless output.nil?
end
end
class Number
include Recursion
def initialize(value)
#value = value
end
def next
#value > 0 ? Number.new(#value - 1) : nil
end
def print
puts #value
self.next
end
end
Number.new(10).recursively(&:print)
How would I create an attr_accessor to array?
for example
class MyClass
attr_accessor :my_attr_accessor
def initialize()
end
def add_new_value(new_array)
#my_attr_accessor += new_array
return #my_attr_accessor
end
end
my_class = MyClass.new
my_class.my_attr_accessor = 1
my_class.my_attr_accessor[1] = 2
my_class.my_attr_accessor.push = 3
my_class.add_new_value(5)
my_class.my_attr_accessor
=> [1, 2, 3, 5]
Just use an instance variable that points to an array and make an accessor from that instance variable.
Inside your class include something like this:
attr_accessor :my_attr_accessor
def initialize
#my_attr_accessor = []
end
Note that usingattr_accessor will allow you to change the value of the variable. If you want to ensure that the array stays, use attr_reader in place of attr_accessor. You will still be able to access and set array elements and perform operations on the array but you won't be able to replace it with a new value and using += for concatenation will not work.
If you are OK with the Array always existing, #david4dev's answer is good. If you only want the array to pop into existence on the first usage, and never want the user to be able to replace it with a new array (via assignment):
class MyClass
def my_attr_accessor
#my_attr_accessor ||= []
end
def add_new_value( value )
my_attr_accessor << value
end
def add_new_values( values_array )
my_attr_accessor.concat values_array
end
end
The user could still call my_class.my_attr_accessor.replace( [] ) to wipe it out.