Define default accessor of class instance - ruby

Is it possible to define a default accessor for an instance of my class?
I have a class:
class Foo
def initialize(a, b)
#a = a
#b = b
end
end
I want to create a new instance of this class:
foo = Foo.new(:a, :b)
# => #<Foo:0x00007f9e04c7b240 #a=:a, #b=:b>
Creating a new array returns a real value:
arr = Array.new(2, :bar)
# => [:bar, :bar]
How can I set a default accessor of my own class instance so that when I call foo, I get a real value instead of #<Foo:0x00007f9e04c7b240 #a=:a, #b=:b>?

When you see the output on the IRB console, all it's doing is calling inspect on the object. So, all you need to do is (like Array), define an inspect method for you custom object:
class Foo
def initialize(a, b)
#a = a
#b = b
end
def inspect
%["Real value" for Foo with #{#a} and #{#b}]
end
end
foo = Foo.new(:a, :b) # => "Real value" for Foo with a and b
What you see by default, is just the implementation of Object#inspect, so you could if you really wanted to, just override that for all objects (that don't have a custom implementation):
class Object
def inspect
"Custom Inspection of #{self.class.name}"
end
end
# Foo2 is the same as Foo just without the `inspect` method)
foo_2 = Foo2.new(:a, :b) # => Custom Inspection of Foo2
I'd avoid doing this for Object#inspect though, since people are used to and expect to see the default format and changing things might throw them off.

Related

Is attr_reader bad?

I'm learning ruby and my opinion about attr_reader is bad. Because you can change the value of particular instance variable from outside of class. For example:
class X
attr_reader :some_string
def initialize
#some_string = "abc"
end
end
tmp = X.new
puts tmp.some_string
tmp.some_string[0] = "aaaaaaaaa"
puts tmp.some_string
When you run this code you can see that the instance variable some_string has changed. So to avoid this I'm always making my own getter, and returning frozen object. For example:
class X
def initialize
#some_string = "abc"
end
def some_string
#some_string.freeze
end
end
tmp = X.new
puts tmp.some_string
tmp.some_string[0] = "aaaaaaaaa"
puts tmp.some_string
Now when you run the code, it throws an error saying can't modify frozen String: "abc", which is what I wanted. So my question is should I use attr_reader and is always returning frozen objects from getters bad practice?
attr_reader is not "bad", it simply does what it does, which is to return the value of the instance variable, which is a reference. Unlike attr_writer or attr_accessor it will not allow you to change the value of the instance variable (ie you can't change what the instance variable refers to)
The question is do you really want or need the reference. For example say you want to convert the value of some_string to upper case. You could use attr_reader to get the reference to some_string and then call upcase! on it like below. But having the reference allows you to call any method on the object including the []= method which maybe you don't want to allow. Note: []= is a method that manipulates the content of what some_string references it does not set #some_string to a new value, it still points to the same object, but the object it points to was manipulated.
class Foo
attr_reader :some_string
def initialize()
#some_string = "abc"
end
end
puts "Foo"
foo = Foo.new
some_string = foo.some_string
puts some_string #=> abc
some_string.upcase!
p foo # => #<Foo:0x0000563c38391ac8 #some_string="ABC">
puts some_string.object_id # => 60
some_string[0] = "x"
p foo # => #<Foo:0x0000563c38391ac8 #some_string="xBC">
puts some_string.object_id # => 60 ... ie same object different content
# foo.some_string = "ABC" is a runtime error
If you don't want to allow arbitrary methods to be called on an instance variable then you should not expose it using attr_reader, rather you should manipulate the instance variable via methods in your class. For example below I "delegate" the upcase! method to the instance variable #some_string, and I provide a string_value method to return a new string with the same value as the instance variable.
class Bar
def initialize()
#some_string = "abc"
end
def upcase!()
#some_string.upcase!
end
def string_value()
"#{#some_string}"
end
end
puts "Bar"
bar = Bar.new
p bar # => #<Bar:0x0000563c383915a0 #some_string="abc">
bar.upcase!
p bar # => #<Bar:0x0000563c383915a0 #some_string="ABC">
some_string = bar.string_value
p some_string # => "ABC"
some_string[0] = "x"
p bar # => #<Bar:0x0000563c383915a0 #some_string="ABC">
p some_string # => "xBC"
So I would say attr_reader is not bad, you might argue it is over used, I know I will often use it to "get" an instance variable when all I really need is some property on the instance variable.
A lot of developers try to use private attr_reader and use it inside the class or avoid using it at all
A good conversations about attr_reader are here and here

How to call instance variables from one function to another function in same class?

Actually i am new to the oops concepts in ruby, here i want to call the instance variables from one function to another function in same class.but im getting undefined method for second function "one_object"
module User
class Object
class << self
def add_object(items)
#data={}
#data.merge!(items)
end
# i want to use above #data = {} for next one_object method after adding items to the add_object method
def one_object
#data.merge!({"ext"=>"1.0"})
end
end
end
end
a = User::Object.add_object({"txt"=>"text_file","csv"=>"csv_file"})
p a.one_object
Expected Output:
{"txt"=>"text_file", "csv"=>"csv_file", "ext"=>"1.0"}
In Ruby, Object is the root class of all objects. Although you can have your own Object class within User, it could cause a lot of confusion.
Let's simplify your problem by removing the User module (it's not relevant to the example) and by renaming Object to Foo (you'll find a better name). To initialize instance variables you can use the initialize method which is invoked by default every time you construct an object via new:
class Foo
def initialize
#data = {}
end
end
foo = Foo.new
#=> #<Foo:0x00007fb551823d78 #data={}>
# ^^^^^^^^
That hash you assign to #data will be shared among all instance methods. In addition, each Foo instance will have its own #data hash. To merge! another hash into it, you can use:
class Foo
def initialize
#data = {}
end
def add(hash)
#data.merge!(hash)
end
end
foo = Foo.new
#=> #<Foo:0x00007fbc80048230 #data={}>
foo.add({"abc"=>123})
#=> {"abc"=>123}
foo.add({"def"=>456})
#=> {"def"=>456}
foo
#=> #<Foo:0x00007fbc80048230 #data={"abc"=>123, "def"=>456}>
In order to chain multiple add calls (a so-called fluent interface), you have to return self from within the method:
class Foo
def initialize
#data = {}
end
def add(hash)
#data.merge!(hash)
self # <- like this
end
end
foo = Foo.new
#=> #<Foo:0x00007ff7408003d8 #data={}>
foo.add({"abc"=>123}).add({"def"=>456})
#=> #<Foo:0x00007ff7408003d8 #data={"abc"=>123, "def"=>456}>
Finally, to add static data, you could simply call your own method:
class Foo
def initialize
#data = {}
end
def add(hash)
#data.merge!(hash)
self
end
def add_more
add({"more" => 789})
end
end
foo = Foo.new
#=> #<Foo:0x00007f99b20f8590 #data={}>
foo.add({"abc"=>123}).add({"def"=>456}).add_more
#=> #<Foo:0x00007f99b20f8590 #data={"abc"=>123, "def"=>456, "more"=>789}>
You assign the result of add_object to a (i.e. your a is now a Hash), but next you are going to call one_object on a (but one_object is part of User::Object and not of your Hash instance).
If you add to add_object another line, containing self, you will receive your expected output.
With the change, add_object will return User::Object, and you won't run into your initial issue.

Behaviour of instance_eval

My understanding of instance_eval was that if I have module M then the following were equivalent:
module M
def foo
:foo
end
end
class C
class << self
include M
end
end
puts C.foo
equivalent to:
module M
def foo
:foo
end
end
class C
end
C.instance_eval do
include M
end
puts C.foo
However, the first example prints :foo and the second throws a NoMethodError? (Ruby 2.3.0)
In both cases above, if I had replaced:
include M
with:
def foo
:foo
end
ie directly defining the method rather than including a module then both cases would have resulted in a C.foo method being defined. Should I be surprised at this difference between include and defining the method directly?
Or does it ever even make sense to call include within the context of instance_eval? Should it only ever be called within a class_eval?
In each of these cases, what object are you calling include on? In your first example, you're calling include on C's singleton class:
class C
class << self
p self == C.singleton_class
include M
end
end
# => true
p C.foo
# => :foo
...so your include line is equivalent to C.singleton_class.include(M).
In your second example, however, you're calling include on C itself:
class C
end
C.instance_eval do
p self == C
include M
end
# => true
p C.foo
# => NoMethodError: undefined method `foo' for C:Class
p C.new.foo
# => :foo
...so you're doing the equivalent of C.include(M), which is the same as:
class C
p self == C
include M
end
# => true
p C.new.foo
# => :foo
What would work like you want would be to call instance_eval on C's singleton class:
class D
end
D.singleton_class.instance_eval do
p self == D.singleton_class
include M
end
# => true
p D.foo
# => :foo
Module#class_eval() is very different from Object#instance_eval(). The instance_eval() only changes self, while class_eval() changes both self and the current class.
Unlike in your example, you can alter class_instance vars using instance_eval though, because they are in the object scope as MyClass is a singleton instance of class Class.
class MyClass
#class_instance_var = 100
##class_var = 100
def self.disp
#class_instance_var
end
def self.class_var
##class_var
end
def some_inst_method
12
end
end
MyClass.instance_eval do
#class_instance_var = 500
def self.cls_method
##class_var = 200
'Class method added'
end
def inst_method
:inst
end
end
MyClass.disp
#=> 500
MyClass.cls_method
#=> 'Class method added'
MyClass.class_var
#=> 100
MyClass.new.inst_method
# undefined method `inst_method' for #<MyClass:0x0055d8e4baf320>
In simple language.
If you have a look in the upper class defn code as an interpreter, you notice that there are two scopes class scope and object scope. class vars and instance methods are accessible from object scope and does not fall under jurisdiction of instance_eval() so it skips such codes.
Why? because, as the name suggests, its supposed to alter the Class's instance(MyClass)'s properties not other object's properties like MyClass's any object's properties. Also, class variables don’t really belong to classes—they belong to class hierarchies.
If you want to open an object that is not a class, then you can
safely use instance_eval(). But, if you want to open a class definition and define methods with def or include some module, then class_eval() should be your pick.
By changing the current class, class_eval() effectively reopens the class, just like the class keyword does. And, this is what you are trying to achieve in this question.
MyClass.class_eval do
def inst_method
:inst
end
end
MyClass.new.inst_method
#=> :inst

Ruby modify original variable

In C programming I believe they call this pass by reference. What I want to do is this.
class A
attr_accessor :foo
def initialize
#foo = 'foo'
end
def get_b
_b = Object.new
_b.extend B
_b.foo = #foo
end
module B
attr_accessor :foo
def change_foo
#foo = 'bar'
end
end
end
a = A.new
puts a.foo # 'foo'
b = a.get_b
puts b.foo # 'foo'
b.change_foo
puts b.foo # 'bar'
puts a.foo # This should also be 'bar' but is instead still 'foo'
After b.change_foo I would like the value of a.foo to be modified. Is there a way of passing the reference of #foo from class A to module B instead of the value?
With this concrete example of strings, you can make it work.
module B
attr_accessor :foo
def change_foo
# #foo = 'bar' # this won't work
foo.replace('bar')
end
end
When you do a #foo = 'bar', you're completely breaking any connection between foo of B and foo of A. They're now two separate objects.
What the code above does is, instead of making another object, use the reference to the object to call a method on it (which will change its state). Will work equally well with other mutable objects (arrays, hashes, instances of your custom classes). But not with immutable primitives like integers.
Is there a way of passing the reference of #foo from class A to module B instead of the value?
A reference (or a "pointer", in C-speak) is actually passed here. You then overwrite it.
Objects are passed as reference in Ruby, but not pointers to variables. This means that, although you can modify any object, if you change the variable's reference, this change will occur only in the current scope. I'd recommend you to actually change your architecture and use more object-oriented techniques, instead of trying to rely on error-prone language features like this. For example, you can use composition and delegation to implement what you're trying to accomplish:
class A
attr_accessor :foo
def initialize
#foo = 'foo'
end
def get_b
_b = Object.new
_b.extend B
_b.a = self
end
module B
attr_accessor :a
def foo
a.foo
end
def foo=(value)
a.foo = value
end
def change_foo
a.foo = 'bar'
end
end
end
I don't know exactly your purpose, but I'd probably not dynamically extend modules in a factory method like this. I prefer to create classes whose purpose is clear, without depending on context.

Instance variable array containing other instance variables doesn't reflect changes to contained elements

I noticed some weird behavior with instance variables in Ruby the other day. I was trying to add an instance variable array, containing other instance variable "attributes" of the class. The class is initialized without any parameters, but I still wanted to create this array at initialization. Here's an example of a (stripped-down) class:
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
end
end
Here's where it gets weird:
f = Foo.new #=><Foo.0x[object_id] #attrs=[nil, nil]>
f.bar = "bar" #=>"bar"
f.baz = "baz" #=>"baz"
f.attrs #=>[nil, nil]
At initialization, I can see that Foo.attrs is [nil, nil]. But after updating Foo.bar and Foo.baz, why is Foo.attrs still returning [nil, nil]? Why aren't their new values reflected?
I figured this wasn't the best way to do this, and found a way around it, but I'm still curious about this behavior.
Because that's how variables work, here and in virtually every other programming language.
Your array contains the values of #bar and #baz at the time the array was created. It does not contain references to the variables themselves. Modifying one does not modify the other.
Effectively you've done this:
x = 3;
y = x;
x = 4;
# Why doesn't y equal 4?
y is not 4 because x and y share a value but are otherwise unrelated. Reassigning x to a new value does not modify the value that y contains.
If you want this to work, you need to make an accessor that builds the array on-demand, using the current values of your member variables:
class Foo
attr_accessor :bar, :baz
def attrs
[#bar, #baz]
end
end
You can simply add a puts and see what happens
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
puts "inside initialize"
end
end
Now you can see initialize gets executed when you create an instance of Foo
f = Foo.new
#=> inside initialize
#=> #<Foo:0x2bc1bb0 #attrs=[nil, nil]>
f.bar = "bar" #=>"bar" , "inside initialize" not printed
If you do want to get them assigned then create a setter
class Foo
attr_accessor :bar, :baz
attr_reader :attrs
def initialize
#attrs = [#bar, #baz]
puts "inside initialize"
end
def bar=(v)
#bar = v
#attrs = [#bar,#baz]
end
def baz=(v)
#baz = v
#attrs = [#bar,#baz]
end
end
f = Foo.new
#=> inside initialize
#=> #<Foo:0x2bc1bb0 #attrs=[nil, nil]>
f.bar = "bar"
f.attrs #=> ["bar", nil]
f.baz = "baz"
f.attrs #=> ["bar", "baz"]

Resources