The challenge is to make a class that build equals objects for the same value for example:
class Person
att_reader :name
def initialize(name)
#name = name
end
end
I want to make
p1 = Person.new('joe')
p2 = Person.new('joe')
p3 = Person.new('mary')
p1 == p2 #true
p2 == p3 #false
I already tried to look for singleton but I can't make method new to be private
Let's use Comparable mixin
class Person
include Comparable
attr_reader :name
def initialize(name)
#name = name
end
def <=>(other)
name <=> other.name
end
end
Related
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.
Im struggling on understanding (after googling) on how to implement this: I have a class:
class Student
# constructor method
def initialize(name,age)
#name, #age = name, age
end
# accessor methods
def getName
#name
end
def getAge
#age
end
# setter methods
def setName=(value)
#name = value
end
def setAge=(value)
#age = value
end
end
And lets say I have another class which inherits from Student
class Grade < Student
#constructor method
def initialize(grade)
super
#grade = grade
end
# accessor methods
def getGrade
#grade
end
# setter methods
def setGrade=(value)
#grade = value
end
end
I understand how to build an abject:
student = Student.new(name, age)
How can I build this Student (that I have just created) a Grade object associated with the student and how would I call the inherited object, for example i wanted to:
puts 'student name and associated grade'
I know I can place the grade variable within the Student class, but for the purpose of learning im doing it this way.
This code would do what you wanted:
class Grade
attr_accessor :value
def initialize value
#value = value
end
end
class Student
attr_accessor :name, :age, :grade
def initialize name, age, grade
#name, #age, #grade = name, age, Grade.new(grade)
end
end
st = Student.new 'John', 18, 5
puts "student #{st.name} and associated grade #{st.grade.value}"
First off, no need to define accessors in Ruby like that, it's far from idiomatic. Let's clean that up first:
class Student
attr_accessor :name, :age
def initialize(name, age)
#name =name
#age = age
end
end
class Grade
attr_accessor :value
def initialize(grade)
#value = grade
end
end
Secondly it doesn't seem like Grade should inherit from Student at all, just adjust the latter to also store a Grade instance variable:
class Student
attr_accessor :name, :age, :grade
def initialize(name, age, grade = nil)
#name =name
#age = age
#grade = grade
end
end
You can then instantiate a student like this:
student = Student.new("Test", 18, Grade.new(1))
Or because of the default value you leave off the grade and assign it later:
student = Student.new("Test", 18)
# later
student.grade = Grade.new(1)
I've been playing around with Ruby as of late and I can't seem to find the answer to my question.
I have a class and a subclass. Class has some initialize method, and subclass has its own initialize method that is supposed to inherit some (but not all) variables from it and additionally add its own variables to the subclass objects.
My Person has #name, #age and #occupation.
My Viking is supposed to have a #name and #age which it inherits from Person, and additionally a #weapon which Person doesn't have. A Viking obviously doesn't need any #occupation, and shouldn't have one.
# doesn't work
class Person
def initialize(name, age, occupation)
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, weapon)
super(name, age) # this seems to cause error
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'broadsword')
# ArgError: wrong number of arguments (2 for 3)
You can make it work in the following ways, but neither solution appeals to me
class Person
def initialize(name, age, occupation = 'bug hunter')
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, weapon)
super(name, age)
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'broadsword')
# Eric now has an additional #occupation var from superclass initialize
class Person
def initialize(name, age, occupation)
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, occupation, weapon)
super(name, age, occupation)
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'pillager', 'broadsword')
# eric is now a pillager, but I don't want a Viking to have any #occupation
The question is either
is it by design and I want to commit some Cardinal Sin against OOP principles?
how do I get it to work the way I want to (preferably without any crazy complicated metaprogramming techniques etc)?
How super handles arguments
Regarding argument handling, the super keyword can behave in three ways:
When called with no arguments, super automatically passes any arguments received by the method from which it's called (at the subclass) to the corresponding method in the superclass.
class A
def some_method(*args)
puts "Received arguments: #{args}"
end
end
class B < A
def some_method(*args)
super
end
end
b = B.new
b.some_method("foo", "bar") # Output: Received arguments: ["foo", "bar"]
If called with empty parentheses (empty argument list), no arguments are passed to the corresponding method in the superclass, regardless of whether the method from which super was called (on the subclass) has received any arguments.
class A
def some_method(*args)
puts "Received arguments: #{args}"
end
end
class B < A
def some_method(*args)
super() # Notice the empty parentheses here
end
end
b = B.new
b.some_method("foo", "bar") # Output: Received arguments: [ ]
When called with an explicit argument list, it sends those arguments to the corresponding method in the superclass, regardless of whether the method from which super was called (on the subclass) has received any arguments.
class A
def some_method(*args)
puts "Received arguments: #{args}"
end
end
class B < A
def some_method(*args)
super("baz", "qux") # Notice that specific arguments were passed here
end
end
b = B.new
b.some_method("foo", "bar") # Output: Received arguments: ["baz", "qux"]
Yes, you are committing a Cardinal Sin (obviously, you are aware of it, since you are asking about it). :)
You are breaking Liskov substitution principle (and probably some other named or unnamed rules).
You should probably extract another class as a common superclass, which does not contain occupation. That will make everything much clearer and cleaner.
You can avoid setting a default value to your occupation parameter in Person Class by simply passing the nil argument for occupation in super(). This allows
you to call Viking.new with your 3 arguments (name, age, weapon) without
having to take extra consideration for occupation.
class Person
def initialize(name, age, occupation)
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, weapon)
super(name, age, nil)
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'broadsword')
p eric
output Viking:0x00007f8e0a119f78 #name="Eric", #age=24, #occupation=nil, #weapon="broadsword"
Really this is just 2 ways - auto include all attributes (just the word super) and super where you pick which arguments get passed up. Doing super() is picking no arguments to hand to the parent class.
Edit: This was meant as a comment on the above comment but they don't allow comments when someone is new... oh well.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names
I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?