I'm trying to create a class with some instance variables that can be linked to and manipulated by a separate class. Here's my code:
class Product
attr_accessor :name, :quantity
def initialize(name, quantity)
#name = name
#quantity = quantity
end
end
class Production
def initialize(factory)
#factory = factory
end
def create
"#{#name}: #{#factory}"
end
end
Basically, I'd like to be able to use attributes from an instance of the Product class in the Production class. So if A = Product.new('widget', 100), I want to be able to call A.name as a variable in an instance of Production. To test this I have:
a = Product.new('widget', 100)
b = Production.new('Building 1')
puts b.create('Building 1')
However, it's returning the following:
: Building 1
instead of
widget: Building 1
Any ideas as to what's going wrong? Ultimately I'd also like to add, subtract, etc. from the quantity. I'm hoping that if I figure out how to link the name property I can do the same for the quantity pretty easily.
First of all, you want to produce a Product during a Production, so the Production must know about the Product. You can for example pass the product as an argument to create:
class Production
def initialize(factory)
#factory = factory
end
def create(product)
"#{product.name}: #{#factory}"
end
end
product = Product.new('Thingy', 123)
production = Production.new('ACME')
puts production.create(product)
#=> "Thingy: ACME"
or you could store it in an instance variable if you need it across the entire Production class:
class Production
def initialize(factory, product)
#factory = factory
#product = product
end
def create
"#{#product.name}: #{#factory}"
end
end
product = Product.new('Thingy', 123)
production = Production.new('ACME', product)
puts production.create
#=> "Thingy: ACME"
Related
So, I'm trying to initialize multiple objects and add them to a list. What I'm wanting to happen is by running Market.new, I want every item from the api added as an object. Below is the code I thought might work. But, it's adding the same object to the list 100x. Is there a way to accomplish this?
def initialize
data = JSON.parse(open(BASE_URL + "markets?vs_currency=usd").read)
i = 0
# looping until we hit the end of the list. adding them all as objects.
while i < data.length
#id = data[i]["id"].to_s
#name = data[i]["name"].to_s
#symbol = data[i]["symbol"].to_s
#price = data[i]["current_price"].to_s
#price_movement_24h = data[i]["price_change_percentage_24h"].to_s
#market_cap = data[i]["market_cap"].to_s
##market << self
i += 1
end
end
This gives me the same object added to the ##market list 100x.
=> [#<Market:0x0000561b3f853f30
#id="iostoken",
#market_cap="43969067",
#name="IOST",
#price="0.0036523",
#price_movement_24h="-0.76702",
#symbol="iost">,
#<Market:0x0000561b3f853f30
#id="iostoken",
#market_cap="43969067",
#name="IOST",
#price="0.0036523",
#price_movement_24h="-0.76702",
#symbol="iost">,
I want to start by saying this is very strange Ruby code and not something you'd typically do. That's not meant as an insult, just to say that Ruby devs tend to follow the same or similar guidelines on structuring objects and this chunk of code feels like it's ported from another language.
The issue you're seeing is due to the fact that within the initialize method you're not creating any new objects but instead updating the instance variables and pushing self into a class variable. self is referencing this instance directly which means the class variable array is filling up with references to the same object. If you're adamant on keeping the code the same then you should instead push a duplicate of your object after you've updated the instance variables.
##market << self.dup
This creates a duplicate object that has a different memory address and reference.
If you're looking to write more idiomatic code you'd want to use multiple objects and not rely on class variables at all. If you're not interpolating a variable in a string use single quotes instead of double quotes. Keep object methods simple and focused on specific tasks. These are just a few things Ruby developers consider when writing code, but find what works best for you.
Take something like this for instance:
class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap
def initialize(data = {})
#id = data['id'].to_s
#name = data['name'].to_s
#symbol = data['symbol'].to_s
#price = data['current_price'].to_s
#price_movement_24h = data['price_change_percentage_24h'].to_s
#market_cap = data['market_cap'].to_s
end
end
class ImportService
def self.from_api(url)
response = JSON.parse(open(url).read) || []
response.map { |data| Market.new(data) }
end
end
You could then call this as such:
#market_data = ImportService.from_api(BASE_URL + 'markets?vs_currency=usd')
When adding self to the ##market, you should change the code to this
##market << self.dup
However, I don't think it's a good practice to use a class variable here and add self to init an array of object. Instead, you should create a new class (for example MarketImporter)
class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap
def initialize(data = {})
#id = data["id"].to_s
#name = data["name"].to_s
#symbol = data["symbol"].to_s
#price = data["current_price"].to_s
#price_movement_24h = data["price_change_percentage_24h"].to_s
#market_cap = data["market_cap"].to_s
end
end
class MarketImporter
attr_accessor :markets
def initialize
data = JSON.parse(open(BASE_URL + "markets?vs_currency=usd").read)
#markets = data.collect { |item| Market.new(item) }
end
end
Then you can init the collection by
MarketImporter.new
I have 2 classes, When initializing the 'Shop' class I am giving the class a currency. How can I have "#checkout_currency" from my second class adopt the '#currency' value from the first class? #checkout_currency currently equals nil and I want it to equal "USD"
Here are my classes.
First..
require_relative 'convert'
class Shop
DEFAULT_CURRENCY = "USD"
def initialize(currency = DEFAULT_CURRENCY,
convert = Convert.new)
#currency = currency
#convert = convert
end
def self.currency
#currency
end
end
Second..
require_relative 'shop'
class Convert
attr_reader :checkout_currency
def initialize(checkout_currency = Shop.currency)
#checkout_currency = checkout_currency
end
end
Yes, defining a method with self will make it a method on the class rather than a method on any instance of the class. But #currency is an instance variable – it’s different for every instance of Shop, and not defined for the Shop class itself. What if you had 3 Shop objects with different currencies?
The thing that is really wrong is your composition of objects – is Convert something that Shop constructs, or is it something that must be passed in fully formed? If you restructure to either of these, your problem goes away.
class Shop
def initialize(convert, currency=DEFAULT_CURRENCY)
#convert = convert
#currency = currency
end
end
convert = Convert.new(Shop.DEFAULT_CURRENCY)
shop = Shop.new(convert)
Or maybe:
class Shop
def initialize(currency=DEFAULT_CURRENCY, convert_class=Convert)
#convert = convert_class.new(currency)
#currency = currency
end
end
shop = Shop.new
I need to save all instances of an object in a global variable, so I can access that instances from another object. There is no need to pass them like parameteres.
In my solution I have a mixin with a method that puts the instance in a variable, also I used an open class technique to include that mixin in Object, so other objects use that method (and not only ONE class).
class Object
include Favourite
end
module Favourite
def favourite_it
#if the variable its not initialized:
#favourites.class == Array.class ? #favourites.push(self) :
#favourites = [].push(self)
end
def get_favourites
#favourites
end
end
#this class is only an example
class Dog
def initialize age
#age = age
end
end
class Dog_T
#all instances of this class will be saved in the variable
def initialize age
#age = age
favourite_it
end
end
class Handler
def do_something
#here I need to access the variable with all the instances of favourites to do something to them
end
end
And here is a simple test
handler = Handler.new
d1 = Dog_T.new(10)
d2 = Dog_T.new(12)
all_f = Handler.get_favourites
expect(all_f[0].age).to eq 10
expect(all_f[1].age).to eq 12
d3 = Dog_T.new(15)
all_f = Handler.get_favourites
expect(all_f[3].age).to eq 15
I tried to do this, but only each instance save itself in a different list (it makes sense because I'm not using global variables yet).
How can I do to have only one list, add the instances when are created and have the ability to empty and manipulate that list from Handler?
Ruby supports using a class variable in a Module. You also need a reader method for the Dog_T object to be able to access the instance variable. Since Favourite doesn't know anything about the object, you will want to use respond_to? to guard against calling an non-existent method in the list. For example, if there were a Dog_R class that did not have a method age but did add itself, you would get a runtime error blindly calling the age method on members of the Favourite array.
module Favourite
##favourites = [] # you can use a class variable in module
def self.favourite_it(obj) # singleton method of the class
##favourites.push(obj)
end
def self.get_favourites # singleton method of the class, see below for usage example
##favourites
end
end
class Object
include Favourite
end
class Dog
def initialize age
#age = age
end
end
class Dog_T
attr_reader :age # you need a reader to able to access it
def initialize age
#age = age
Favourite.favourite_it(self)
end
end
d1 = Dog_T.new(10)
d2 = Dog_T.new(12)
all_f = Favourite.get_favourites
all_f.each do |obj|
puts "#{obj.class}: #{obj.age if obj.respond_to?(:age)}"
end
puts '-' * 20
d3 = Dog_T.new(15)
all_f = Favourite.get_favourites
all_f.each do |obj|
puts "#{obj.class}: #{obj.age if obj.respond_to?(:age)}"
end
The output of this program is:
Dog_T: 10
Dog_T: 12
--------------------
Dog_T: 10
Dog_T: 12
Dog_T: 15
I just started with ruby, and just started learning oop today, after making a class, I am trying to print to console yet I keep getting this error. Does anyone know what's wrong?
undefined method `set_brand_name=' for # (NoMethodError)
Here is the code causing this error:
class Laptop
def set_brand_name(brand_name)
#brand = brand_name
end
def get_brand_name
return #brand
end
def set_color(color)
#color = color
end
def get_color
return #color
end
def set_processor(processor)
#processor = processor
end
def get_processor
return #processor
end
def set_storage(hard_drive)
#storage = hard_drive
end
def get_storage
return #storage
end
def set_memory(ram)
#memory = ram
end
def get_memory
return #memory
end
end
my_laptop = Laptop.new
my_laptop.set_brand_name = "HP"
my_laptop.set_processor = 'i7-4700k'
my_laptop.set_memory = '16gb'
my_laptop.set_storage = '750gb'
my_laptop.set_color = 'Silver'
brand = my_laptop.get_brand_name
color = my_laptop.get_color
processor = my_laptop.get_processor
memory = my_laptop.get_memory
storage = my_laptop.get_storage
This should output the message:
"""The Laptop I want is an #{brand}, it has a #{processor},
#{memory} of ram, a #{storage}, and it #{color}!!!"""
What am I doing wrong?
The problem is that you are not calling the method names as you've defined them. You defined set_brand_name without an equal sign so use:
my_laptop.set_brand_name("HP")
I would simply the getters and setters like so:
class Laptop
def brand_name=(brand_name)
#brand_name = brand_name
end
def brand_name
#brand_name
end
end
Or even better:
class Laptop
attr_accessor :brand_name
end
Then you can use it the same way:
my_laptop = Laptop.new
my_laptop.brand_name = "HP"
puts my_laptop.brand_name # => "HP"
In line 45, you are calling the method set_brand_name=, but your Laptop class doesn't have a method with that name. You need to either call the method which you do have (set_brand_name), or rename the set_brand_name method to set_brand_name=.
Note that neither of those two is idiomatic, though. Idiomatically, the method should be named brand_name= (without the set_ prefix, the "setting" part is already implied by the = sign), and you shouldn't define it manually, but programmatically using the Module#attr_writer method.
Your entire code can be condensed to:
Laptop = Struct.new(:brand_name, :color, :processor, :storage, :memory)
my_laptop = Laptop.new('HP', 'Silver', 'i7-4700k', '750gb', '16gb')
brand = my_laptop.brand_name
color = my_laptop.color
processor = my_laptop.processor
memory = my_laptop.memory
storage = my_laptop.storage
puts "The Laptop I want is an #{brand}, it has a #{processor}, #{memory} of ram, a #{storage}, and it's #{color}!!!"
Your setter methods are defined incorrectly.
Here's your definition of the set_brand_name method:
def set_brand_name(brand_name)
#brand = brand_name
end
And here's how you're calling it:
my_laptop.set_brand_name = "HP"
You're calling the method incorrectly. If you'd like to keep your definition, you should be calling it like this:
my_laptop.set_brand_name("HP")
Or, if you'd like to use the equals sign, you should define your method like this:
def set_brand_name=(brand_name)
#brand = brand_name
end
Notice the equals in the method definition? You're required to use it when you want the setter to look like a regular assignment.
However, for most trivial cases you don't need to define getters and setters manually. You can just use attr_accessor on the class and pass it the properties you want to define. Here's what your class would look like with attr_accessor:
class Laptop
attr_accessor: :brand_name, :color, :processor, :storage, :memory
end
my_laptop = Laptop.new
my_laptop.brand_name = "HP"
my_laptop.processor = 'i7-4700k'
my_laptop.memory = '16gb'
my_laptop.storage = '750gb'
my_laptop.color = 'Silver'
brand = my_laptop.brand_name
color = my_laptop.color
processor = my_laptop.processor
memory = my_laptop.memory
storage = my_laptop.storage
puts """The Laptop I want is an #{brand}, it has a #{processor},
#{memory} of ram, a #{storage}, and it #{color}!!!"""
I encourage you to try it.
I have a module FDParser that reads a csv file and returns a nice array of hashes that each look like this:
{
:name_of_investment => "Zenith Birla",
:type => "half-yearly interest",
:folio_no => "52357",
:principal_amount => "150000",
:date_of_commencement => "14/05/2010",
:period => "3 years",
:rate_of_interest => "11.25"
}
Now I have an Investment class that accepts the above hash as input and transforms each attribute according to what I need.
class Investment
attr_reader :name_of_investment, :type, :folio_no,
:principal_amount, :date_of_commencement,
:period, :rate_of_interest
def initialize(hash_data)
#name = hash_data[:name_of_investment]
#type = hash_data[:type]
#folio_no = hash_data[:folio_no]
#initial_deposit = hash_data[:principal_amount]
#started_on =hash_data[:date_of_commencement]
#term = hash_data[:period]
#rate_of_interest = hash_data[:rate_of_interest]
end
def type
#-- custom transformation here
end
end
I also have a Porfolio class with which I wish to manage a collection of investment objects. Here is what the Portfolio class looks like:
class Portfolio
include Enumerable
attr_reader :investments
def initialize(investments)
#investments = investments
end
def each &block
#investments.each do |investment|
if block_given?
block.call investment
else
yield investment
end
end
end
end
Now what I want is to loop over the investment_data yielded by the module and dynamically create instances of the investment class and then send those instances as input to the Portfolio class.
So far I tried:
FDParser.investment_data.each_with_index do |data, index|
"inv#{index+1}" = Investment.new(data)
end
But obviously this doesn't work because I get a string instead of an object instance. What is the right way to send a collection of instances to a enumerable collection class that can then manage them?
I'm not sure what "send as input to the Portfolio class" means; classes themselves don't accept "input". But if you're just trying to add Investment objects to the #investments instance variable inside an instance of Portfolio, try this:
portfolio = Portfolio.new([])
FDParser.investment_data.each do |data|
portfolio.investments << Investment.new(data)
end
Note that the array literal [] and the return value of portfolio.investments point to the self-same Array object here. This means you could equivalently do this, which arguably is a little clearer:
investments = []
FDParser.investment_data.each do |data|
investments << Investment.new(data)
end
Portfolio.new(investments)
And if you want to play a little code golf, it shrinks further if you use map.
investments = FDParser.investment_data.map {|data| Investment.new(data) }
Portfolio.new(investments)
I think this is a little harder to read than the previous option, though.