Is #fname similar to invoking this->fname in other languages - ruby

Let's use this class definition as an example:
class Person
def fname
#fname
end
def fname=(fname)
#fname = fname
end
def lname
#lname
end
def lname=(lname)
#lname = lname
end
end
Just trying to connect the dots between Ruby and C++ syntax for example.

Yes, #foo-ish variables are instance variables.
Note the above can be shortened to:
class Person
attr_accessor :fname, :lname
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.

Trying to figure out what's wrong with this code

I can't come up with a solution.
class Person
def initialize(name)
#name = name
end
def greet(other_name)
#other_name
print "Hi #{#other_name}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
I would appreciate a helping hand.
You have to make a decision:
Do you want to provide both names when initializing the Person:
class Person
def initialize(name, other)
#name = name
#other = other
end
def greet
puts "Hi #{#other}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
#=> Hi Jerry, my name is Tom
Or do you want to provide the second name when calling the greet method:
class Person
def initialize(name)
#name = name
end
def greet(other)
puts "Hi #{other}, my name is #{#name}"
end
end
kit = Person.new("Tom")
kit.greet("Jerry")
#=> Hi Jerry, my name is Tom
In the initialize method, you should take in two parameters, because you are calling it with two. You were declaring the #other_name variable inside the greet function instead of the initialize one.
This will work.
class Person
def initialize(name, other_name)
#name = name
#other_name = other_name
end
def greet
print "Hi #{#other_name}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
https://repl.it/C5wn
Consider writing your code like this:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def greet(person)
puts "Hi #{person.name}, my name is #{name}"
end
end
tom = Person.new("Tom")
jer = Person.new("Jerry")
tom.greet(jer) #=> Hi Jerry, my name is Tom.
This way you actually have another person as an object instead of just a name.

Returning objects from Ruby Classes

I'm convinced I've had this working before!
I have the following class. The title string is created within the class the fname and lname strings are passed in as parameters. However, I can't seem to ever get the #title object to return anything other than nil.
What am I doing wrong here?
class Person
attr_accessor :fname, :lname, :title
def initialize(fname, lname)
#fname = fname
#lname = lname
#title = title
end
def string1
#lname + ", " + #fname
end
#title = "Director"
def string2
#title
end
end
p = Person.new("Yukihiro", "Matsumoto")
p p.string1
p p.string2
Within the context of your class, #title refers to the class instance variable. Within the context of an instance method, #title refers to an instance variable. You're confusing these two.
What you need instead is a lazy initializer if you have some kind of default that you need applied:
def title
#title ||= "Director"
end
Or better, to populate it in the first place. One way to fix this is to augment your initialize method:
def initialize(fname, lname, title = nil)
#fname = fname
#lname = lname
#title = title || "Director"
end
The following code:
class Person
attr_accessor :fname, :lname
def initialize(fname, lname)
#fname = fname
#lname = lname
end
def string1
#lname + ", " + #fname
end
def title
##title
end
##title = "Director"
def string2
##title
end
end
p = Person.new("Yukihiro", "Matsumoto")
p p.string1
p p.string2
gives the following output when run with Ruby 1.9.3:
"Matsumoto, Yukihiro"
"Director"
I've assumed that you want to keep the title the same for all the objects you create. If not, then iamnotmaynard's answer is what you're after.
You're not passing title to the constructor. You need to have something along the lines of
def initialize(fname, lname, title)
#fname = fname
#lname = lname
#title = title
end
and call it with
p = Person.new("Yukihiro", "Matsumoto", "sensei")
The line #title = "Director" is executed during the class definition, which sets the variable on the class Person itself, rather than during the construction of an instance of the class, which is why the instance variable #title is not being set to "Director" in the new object you create with Person.new.
In fact, here's how to show that this is what's happening. Adding this method
class Person
def self.string3
#title
end
end
adds a class method to Person to get the class's instance variable that was set during the class definition:
>> p Person.string3
"Director"

Dynamically Create Class Attributes with attr_accessor

