Why am I getting a nil value (Ruby) - ruby

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

Related

Initializing multiple objects at once?

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

Understanding Ruby Setters

If I create a class like this:
class Player
def initialize(position, name)
#position = position
#name = name
end
end
Isn't that setting the name to an instance variable? if so, why would I need to write a setter like this
class Player
def initialize(position, name)
#position = position
#name = name
end
def name=(name)
#name = name
end
end
Basically when is it necessary to write getters in a class?
Getters and setters job is to provide you a quick implementation of read and write of instance variables that you define in your constructor:
class Player
attr_accessor :name, :position
def initialize(position, name)
#position = position
#name = name
end
end
you can also user attr_reader(for getters) and attr_writer(for setters) specifically for these variables.
Above code: attr_accessor :name, :position gives you: #name, #position, #name=, and #position= methods for instance of Player class.
However, they're not going to give you validation or a customized logic for getters/setters.
For example: you might want to show a player's full name or do not wish your code to accept a 0 or negative position, in such a case you'd have to write getter and setter yourself:
class Player
def initialize(first_name, last_name, position)
#first_name = first_name
#last_name = last_name
#position = position
end
# validation for updating position using setter of position
def position=(new_position)
raise "invalid position: #{new_position}" if new_position <= 0
#position = new_position
end
# customized getter for name method
def name
"#{#first_name} #{#last_name}"
end
end
If you do not require customization as stated above then using attr_* method for these variables makes more sense.
initialize sets attributes during the initialization of a new object.
keeper = Player.new('goalkeeper','Shilton').
What if you want to update an attribute of keeper after initialzation? Well you'll need to use your ordinary setter method:
def name=(name)
#name = name
end
like so:
keeper.name = 'Banks'
If you don't have a setter method defined for Player instances, then you can't do this. Similarly for getter methods. Also be aware you can refactor your code by using attr_accessor like so:
class Player
attr_accessor :name, :position
def initialize(position, name)
#position = position
#name = name
end
end
Getters/setters, also known as "accessors", are accessible outside the class, instance variables are not. If you want things to be able to read or change #name from outside the class, you to define accessors for it.
Additionally accessor methods allow you to perform a certain amount of sanity checking or mutate incoming/outgoing values, and otherwise protect the internal state of your objects.

How can I create instances of a ruby class from a hash array?

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.

Inter-class use of variables in ruby

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"

Intermingling attr_accessor and an initialize method in one class

I see code like:
class Person
def initialize(name)
#name = name
end
end
I understand this allows me to do things like person = Person.new and to use #name elsewhere in my class like other methods. Then, I saw code like:
class Person
attr_accessor :name
end
...
person = Person.new
person.name = "David"
I'm just at a loss with these two methods mesh. What are the particular uses of def initialize(name)? I suppose attr_accessor allows me to read and write. That implies they are two separate methods. Yes? Want clarifications on def initialize and attr_accessor and how they mesh.
initialize and attr_accessor have nothing to do with each other. attr_accessor :name creates a couple of methods:
def name
#name
end
def name=(val)
#name = val
end
If you want to set name upon object creation, you can do it in the initializer:
def initialize(name)
#name = name
# or
# self.name = name
end
But you don't have to do that. You can set name later, after creation.
p = Person.new
p.name = "David"
puts p.name # >> "David"
Here is the answer you are looking for Classes and methods. Read it carefully.
Here is a good documentation from the link:
Classes and methods
Now we are ready to create our very own Address class. Let's start simple. Let's start with an address that only contains the "street" field.
This is how you define a class:
class Address
def initialize(street)
#street = street
end
end
Let's go through this:
The class keyword defines a class.
By defining a method inside this class, we are associating it with this class.
The initialize method is what actually constructs the data structure. Every class must contain an initialize method.
#street is an object variable. Similar to the keys of a hash. The # sign distinguishes #street as an object variable. Every time you create an object of the class Address, this object will contain a #street variable.
Let's use this class to create an address object.
address = Addres.new("23 St George St.")
That's it. address is now an object of the class Address
Reading the data in an object
Suppose that we want to read the data in the address object. To do this, we need to write a method that returns this data:
class Address
def initialize(street)
#street = street
end
# Just return #street
def street
#street
end
end
Now the method Address#street lets you read the street of the address. In irb:
>> address.street
=> "23 St George St."
A property of an object, which is visible outside, is called an attribute. In this case, street is is an attribute. In particular, it is a readable attribute. Because this kind of attribute is very common, Ruby offers you a shortcut through the attr_reader keyword:
class Address
attr_reader :street
def initialize(street)
#street = street
end
end
Changing the data in an object
We can also define a method to change the data in an object.
class Address
attr_reader :street
def initialize(street)
#street = street
end
def street=(street)
#street = street
end
end
Ruby is pretty smart in its use of the street= method:
address.street = "45 Main St."
Notice that you can put spaces betten street and =. Now that we can change the address data, we can simplify the initialize method, and have it simply default the street to the empty string "".
class Address
attr_reader :street
def initialize
#street = ""
end
def street=(street)
#street = street
end
end
address = Address.new
address.street = "23 St George St."
This might not seem like much of a simplification, but when we add the city, state and zip fields, and more methods this will make the class definition a bit simpler.
Now, street is also a writable attribute. As before, you can declare it as such with attr_writer:
class Address
attr_reader :street
attr_writer :street
def initialize
#street = ""
end
end
Accessing data
Very often, you have attributes that are both readable and writable attributes. Ruby lets you lump these together with attr_accessor. I guess these would be called "accessible attributes", but I have never seen them be called that.
class Address
attr_accessor :street
def initialize
#street = ""
end
end
With this knowledge, it is now easy to define the entire addressbook structure. As it turns out, attr_accessor and friends all accept multiple arguments.
class Address
attr_accessor :street, :city, :state, :zip
def initialize
#street = #city = #state = #zip = ""
end
end
I think you consider initialize as a constructor. To be precise, it is not. The default constructor is the new method on the class, and initialize is called by that method. If you do not define initialize, you can still create an object with new because initialize is not the constructor itself. In that case, the default initialize does nothing. If you do define initialize, then that is called right after the object creation.
The statement #foo = ... and attr_accessor :foo are different. The former assigns a value to the instance variable #foo, whereas the latter lets you access #foo via methods foo and foo=. Without the latter, you can still access #foo by directly describing so.
Unlike C++,Java instance variables in Ruby are private by default(partially as they can be accessed by using a.instance_variable_get :#x)
eg:
class Dda
def initialize task
#task = task
#done = false
end
end
item = Dda.new "Jogging" # This would call the initializer and task = Jogging would
be set for item
item.task # would give error as their is no function named task to access the instance
variable.
Although we have set the value to item but we won't be able to do anything with it as instace variables are private in ruby.
code for getter:
def task
#task
end
#for getter
def task=task
#task = task
end
Using getter would ensure that item.task returns it's value
And using setter gives us the flexibility to provide values to instance variables at any time.

Resources