There's a convention to reference an object's attributes over its instance variables, where possible. Practical Object-Oriented Design in Ruby says:
Always wrap instance variables in accessor methods instead of directly
referring to variables...
This is shown with an example, which I've paraphrased:
class Gear
attr_reader :chainring, :cog
...
def ratio
# this is bad
# #chainring / #cog.to_f
# this is good
chainring / cog.to_f
end
The most common way I see to create a new object with an instance variable is this:
class Book
attr_accessor :title
def initialize(title)
#title = title
end
end
#title= directly accesses the instance variable title. Assuming we are following the the 'attribute over instance variable' convention, is it more appropriate to use self.title=, which would tell the object to send itself the message title=, thereby using the attribute write method, over the instance variable directly?
class Book
attr_accessor :title
def initialize(title)
self.title = title
end
end
The book talks about 'attribute over instance variable' with reference to reading an instance variable, but doesn't it also apply to writing?
The book talks about 'attribute over instance variable' with reference
to reading an instance variable, but doesn't it also apply to writing?
Yes, it also applies to writing. However, the initialize method is special because it is responsible for setting up the object. When you use a setter method, you do that because the setter might be doing some extra work (e.g. attribute-setters in Rails). In an initializer, you usually don't want to have any side effects, so you access instance variables directly.
At first, in some case self.feature= is more preferably than #feature=, generally in that case, when assignment to feature property shell do more actions, then just assignment. For example, in a database access methods.
At second, in the good ruby style guide you can see that good style self.feature= is encontered only once, when the complex assignment is occurred. It means that not just assisnment to a instance variable. In case of read, construction like feature == "value" is always used.
Related
As a beginner, I've not quite got my head around self so I'm having trouble understanding how the self.blogs in initialize, and blogs then self.blogs on the next line after in the add_blog method, are all working together in the below code.
Why does blogs in the add_blog method access the same variable as self.blogs in initalize?
And then why is self.blogs used afterwards to sort the blogs array?
Also, would it matter if I used #blogs in initialize, instead of self.blogs?
class User
attr_accessor :username, :blogs
def initialize(username)
self.username = username
self.blogs = []
end
def add_blog(date, text)
added_blog = Blog.new(date, self, text)
blogs << added_blog
self.blogs = blogs.sort_by { |blog| blog.date }.reverse
added_blog
end
end
To answer your question, we have to reveal the true nature of attr_accessor.
class Foo
attr_accessor :bar
end
is completely equivalent to
class Foo
def bar
#bar
end
def bar=(value)
#bar = value
end
end
You can see that attr_accessor :bar defines two instance methods Foo#bar and Foo#bar= that access an instance variable #bar.
Lets then look at your code.
self.blogs = [] in initialize is actually calling the method User#blogs=, and through it sets the instance variable #blogs with an empty array. It can be written as self.blogs=([]) but it's noisy, isn't it? By the way, you can't omit self. here otherwise it just sets a local variable.
blogs << added_blog calls the method User#blog which returns the value of #blogs. It can also be written as self.blogs().push(added_blog), but again it's not rubyish. You can omit self. because there is no local variable named blogs in User#add_blog, so ruby falls back to call the instance method.
self.blogs = blogs.sort_by { |blog| blog.date }.reverse mixes call to User#blogs= and User#blogs.
For most method calls on self, self.method_name is equivalent to just method_name. That's not the case for methods whose name ends with an =, though.
The first thing to note, then, is that self.blogs = etc doesn't call a method named blogs and then somehow 'assign etc to it'; that line calls the method blogs=, and passes etc to it as an argument.
The reason you can't shorten that to just blogs = etc, like you can with other method calls, is because blogs = etc is indistinguishable from creating a new local variable named blogs.
When, on the previous line, you see a bare blogs, that is also a method call, and could just as easily have been written self.blogs. Writing it with an implicit receiver is just shorter. Of course, blogs is also potentially ambiguous as the use of a local variable, but in this case the parser can tell it's not, since there's no local variable named blogs assigned previously in the method (and if there had been, a bare blogs would have the value of that local variable, and self.blogs would be necessary if you had meant the method call).
As for using #blogs = instead of self.blogs =, in this case it would have the same effect, but there is a subtle difference: if you later redefine the blogs= method to have additional effects (say, writing a message to a log), the call to self.blogs = will pick up those changes, whereas the bare direct access will not. In the extreme case, if you redefine blogs= to store the value in a database rather than an instance variable, #blogs = won't even be similar anymore (though obviously that sort of major change in infrastructure will probably have knock-on effects internal to the class regardless).
#variable will directly access the instance variable for that class. Writing self.variable will send to the object a message variable. By default it will return the instance variable but it could do other things depending on how you set up your object. It could be a call to a method, or a subclass, or anything else.
The difference between calling blogs or self.blogs is totally up to syntax. If you use an opinionated syntax checker like rubocop it will tell you that you have a redundant use of self
I am learning Ruby OOP and have been faced with the following question.
What could we add to the class below to access the instance variable
#volume?
class Cube
def initialize(volume)
#volume = volume
end
end
My initial thought was to add attr_reader :volume to access the instance variable.
Instead the model answer suggests adding a new method as below.
def get_volume
#volume
end
Why is this the preferred method?
Both methods would output 100 if cube.volume or cube.get_volume were called.
attr_reader. In general methods with get_ prefix are rather avoided in Ruby community (in opposite to commonly seen in Java/C# code)
If it is a dynamically created variable, then you can use instance_vairable_get, like below -
instance_variable_get("#volume")
Since it is desired to get the variable volume ,I will suggest the use of attr_reader :volume. This creates a proxy method volume so no need to add( and later have to maintain) an extra method get_volume for this sole purpose.
https://ruby-doc.org/core-2.1.1/Module.html#method-i-attr_reader
You should use attr_reader :volume instead of using get_volume method.
Stil attr_reader and your method get_volume both are functioning same.
Generally get_ methods should be avoided in ruby.
class Human
#core = "heart"
def cardiovascular
arr = ['heart','blood','lungs']
core = #core
end
end
Is the only way I am able to access #core directly by using this:
Human.instance_variable_get(:#core) #=> "heart"
My understanding is that an instance variable is accessible from anywhere within the scope of the Class.
I am able to access the method by: Human.new.cardiovascular and I expect the return to be "heart" but the return I get is nil
How come I can't access the instance variable as Human.core or Human.new.core?
Why does the Human.new.cardiovascular return nil? (Shouldn't core == #core?)
Update
After putting #core within an initialize block I see the following output in IRB:
Human.new
=> #<Human:0x2f1f030 #core="heart">
This makes sense as it's now available to the entire Class, but how do I access the specific instance variables within the initialize block? Meaning, how do I get just: #core without calling the cardiovascular method in this case?
Do this:
class Human
def initialize
#core = "heart"
end
def cardiovascular
arr = ['heart','blood','lungs']
core = #core
end
end
In short, you're setting instance variable on a class itself (which is an object too), but you want it on an instance.
Also..
How come I can't access the instance variable as Human.core or Human.new.core?
Because, in Ruby, only methods can be accessed as such - not variables/fields. In this aspect, Ruby acts like SmallTalk (and unlike Python, Java, and many other OOP languages). The "accessor" (e.g. attr_accessor) methods create applicable proxies.
Why does the Human.new.cardiovascular return nil? (Shouldn't core == #core?)
Because #core in context is nil as explained in the other answers (and observations), however, the assignment core = #core is useless in the context (and this ties in with the previous question) because the local variable assignments are only accessible within that scope.
Note the correctness of the observation(s) - e.g. Human.instance_variable_get(:#core) works because it is an instance variable for the receiver (the Human class which is also an object), even if not for any instance of said class. Continue with this logic analysis, it's right :)
I have a class something like below, and I used instance variables (array) to avoid using lots of method parameters.
It works as I expected but is that a good practice?
Actually I wouldn't expect that worked, but I guess class methods are not working as static methods in other languages.
class DummyClass
def self.dummy_method1
#arr = []
# Play with that array
end
def self.dummy_method2
# use #arr for something else
end
end
The reason instance variables work on classes in Ruby is that Ruby classes are instances themselves (instances of class Class). Try it for yourself by inspecting DummyClass.class. There are no "static methods" in the C# sense in Ruby because every method is defined on (or inherited into) some instance and invoked on some instance. Accordingly, they can access whatever instance variables happen to be available on the callee.
Since DummyClass is an instance, it can have its own instance variables just fine. You can even access those instance variables so long as you have a reference to the class (which should be always because class names are constants). At any point, you would be able to call ::DummyClass.instance_variable_get(:#arr) and get the current value of that instance variable.
As for whether it's a good thing to do, it depends on the methods.
If #arr is logically the "state" of the instance/class DummyClass, then store it in instance variable. If #arr is only being used in dummy_method2 as an operational shortcut, then pass it as an argument. To give an example where the instance variable approach is used, consider ActiveRecord in Rails. It allows you to do this:
u = User.new
u.name = "foobar"
u.save
Here, the name that has been assigned to the user is data that is legitimately on the user. If, before the #save call, one were to ask "what is the name of the user at this point", you would answer "foobar". If you dig far enough into the internals (you'll dig very far and into a lot of metaprogramming, you'll find that they use instance variables for exactly this).
The example I've used contains two separate public invocations. To see a case where instance variables are still used despite only one call being made, look at the ActiveRecord implementation of #update_attributes. The method body is simply load(attributes, false) && save. Why does #save not get passed any arguments (like the new name) even though it is going to be in the body of save where something like UPDATE users SET name='foobar' WHERE id=1;? It's because stuff like the name is information that belongs on the instance.
Conversely, we can look at a case where instance variables would make no sense to use. Look at the implementation of #link_to_if, a method that accepts a boolean-ish argument (usually an expression in the source code) alongside arguments that are ordinarily accepted by #link_to such as the URL to link to. When the boolean condition is truthy, it needs to pass the rest of the arguments to #link_to and invoke it. It wouldn't make much sense to assign instance variables here because you would not say that the invoking context here (the renderer) contains that information in the instance. The renderer itself does not have a "URL to link to", and consequently, it should not be buried in an instance variable.
Those are class instance variables and are a perfectly legitimate things in ruby: classes are objects too (instances of Class) and so have instance variables.
One thing to look out for is that each subclass will have its own set of class instance variables (after all these are different objects): If you subclassed DummyClass, class methods on the subclass would not be able to see #arr.
Class variables (##foo) are of course the other way round: the entire class hierarchy shares the same class variables.
Is there any way to make instance variables "private"(C++ or Java definition) in ruby? In other words I want following code to result in an error.
class Base
def initialize()
#x = 10
end
end
class Derived < Base
def x
#x = 20
end
end
d = Derived.new
Like most things in Ruby, instance variables aren't truly "private" and can be accessed by anyone with d.instance_variable_get :#x.
Unlike in Java/C++, though, instance variables in Ruby are always private. They are never part of the public API like methods are, since they can only be accessed with that verbose getter. So if there's any sanity in your API, you don't have to worry about someone abusing your instance variables, since they'll be using the methods instead. (Of course, if someone wants to go wild and access private methods or instance variables, there isn’t a way to stop them.)
The only concern is if someone accidentally overwrites an instance variable when they extend your class. That can be avoided by using unlikely names, perhaps calling it #base_x in your example.
Never use instance variables directly. Only ever use accessors. You can define the reader as public and the writer private by:
class Foo
attr_reader :bar
private
attr_writer :bar
end
However, keep in mind that private and protected do not mean what you think they mean. Public methods can be called against any receiver: named, self, or implicit (x.baz, self.baz, or baz). Protected methods may only be called with a receiver of self or implicitly (self.baz, baz). Private methods may only be called with an implicit receiver (baz).
Long story short, you're approaching the problem from a non-Ruby point of view. Always use accessors instead of instance variables. Use public/protected/private to document your intent, and assume consumers of your API are responsible adults.
It is possible (but inadvisable) to do exactly what you are asking.
There are two different elements of the desired behavior. The first is storing x in a read-only value, and the second is protecting the getter from being altered in subclasses.
Read-only value
It is possible in Ruby to store read-only values at initialization time. To do this, we use the closure behavior of Ruby blocks.
class Foo
def initialize (x)
define_singleton_method(:x) { x }
end
end
The initial value of x is now locked up inside the block we used to define the getter #x and can never be accessed except by calling foo.x, and it can never be altered.
foo = Foo.new(2)
foo.x # => 2
foo.instance_variable_get(:#x) # => nil
Note that it is not stored as the instance variable #x, yet it is still available via the getter we created using define_singleton_method.
Protecting the getter
In Ruby, almost any method of any class can be overwritten at runtime. There is a way to prevent this using the method_added hook.
class Foo
def self.method_added (name)
raise(NameError, "cannot change x getter") if name == :x
end
end
class Bar < Foo
def x
20
end
end
# => NameError: cannot change x getter
This is a very heavy-handed method of protecting the getter.
It requires that we add each protected getter to the method_added hook individually, and even then, you will need to add another level of method_added protection to Foo and its subclasses to prevent a coder from overwriting the method_added method itself.
Better to come to terms with the fact that code replacement at runtime is a fact of life when using Ruby.
Unlike methods having different levels of visibility, Ruby instance variables are always private (from outside of objects). However, inside objects instance variables are always accessible, either from parent, child class, or included modules.
Since there probably is no way to alter how Ruby access #x, I don't think you could have any control over it. Writing #x would just directly pick that instance variable, and since Ruby doesn't provide visibility control over variables, live with it I guess.
As #marcgg says, if you don't want derived classes to touch your instance variables, don't use it at all or find a clever way to hide it from seeing by derived classes.
It isn't possible to do what you want, because instance variables aren't defined by the class, but by the object.
If you use composition rather than inheritance, then you won't have to worry about overwriting instance variables.
If you want protection against accidental modification. I think attr_accessor can be a good fit.
class Data
attr_accessor :id
private :id
end
That will disable writing of id but would be readable. You can however use public attr_reader and private attr_writer syntax as well. Like so:
class Data
attr_reader :id
private
attr_writer :id
end
I know this is old, but I ran into a case where I didn't as much want to prevent access to #x, I did want to exclude it from any methods that use reflection for serialization. Specifically I use YAML::dump often for debug purposes, and in my case #x was of class Class, which YAML::dump refuses to dump.
In this case I had considered several options
Addressing this just for yaml by redefining "to_yaml_properties"
def to_yaml_properties
super-["#x"]
end
but this would have worked just for yaml and if other dumpers (to_xml ?) would not be happy
Addressing for all reflection users by redefining "instance_variables"
def instance_variables
super-["#x"]
end
Also, I found this in one of my searches, but have not tested it as the above seem simpler for my needs
So while these may not be exactly what the OP said he needed, if others find this posting while looking for the variable to be excluded from listing, rather than access - then these options may be of value.