In Ruby, is there a way to dynamically add a instance variable to a class? For Example:
class MyClass
def initialize
create_attribute("name")
end
def create_attribute(name)
attr_accessor name.to_sym
end
end
o = MyClass.new
o.name = "Bob"
o.name
One way (there are others) is to use instance_variable_set and instance_variable_get as so:
class Test
def create_method( name, &block )
self.class.send( :define_method, name, &block )
end
def create_attr( name )
create_method( "#{name}=".to_sym ) { |val|
instance_variable_set( "#" + name, val)
}
create_method( name.to_sym ) {
instance_variable_get( "#" + name )
}
end
end
t = Test.new
t.create_attr( "bob" )
t.bob = "hello"
puts t.bob
maybe ,
instance_variable_set(name,value)
is what your want!
eg:
class Mclass
def show_variables
puts self.class.instance_variables
end
end
Mclass.instance_variable_set(:#test,1)
o=Mclass.new
o.show_variables
you know, class is object too.
Made a mess of answering #ericso's comment/question in another comment, so here it is in an answer - a module which I use (based on #ReeseMoore's code) to do this & set instance vars, if necessary:
# adds ability to dynamically create instance vars & attr_accessors
module Attributable
def create_method(name, &block)
self.class.send(:define_method, name.to_sym, &block)
end
def create_setter(m)
create_method("#{m}=".to_sym) { |v| instance_variable_set("##{m}", v) }
end
def create_getter(m)
create_method(m.to_sym) { instance_variable_get("##{m}") }
end
def set_attr(method, value)
create_setter method
send "#{method}=".to_sym, value
create_getter method
end
end

In Ruby is there a way to overload the initialize constructor?

In Java you can overload constructors:
public Person(String name) {
this.name = name;
}
public Person(String firstName, String lastName) {
this(firstName + " " + lastName);
}
Is there a way in Ruby to achieve this same result: two constructors that take different arguments?
The answer is both Yes and No.
You can achieve the same result as you can in other languages using a variety of mechanisms including:
Default values for arguments
Variable Argument lists (The splat operator)
Defining your argument as a hash
The actual syntax of the language does not allow you to define a method twice, even if the arguments are different.
Considering the three options above these could be implemented with your example as follows
# As written by #Justice
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
#name = name
end
end
class Person
def initialize(args)
name = args["name"]
name = name + " " + args["lastName"] unless args["lastName"].nil?
#name = name
end
end
class Person
def initialize(*args)
#Process args (An array)
end
end
You will encounter the second mechanism frequently within Ruby code, particularly within Rails as it offers the best of both worlds and allows for some syntactic sugar to produce pretty code, particularly not having to enclose the passed hash within braces.
This wikibooks link provides some more reading
I tend to do
class Person
def self.new_using_both_names(first_name, last_name)
self.new([first_name, last_name].join(" "))
end
def self.new_using_single_name(single_name)
self.new(single_name)
end
def initialize(name)
#name = name
end
end
But I don't know if this is the best approach.
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
#name = name
end
end
class StatementItem
attr_reader :category, :id, :time, :amount
def initialize(item)
case item
when Order
initialize_with_order(item)
when Transaction
initialize_with_transaction(item)
end
end
def valid?
!(#category && #id && #time && #amount).nil?
end
private
def initialize_with_order(order)
return nil if order.status != 'completed'
#category = 'order'
#id = order.id
#time = order.updated_at
#amount = order.price
end
def initialize_with_transaction(transaction)
#category = transaction.category
#id = transaction.id
#time = transaction.updated_at
#amount = transaction.amount
end
end
You can use konstructor gem to declare multiple constructors in Ruby and imitate overloading:
class Person
def initialize(name)
#name = name
end
konstructor
def from_two_names(first_name, last_name)
#name = first_name + ' ' + last_name
end
end
Person.new('John Doe')
Person.from_two_names('John', 'Doe')
You could use the double splat operator ** in conjunction with logical or (double pipes) || inside the initialize method to achieve the same effect.
class Person
def initialize(**options)
#name = options[:name] || options[:first_name] << ' ' << options[:last_name]
end
end
james = Person.new(name: 'James')
#=> #<Person #name="James">
jill_masterson = Person.new(first_name: 'Jill', last_name: 'Masterson')
#=> #<Person #name="Jill Masterson">
However, if a new Person is created without a first_name, then the append << operation will fail with NoMethodError: undefined method '<<' for nil:NilClass. Here is a refactored initialize method to handle this case (using strip to remove whitespace if either option is excluded).
class Person
def initialize(**options)
#name = options[:name] || [ options[:first_name] , options[:last_name] ].join(' ').strip
end
end
goldfinger = Person.new(last_name: 'Goldfinger')
#=> #<Person #name="Goldfinger">
oddjob = Person.new(first_name: 'Oddjob')
#=> #<Person #name="Oddjob">
In fact, this approach handles calling Person.new without arguments or with an unexpected key to return the new instance with #name set to an empty string:
nameless = Person.new
#=> <#Person #name="">
middle_malcom = Person.new(middle_name: 'Malcom')
#=> <#Person #name="">
checkout functional-ruby gem which is inspired by Elixir pattern matching features.
class Person
include Functional::PatternMatching
defn(:initialize, String) { |name|
#name = name
}
defn(:initialize, String, String) {|first_name, last_name|
#name = first_name + ' ' + last_name
}
end

Resources