This question already has answers here:
In Ruby is there a way to overload the initialize constructor?
(7 answers)
Closed 9 years ago.
I've got a class just like this:
class Equipment
attr_reader :text
attr_reader :name
attr_reader :array1
attr_reader :number
end
then, I want to make 2 constructors with 3 parameters each:
1º one -> (text,name,array1)
2º one -> (text, name,number)
The first one as an argument has an array and the other one has an integer (1,2...), so I need to define both constructors so when I create an object of this class it makes a difference between array or integer as the 3º argument.
Any ideas?
EDIT.: I thought this:
def initialize(text = "", name = "", array = array.new, number =0)
#text = text
#name = name
#array1 = array
#number = number
end
(initializing all of them) then:
def Equipment.newc_witharray(sometext, somename, somearray)
#text = sometext
#name = somename
#array1 = somearray
end
def Equipment.newc_withint(sometext, somename, somenumber)
#text = text
#name = name
#number = somenumber
end
and finally calling objects like this:
character1 = Equipment.newc_withint("Barbarian", "conan", 3)
shouldn't this work?
You can create as many constructors as you want on the class with whatever name you want. There is one constructor new, which is inherited from Object, and that can be used to write other constructors. What other answers mention as the constructor, namely the instance method initialize is not a constructor. That is the method called by the constructor method new by default.
class Foo
def self.new1 text, name, array1
obj = new
# do something on obj with text, name, array1
obj
end
def self.new2 text, name, number
obj = new
# do something on obj with text, name, number
obj
end
end
Foo.new1(text, name, array1)
Foo.new2(text, name, number)
There are various ways to achieve this.
Hash arguments
You could pass a hash and extract the values you're interested in:
def initialize(options={})
#text = options.fetch(:text) # raises KeyError if :text is missing
#name = options.fetch(:name) # raises KeyError if :name is missing
#array = options.fetch(:array, []) # returns [] if :array is missing
#number = options.fetch(:number, 0) # returns 0 if :number is missing
end
Keyword arguments
In Ruby 2.0 you can use keyword arguments with default values:
def initialize(text: text, name: name, array: [], number: 0)
#text = text
#name = name
#array = array
#number = number
end
Switching on argument type
This makes the method harder to read, but would work, too:
def initialize(text, name, number_or_array)
#text = text
#name = name
#number = 0
#array = []
case number_or_array
when Integer then #number = number_or_array
when Array then #array = number_or_array
else
raise TypeError, "number_or_array must be a number or an array"
end
end
Built into the language, no, Ruby does not give you that ability.
However, if you want that ability, I would create an initialize method which takes a hash as its parameter. Then you could create an instance of the class using any number of parameters.
E.g:
class Equipment
attr_reader :text, :name, :array1, :number
def initialize(options)
[:text, :name, :array1, :number].each do |sym|
self.send(sym) = options[sum]
end
end
end
The ruby interpreter wouldn't be able to differentiate between the constructors, as the types are not known until runtime :(
However, you can use a very nice workaround:
class Foobar
def initialize(h) # <-- h is a hash
# pass combination of params into the hash, do what you like with them
end
end
and then, using this pattern, you can pass any combination of params into the constructor:
foobar = Foobar.new(:foo => '5', :bar => 10, :baz => 'what?')
Related
class Cat
SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
attr_accessor :name
def initialize(name)
#name = name
end
end
I get an error
ArgumentError: wrong number of arguments(1 for 0)
because initialize is not again defined.
If I put the definition on the end:
class Cat
attr_accessor :name
def initialize(name)
#name = name
end
SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
end
it works.
Is it possible to keep constant declaration on the top of the file?
I think, you can use this trick:
class Cat
def self.const_missing(name)
[Cat.new('John'), Cat.new('Alfred')] if name == :SUPERSTARS
end
attr_accessor :name
def initialize(name)
#name = name
end
end
Avdi Grimm in his free episode of rubytapas called "Barewords" recommends that you don't use constants directly. Instead, wrap them in a method. And when you have a method, you don't even need the constant (which in ruby is not really constant anyway).
So, you can do something like this:
class Cat
def self.superstars
#superstars ||= [Cat.new('John'), Cat.new('Alfred')]
end
attr_accessor :name
def initialize(name)
#name = name
end
end
The only difference is that you now call it Cat.superstars instead of Cat::SUPERSTARS. Your code now works and looks better too! I call it a win-win. :)
The problem is that class definitions are just code that gets executed when the definition is reached. It's like asking if you can access the value of a local variable that you have not yet defined.
A possible workaround is to delay the evaluation of the constant until the class body is executed. Note that it's not really worth it and it shows a working solution that shouldn't be used in practice:
# BasicObject for minimal amount of methods
class Retarded < BasicObject
def initialize(&value)
#value = value
end
# Make sure we remove as many methods as we can to
# make the proxy more transparent.
(instance_methods - %i(__id__ __send__ __binding__)).each do |name|
undef_method name
end
def method_missing(*args, &block)
# Get the actual value
value = #value.call
# Suppress warnings while we traverse constants
warn_level, $VERBOSE = $VERBOSE, nil
traversed_modules = []
constant_finder = -> (root) do
constant_name = root.constants.find do |constant|
# We undefed ==, so we need a different way to compare.
# Given that this class should be used for constant values in place,
# comparing object ids does the job.
root.const_get(constant).__id__ == __id__
end
if constant_name
# Just set the constant to the actual value
root.const_set(constant_name, value)
else
# Recursively search for the containing module of the constant
root.constants.each do |child_name|
child_constant = root.const_get(child_name)
if child_constant.is_a?(::Module) &&
!traversed_modules.include?(child_constant)
# Handle circular references
traversed_modules.push child_constant
constant_finder.(child_constant)
end
end
end
end
# ::Object is the root module where constants are contained
constant_finder.(::Object)
# Bring back warnings
$VERBOSE = warn_level
# We have already found the constant and set its value to whatever was
# passed in the constructor. However, this method_missing was called
# in an attempt to call a method on the value. We now should make that
# invocation.
value.public_send(*args, &block)
end
end
# I know, the example is mononic.
class EdwardKing
DEAD = Retarded.new { [new('I'), new('II'), new('III')] }
def initialize(number)
#number = number
end
def whoami
"Edward #{#number} of England"
end
end
p EdwardKing::DEAD
# [#<EdwardKing:0x007f90fc26fd10 #number="I">, #<EdwardKing:0x007f90fc26fc70 #number="II">, #<EdwardKing:0x007f90fc26fc20 #number="III">
p EdwardKing::DEAD.map(&:whoami)
# ["Edward I of England", "Edward II of England", "Edward III of England"]
I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end
This is the problem "I am trying to create a generic object, could be thought of as a "dynamic schema
object" each schema object will have a different number of instances variables." and this approach doesn't work.
class GenericObjectArray
def initialize
#data_fields = []
end
def data_fields(t)
#data_fields << t
end
def initialize(attrs = {})
attrs.each { |attr,val| instance_variable_set "##{attr}", val }
end
end
p GenericObjectArray.new(:data_fields=> "may_sales", :data_fields=>"june_sales", :data_fields=>"july_sales")
This is my approach, bu it doesnt work. I would like to set may_sales, june_sales, july_sales as an instance variables. Set all three as instance variables. It only returns that last one.
GenericObjectArray:0x007f8c5b883cd8 #data_fields="july_sales"
Think it from this approach:
You have objects (lets say GenericObject)
Objects have many attributes (GenericObject#attributes => [GenericObject::Attribute])
Attributes have a name, a value, and a type (GenericObject::Attribute#value, #name and #type)
Which translates into code like this:
class GenericObject
attr_accessor :attributes
def add_attribute(name, value, type)
(#attributes ||= []) << Attribute.new(name, value, type)
end
class Attribute
attr_accessor :name, :value, :type
def initialize(name, value, type)
#name, #value, #type = name, value, type
end
end
end
# so...
cat = GenericObject.new
cat.add_attribute :leg_number, 4, :integer
cat.add_attribute :fur_color, 'Orange', :color
cat.add_attribute :name, 'Garfield', :string
cat.attributes.each { |attr| puts "My cat's #{attr.name} is #{attr.value} (#{attr.type})" }
# My cat's leg_number is 4 (integer)
# My cat's fur_color is Orange (color)
# My cat's name is Garfield (string)
You can make a fancy initializer for GenericObject or whatever you see fit.
Or you can just to a little fix
class GenericObjectArray
def initialize(attrs = {})
attrs.each { |attr,val| instance_variable_set "##{attr}", val }
end
end
GenericObjectArray.new(:data_fields=> ["may_sales", "june_sales", "july_sales"])
I'm pretty new to Ruby(and programming in general), but I thought there was a way to call the attributes of all of a classes objects?
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
def self.all_numbers
[] << Player.each {|person| person.number}
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
I would like to just access the numbers for all objects by calling the class..
Player.all_numbers
hoping to return..
[23, 18]
The problem you have right now is that Player is your custom class. It does not respond to a class method each. Another issue is that the Player class has no knowledge of the instances created outside of it.
There's many ways to go about this. The way I would do this is to implement another class called Team like this
class Team
def initialize(*players)
#players = players
end
def player_numbers
#players.map { |player| player.number }
end
end
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
team = Team.new(guy1, guy2)
team.player_numbers
#=> [23, 18]
Write as below with the help of ObjectSpace#each_object :
Calls the block once for each living, nonimmediate object in this Ruby process. If module is specified, calls the block for only those classes or modules that match (or are a subclass of) module. Returns the number of objects found. Immediate objects (Fixnums, Symbols true, false, and nil) are never returned.
If no block is given, an enumerator is returned instead.
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
def self.all_numbers
ObjectSpace.each_object(self).map(&:number)
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
Player.all_numbers
# => [18, 23]
Another approach
class Player
attr_reader :number
#class_obj = []
def initialize(name, number)
#name = name
#number = number
self.class.add_object(self)
end
def self.add_object(ob)
#class_obj << ob
end
def self.all_numbers
#class_obj.map(&:number)
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
Player.all_numbers
# => [23, 18]
Use a Class Variable
Using a Team to hold references to complete Player objects is probably more desirable from a design perspective, but you can do what you want with a simple class variable. For example:
class Player
attr_reader :number
##numbers = []
def initialize(name, number)
#name = name
#number = number
##numbers << #number
end
def self.all_numbers
##numbers
end
end
guy1 = Player.new 'Bill', 23
guy2 = Player.new 'jeff', 18
Player.all_numbers
#=> [23, 18]
For this simple use case, that's probably sufficient. However, if you find yourself trying to stuff entire Player objects into a Player class variable then you will definitely want to find a better noun (e.g. Team or Players) to hold your collection.
I would simply override new and save the instances in a class variable:
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
def self.new(*args)
(##players ||= []) << super
##players.last
end
def self.all_numbers
##players.reduce([]) {|arr, player| arr << player.number}
end
end
Player.new('Bill', 23)
Player.new('jeff', 18)
p Player.all_numbers # => [23, 18]
Carpk, I should explain a few things:
Normally, Player.new('Bill', 23) would send the method Class#new to class Player, together with the two arguments. By defining self.new here, I am overriding Class#new. self.new invokes super, which invokes Class#new with self.new's arguments (there is no need to explicitly pass the arguments, unless you only want to pass some or none). Class#new returns the new class instance to self.new, which returns it as well, but first saves it in the array ##player.
(##players ||= []) is a typical Ruby trick. This has the effect of setting ##players equal to an empty array if it has not yet been defined (is nil), and leaves it unchanged if it already an array (empty or not). This is because ##players ||= [] is the same as ##players = ##players || []. If ##players => nil, this becomes ##players = []; else, ##players remains unchanged ([] is never evaluated). Cute, eh?
##players.last returns the class instance that was just returned by Class#new and added to the end of ##players.
##players.reduce([]) {|arr, player| arr << player.number} creates and returns an array arr of class instances, which in turn self.all_numbers returns. reduce's argument is the initial value of the accumulator, here an empty array named arr within the block. Note reduce and inject are synonyms.
Carpk, don't read any further; it will just be confusing.
I lied. I actually wouldn't do it that way. I suggested using a class variable and class methods because Carpk is new to Ruby and probably not into metaprogramming in a big way. What I'd actually do is define a class instance variable #players and make new and all_numbers singleton methods of the class Player. That way, instances of the class Player would not have easy access to that variable or those two methods. To do this, simply replace the above definitions of self.new and self.all_numbers with the following:
class << self
def new(*args)
(#players ||= []) << super
#players.last
end
def all_numbers
#players.reduce([]) {|arr, player| arr << player.number}
end
end
Carpk, I warned you.
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.