How to implement this method chaining - ruby

In one of the interview, they asked me to write code for
car1 = Car.make("Honda").model("Civic").year("2009")
car2 = Car.make("Toyota").model("Camry").year("2009")
car1.to_s --> Honda Civic 2009
car2.to_s --> Toyota Camry 2009
Can anyone help me with this? Thanks in advance.

This is a so-called fluent interface. You can implement it by returning self from the methods:
class Car
def make(value)
#make = value
self
end
def model(value)
#model = value
self
end
def year(value)
#year = value
self
end
def to_s
[#make, #model, #year].compact.join(' ')
end
end
puts Car.new.make("Honda").model("Civic").year("2009")
# Honda Civic 2009
Note that I had to call new in the beginning because make, model and year are instance methods.
To omit new (as shown in your example), you can provide class methods with the same names that wrap the new call and delegate to their respective instance counterparts:
class Car
def self.make(value)
new.make(value)
end
def self.model(value)
new.model(value)
end
# and so on
end
puts Car.make("Honda").model("Civic").year("2009")
# Honda Civic 2009

There are many ways to do this. Here is one.
module Car
#str = ''
def self.make(car_name)
#str = car_name + ' '
self
end
def self.model(model_name)
#str += model_name + ' '
self
end
def self.year(year)
#str + year
end
end
Car.make("Honda").model("Civic").year("2009").to_s
#=> Honda Civic 2009
Car.make("BMW").model("i3").year("2015").to_s
#=> BMW i3 2015
One could alternatively replace Car::year with
def self.year(year)
#str += year
end
and add
def self.to_s
#str
end

Related

How do I pull a name from a class in an Array?

Okay, this is a little hard to explain but I will try (For starters I am only just learning to code so it may be something super simple I'm missing..)
I created a few classes, I put a name in those classes, I put them in an array, I then chose one at random and try to puts the name, and it outputs blank.
Am I doing this all completely wrong? I've been learning ruby for about 3 months now so I'm sure there is a lot I don't know.
class A
attr :name
def set_name
#name = "Aaa"
end
def get_name
return #name
end
end
class B
attr :name
def set_name
#name = "Bbb"
end
def get_name
return #name
end
end
class C
attr :name
def set_name
#name = "Ccc"
end
def get_name
return #name
end
end
name_a = A.new
name_b = B.new
name_c = C.new
which_name = Array.new
which_name[0] = name_a
which_name[1] = name_b
which_name[2] = name_c
roll = rand(max 3)
puts which_name[roll].get_name
I then chose one at random and try to puts the name, and it outputs
blank.
You never called the #set_name method in your code. You can add this:
name_a.set_name
name_b.set_name
name_c.set_name
Also, you probably want to look into #attr_accessor.

Adding new attribute to Object after it's created using method (Ruby)

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

Methods to create deep copy of objects without the help of Marshal

I have 3 simple classes CashRegister, Bill and Position. A CashRegister is composed of Bill objects and a Bill object is composed of Position objects. They're implemented as followed
class CashRegister
def initialize
#bills = []
end
def clone
#?
end
end
class Bill
def initialize(nr)
#nr = nr
#positions = []
end
def clone
#?
end
end
class Position
def initialize(product, price)
#product = product
#price = price
end
def clone
#?
end
end
How do I create methods that can deep copy the objects of these classes. The use of Marshal.load(Marshal.dump(an_obj)) is not allowed.
Edit: So far I've got this:
class CashRegister
def initialize
#bills = []
end
def clone
#bills.map { |bill| bill.clone}
end
end
class Bill
def initialize(nr)
#nr = nr
#positions = []
end
def clone
cloned = super
cloned.positions = #positions.map{ |pos| pos.clone}
cloned
end
end
class Position
attr_reader :preis
# this method is given
def produkt
#produkt.clone()
end
def initialize(product, price)
#product = product
#price = price
end
def clone
cloned = super
cloned.product
cloned
end
end
The clone method in class Position seems to be ok (no compile error). But there is an error in the one in class Bill, it says "undefined method 'positions=', so the problem must be in the line cloned.positions = #positions.map{ |pos| pos.clone}. But I don't understand, can't we call cloned.positions like that?
It's just the instance variables you have to worry about.
class Position
attr_accessor :product, :price
def initialize(product, price)
#product = product
#price = price
end
end
p1 = Position.new("lima beans", 2.31)
#=> #<Position:0x000000027587b0 #product="lima beans", #price=2.31>
p2 = Position.new(p1.product, p1.price)
#=> #<Position:0x0000000273dd48 #product="lima beans", #price=2.31>
We can confirm that p2 is a deep copy of p1.
p1.product = "lettuce"
p1.price = 1.49
p1 #=> #<Position:0x0000000271f870 #product="lettuce", #price=1.49>
p2 #=> #<Position:0x000000026e9e00 #product="lima beans", #price=2.31>
p2.product = "spinach"
p2.price = 2.10
p1 #=> #<Position:0x0000000271f870 #product="lettuce", #price=1.49>
p2 #=> #<Position:0x000000026e9e00 #product="spinach", #price=2.1>
It's more complex if, for example, the class were defined as follows (where products is an array).
p1 = Position.new ["carrots", "onions"]
#=> #<Position:0x000000025b8928 #products=["carrots", "onions"]>
p2 = Position.new p1.products
#=> #<Position:0x000000025b0048 #products=["carrots", "onions"]>
p1.products << "beets"
p1 #=> #<Position:0x000000025b8928 #products=["carrots", "onions", "beets"]>
p2 #=> #<Position:0x000000025b0048 #products=["carrots", "onions", "beets"]>
p2 is not what we want. We would need to write
p1 = Position.new ["carrots", "onions"]
#=> #<Position:0x00000002450900 #products=["carrots", "onions"]>
p2 = Position.new p1.products.dup
#=> #<Position:0x0000000243aa88 #products=["carrots", "onions"]>
(note the .dup) so that
p1.products << "beets"
#=> ["carrots", "onions", "beets"]
p1 #=> #<Position:0x00000002450900 #products=["carrots", "onions", "beets"]>
p2 #=> #<Position:0x0000000243aa88 #products=["carrots", "onions"]>
More generally, we need to make deep copies of the instance variables.
This solution works
class CashRegister
attr_accessor :bills
def initialize
#bills = []
end
def clone
cloned = super
cloned.bills = #bills.map { |bill| bill.clone }
cloned
end
end
class Bill
attr_accessor :positions
def initialize(nr)
#nr = nr
#positions = []
end
def clone
cloned = super
cloned.positions = #positions.map{ |pos| pos.clone }
cloned
end
end
class Position
attr_reader :price
attr_writer :product
# this method is given
def product
#product.clone
end
def initialize(product, price)
#product = product
#price = price
end
def clone
cloned = super
cloned.product = product
cloned
end
end
Another possible answer is to use the full_dup gem (full disclosure, written by me) then simply use:
p2 = p1.full_dup
Now full_dup, like regular dup, does not copy any singleton methods. If that is important, try the full_clone gem (yup, by me too) instead.
If there are fields that need to excluded from the dup (or clone process), the optional full_dup_exclude (or full_clone_exclude) method can be defined to list fields to be excluded from processing.
Note there is no need to worry about trying to clone numbers, symbols, and other non-clonable things that may exist in your object. These are handled safely be the gems.

Possible help in code refactoring

Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?

Classes in Ruby - Creating parameters that will accept multiple inputs

Ok guys, I am learning ruby and I am having a little bit of trouble with the tutorial. I was wondering if you could help me out!
Take the following code:
class Dish
def initialize(name, ingred, descrip)
#name = name
#ingred = ingred
#descrip = descrip
end
def name
#name
end
def name=(new_name)
#name = new_name
end
def ingred
#ingred
end
def ingred=(new_ingred)
#ingred = new_ingred
end
def descrip
#descrip
end
def descrip=(new_descrip)
#descrip = new_descrip
end
def display
puts "I am a #{#name} and my ingredient is #{#ingred} and my description is #{descrip}"
end
end
dis1 = Dish.new('Pizza', 'sauce', 'put sauce on that thing')
dis1.display
Ok so here is my question and I hope I explain it well enough. So far I have learned to take enter one parameter when making a new instance of a class (i.e. (name, ingred, descrip)). What I am wondering is if a dish has multiple ingredients, how would I add that to my class? Also, if I wanted to count the number of ingredients or the number of names, how would I do that. I am just learning about classes and I am having trouble matching the exact wording I would Google for. Thanks!
I'll try to answer some of your questions. To simplify, I removed your variable, descrip and its associated methods. You see I've put a * in front of ingred in initialize. This means that a variable number of arguments are passed after name. This is one way of dealing with your question about having multiple ingredients. Here ingred is an array. Since #ingred is set equal to ingred, #ingred is also an array. If you look at the various methods and what some print when invoked (shown at the bottom), you should be able to see how this works. (Edited to add a bit of functionality. You may need to scroll down at the bottom.)
class Dish
def initialize(name, *ingred)
#name = name
#ingred = ingred
end
def name
#name
end
def name=(new_name)
#name = new_name
end
def ingred
#ingred
end
def ingred=(*ingred)
#ingred = ingred
end
def add_ingred(ingred)
#ingred << ingred
end
def remove_ingred(ingred)
#ingred.delete(ingred)
end
def nbr_ingred
#ingred.count
end
end
dis1 = Dish.new("Pizza", "sauce", "crust", "cheese", "anchovies")
p dis1.ingred #=> ["sauce", "crust", "cheese", "anchovies"]
dis1.add_ingred("olives")
p dis1.ingred #=> ["sauce", "crust", "cheese", "anchovies", "olives"]
dis1.add_ingred(["capers", "mushrooms"])
p dis1.ingred #=> ["sauce", "crust", "cheese", "anchovies", "olives", ["capers", "mushrooms"]]
dis1.ingred.flatten!
p dis1.ingred #=> ["sauce", "crust", "cheese", "anchovies", "olives", "capers", "mushrooms"]
dis1.remove_ingred("anchovies")
p dis1.ingred #=> ["sauce", "crust", "cheese", "olives", "capers", "mushrooms"]
p dis1.nbr_ingred #=> 6
dis1.ingred = "olives", "pineapple" # treated as ["olives", "pineapple"]
p dis1.ingred #=> [["olives", "pineapple"]]
dis1.ingred = ["cheese", "crust"]
p dis1.ingred #=> [["olives", "pineapple"]]
dis1.ingred.flatten!
p dis1.ingred #=> ["olives", "pineapple"]
Use arrays.
class Dish
class Ingredient
attr_accessor :name, :description
def initialize(name, description)
#name = name
#description = description
end
def to_s
"#{name} - #{description}"
end
end
attr_accessor :name, :description, :ingredients
def initialize(name, description)
#name = name
#description = description
#ingredients = []
end
def to_s
"#{name} - #{description}\n #{ingredients.join("\n").to_s}"
end
end
pizza = Dish.new("Pizza", "Italian style pizza")
pizza.ingredients << Dish::Ingredient.new("Tomato Sauce", "Juicy Juicy Tomato Sauce.")
pizza.ingredients << Dish::Ingredient.new("Cheese", "Cheese, duh.")
puts pizza.to_s
As the two answers before me did both leave out the description parameter i will stealCary Swoveland's answer and add the descrip parameter:
class Dish
attr_accessor :name, :descrip
def initialize(name, *ingred, descrip) # Only in Ruby 1.9+
#name = name
#ingred = *ingred
#descrip = descrip
end
def ingred
#ingred
end
def ingred=(*ingred)
#ingred = ingred
end
def add_ingred(ingred)
#ingred << ingred
end
def remove_ingred(ingred)
#ingred.delete(ingred)
end
def nbr_ingred
#ingred.count
end
def display
puts "I am a #{#name} and my ingredient is #{#ingred.join(', ')} and my description is #{descrip}"
end
end
dis1 = Dish.new('Pizza', 'sauce', 'ham', 'put ingredients on that thing.')
dis1.add_ingred('fish')
dis1.display #=> I am a Pizza and my ingredient is sauce, ham, fish and my description is put ingredients on that thing.

Resources