Ruby classes pass variables between - ruby

Is there any way to pass variables between classes?
I have the next code.
module Test
class Super
def initialize(name)
#name = name
end
end
class Upper
def test
puts #name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()
Thank you!

No, because a is an instance of the class. Two answers for you;
1) It's better programming practice to have send a to b. So you'd do something like this; (assuming attr_reader :name)
class Upper
def test(s)
s.name
end
end
a = Test::Super.new('My Name')
u = Test::Upper.new
u.test(a)
or you could have it part of the setup; I won't give you all the code, but here's how it'd look
a = Test::Super.new('My name')
b = Test::Upper.new(a)
b.test
=> 'My name'
Neither of these examples is particularly good practice for classes but I imagine you have a more specific use case you're trying to achieve that has been anonymised for the purpose of this question :)

If for some reason instances of the class Upper need to have an access to the internals of instances of the class Super, it means you have a design flaw.
One possible way would be Super needs to expose the variable via a getter:
module Test
class Super
def initialize(name)
#name = name
end
def name
#name
end
end
end
Now you might get the name with Test::Super.new("my name").name.
Another possibility is Upper is actually a subclass of Super:
class Upper < Super
def test
puts #name
end
end
Now Test::Upper.new("my name").test will print "my name", because Upper derives the implementation from Super.
Also, one might use an instance variable on the enclosing module level:
module Test
def self.name=(name)
#name = name
end
def self.name
#name
end
class Super
def initialize(name)
Test.name = name
end
end
class Upper
def test
puts Test.name
end
end
end
This would print:
▶ Test::Super.new("my")
#⇒ #<Test::Super:0x0055dae57fe390>
▶ Test::Upper.new.test
#⇒ "my"

You can make use of class variables in module, which is supported natively by Ruby.
You can do:
module Test
##name = ''
class Super
def initialize(name)
##name = name
end
end
class Upper
def test
puts ##name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()

Related

Is it bad practice to declare instance variable values based on the name of the class?

Let's say I have a couple subclasses that inherit from a base class like this:
class Base
def initialize
#name = self.class.name
#num1 = 10;
#num2 = 5;
end
def speak
puts "My name is #{name}"
end
end
class Sub1 < Base
end
class Sub2 < Base
end
class Sub3 < Base
end
Is it bad practice to declare the instance variable of #name to the name of the class? Would it be better to do something like this?
class Base
def initialize
#feet = 10;
#inches = 5;
end
def speak
puts "My name is #{name}"
end
end
class Sub1 < Base
def initialize
super()
#name = "Sub1"
end
end
class Sub2 < Base
def initialize
super()
#name = "Sub2"
end
end
class Sub3 < Base
def initialize
super()
#name = "Sub3"
end
end
If #name is always intended to be equal to the class name, you don't need it – just use self.class.name or myObject.class.name directly, or define an accessor method which returns it if you don't want to use .class.name from the outside. Initializing it manually would be bad practice since you'd always have to remember to do it in every subclass and changes to subclass names would need to be replicated to the initialization.
If #name is not always going to equal the class name in every subclass, then personally I'd still provide a default implementation where it is initialized from self.class.name and let subclasses override it as necessary.
The first method is more concise. What you probably should be doing is evaluating this in a lazy capacity:
def name
#name ||= self.class.to_s.split(/::/).last
end
That way you don't need to worry about assignment and any subclass can initialize if necessary with an override. Your speak method will continue to work without modification as presumably you had declared attr_reader :name for that to work in the first place.
I think it's quite pointless, look at this output:
class Base
def name
self.class.name
end
end
class Sub1 < Base
# This declaration is useless
def name
self.class.name
end
end
class Sub2 < Base
end
b = Base.new
b.name # "Base"
c = Sub1.new
c.name # "Sub1"
d = Sub2.new
d.name # "Sub2" (this function is inherited from base, but is called from Sub2 class context)
Next thing in ruby, you can use superclass method to get parent class name
Sub2.superclass.name # "Base"

Pre-defining variables

This is my class structure:
class Commodity
def initialize(name)
#name = name
end
class PriceSeries
def initialize(name)
#name = name
end
end
end
I want to instantiate the Commodity class:
gold = Commodity.new("gold")
then instantiate the PricePoint class:
gold.xau = Commodity::PriceSeries.new("gold")
It seems that I can only do this with an attribute accessor called xau in the gold class. Is this the only way to define that variable?
def xau
xau
end
I feel like this shouldn't be a function.
Well, there are a lot of ways to do it, but attr_accessor is by far the simplest:
class Commodity
attr_accessor :xau
end
gold = Commodity.new("gold")
gold.xau = some_value
What attr_accessor :xau does is defines a xau= method that assigns its argument to the instance variable #xau, and another method xau that returns the value of #xau. In other words, it basically does this:
class Commodity
# Setter
def xau=(value)
#xau = value
end
# Getter
def xau
#xau
end
end
For convenience and readability, Ruby lets you put whitespace before the = in gold.xau = foo, but in fact it's the same as the method call gold.xau=(foo).
I'm pretty sure what you want is attr_accessor:
class Commodity
attr_accessor :xau
end
Which is essentially equivalent to this:
class Commodity
def xau
#xau
end
def xau=(value)
#xau = value
end
end
It is not clear what you want to do, but it looks like you want to do this:
class Commodity
def initialize(name)
#name = name
#xau = Commodity::PriceSeries.new(name)
end
end
Then,
gold = Commodity.new("gold")
will automatically define
Commodity::PriceSeries.new("gold")
as the value of an instance variable #xau of gold.

In Ruby, can I give access to a protected method to a different specified class?

For example, I'd like to do something like this
class Child
#name = "Bastion, please, call my name!"
attr_reader :name
end
class Parent
#name = "I am a parent"
def rename_child(child,name)
child.name = name
end
end
bastion = Parent.new()
princess = Child.new()
bastion.rename_child(princess,"Moon Child")
I only want instances of the Parent class to be able to change the #name of a Child class.
EDIT
I only want instances of the Parent class to be able to change the #name of a Child instance.
I'm assuming you meant for #name to be an instance variable, rather than a class instance one...if my hunch is right, you can do this using #send:
class Child
def initialize
#name = "Bastion, please, call my name!"
end
attr_reader :name
private
attr_writer :name
end
class Parent
def initialize
#name = "I am a parent"
end
def rename_child(child, name)
child.send(:name=, name)
end
end
bastion, princess = Parent.new, Child.new
p princess.name
#=> "Bastion, please, call my name!"
bastion.rename_child(princess, "Moon Child")
p princess.name
#=> "Moon Child"
No you cannot do that. The problem is not the method being private. You cannot access a class instance variable by an accessor defined on an instance.
This is unfortunately not possible. The closest you can get is:
class Child
#name = "Bastion, please, call my name!"
attr_reader :name
end
class Parent
#name = "I am a parent"
def rename_child(child,name)
child.instance_variable_set(:#name, name)
end
end
Note that instance_variable_set is a public method, which together with eval makes private/public differences in ruby not to be so strict as in other languages. Those methods should be use extremely cautiously and they are usually considered to be slightly 'hacky'.

I can not use in-class assigned property of a class in Ruby

Well, I can do this:
class Person
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.name = 'Dave'
p.greeting # "Hello Dave"
but when I decide to assign the property in the class itself it doesnt work:
class Person
attr_accessor :name
#name = "Dave"
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.greeting # "Hello"
This is the default behavior, albeit a confusing one (especially if you're used to other languages in the OOP region).
Instance variables in Ruby starts being available when it is assigned to and normally this happens in the initialize method of your class.
class Person
def initialize(name)
#name = name
end
end
In your examples you're using attr_accessor, this magical method produces a getter and a setter for the property name. A Person#name and Person#name=, method is created which overrides your "inline" instance variable (that's why your first example works and the second one doesn't).
So the proper way to write your expected behaviour would be with the use of a initialize method.
class Person
def initialize(name)
#name = name
end
def greeting
"Hello, #{#name}"
end
end
Edit
After a bit of searching I found this awesome answer, all rep should go to that question.
Think of a Person class as a blueprint that you can create single person instances with. As not all of these person instances will have the name "Dave", you should set this name on the instance itself.
class Person
def initialize(name)
#name = name
end
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
david = Person.new("David")
p david.greeting
# => "Hello David"
mike = Person.new("Mike")
p mike.greeting
# => "Hello Mike"

Ruby assign context to lambda?

Is it possible not to assign context to lambda?
For example:
class Rule
def get_rule
return lambda {puts name}
end
end
class Person
attr_accessor :name
def init_rule
#name = "ruby"
Rule.new.get_rule.call() # should say "ruby" but say what object of class Rull, does not have variable name
# or self.instance_eval &Rule.new.get_rule
end
end
My target is -> stored procedure objects without contexts, and assign context before call in specific places. Is it possible?
A bit late to party, but here's an alternate way of doing this by explicitly passing the context to the rule.
class Rule
def get_rule
return lambda{|context| puts context.name}
end
end
class Person
attr_accessor :name
def init_rule
#name = "ruby"
Rule.new.get_rule.call(self)
end
end
Person.new.init_rule
#=> ruby
Yeah, but be careful with it, this one is really easy to abuse. I would personally be apprehensive of code like this.
class Rule
def get_rule
Proc.new { puts name }
end
end
class Person
attr_accessor :name
def init_rule
#name = "ruby"
instance_eval(&Rule.new.get_rule)
end
end
In the spirit of being really late to the party ;-)
I think the pattern that you are using here is the Strategy pattern.
This separates the concerns between the code that changes "rules" and
the part that is re-used "person". The other strength of this pattern is
that you can change the rules at run-time.
How it could look
class Person
attr_accessor :name
def initialize(&rules)
#name = "ruby"
instance_eval(&rules)
end
end
Person.new do
puts #name
end
=> ruby

Resources