Should a ruby method modify a class's instance method? - ruby

I am writing a class in Ruby where I have instance variables (i.e. #person_summary_info, #name, #dob, #favorite_food) for the class.
To parse a piece of text, I have a public method that I call from outside the class (let's call it interpret).
This method calls some private class methods such as get_name that use #person_summary_info to extract the respective piece of information (in this case, the name of the person). Should those private methods:
a) use the instance #person_summary_info, or get that information through a parameter passed to them (i.e. get_name vs get_name(person_summary_info))
b) modify the instance variable directly and return nothing, or modify nothing outside the scope of the function, and return the result (i.e. inside get_name, set #name = 'John', or return 'John')?
What is the best practice here?
Thanks!

I have included my best representation of your question in code at the bottom of my answer, but I'd like to present my solution as I understand your dilemma first...
Do this if your name attribute is meant to be publicly accessible:
class Person
attr_accessor :name
def initialize(name)
#name = name
end
def interpret(text_to_parse)
# I have no idea what you are parsing in real life
self.name = text_to_parse.split.last
end
end
person = Person.new("Frederick")
puts person.name
# => "Frederick"
person.interpret("Please, call me Fred")
puts person.name
# => "Fred"
Do this if your name attribute should not be (easily) publicly accessible: (For what it's worth, pretty much anything can be accessed one way or another in Ruby. One of the many things that make it awesome!)
class Person
def initialize(name)
#name = name
end
def interpret(text_to_parse)
# I have no idea what you are parsing in real life
#name = text_to_parse.split.last
end
end
person = Person.new("Frederick")
puts person.instance_variable_get("#name")
# => "Frederick"
person.interpret("Please, call me Fred")
puts person.instance_variable_get("#name")
# => "Fred"
And, as mentioned above, here's my best translation of your question into code:
class Person
def initialize
#person_summary_info = { name: "foo" }
#name = "bar"
#dob = "baz"
#favorite_food = "beer"
end
def interpret(text_to_parse)
# Some kind of parsing?
get_name_1
# OR
get_name_2(#person_summary_info)
# OR
get_name_3
# OR
#name = get_name_4
end
private
def get_name_1
#person_summary_info[:name]
end
def get_name_2(person_summary_info)
person_summary_info[:name]
end
def get_name_3
#name = 'John'
end
def get_name_4
'John'
end
end
Hopefully, you can see why there's some confusion in the comments about what you are asking exactly. If nothing else, maybe seeing this will help you to form your question more clearly so we can help!
Finally, you should avoid writing your own getters/setters in Ruby unless you need to hook in some custom code to the getting/setting processes -- use the class-level attr_reader/attr_writer/attr_accessor macros to create them for you.

If interpret() is not meant to change the state of a particular instance of Person, then consider naming the method something like get_name_from_string(string) and possibly making it static, since it doesnt do anything to the state of the instance.
If you want interpret() to change the state of a particular instance of Person, then consider changing the name of the method, prefixing it with set and include the attribute name being set (set_name_from_string()). If several attributes are being set, then perhaps set_from_string() and include a code comment stating what instance variables are being modified. Internally the method could call get/set_name() as described below.
Typically, getter/setter methods are public and should be quite simple, doing what their name suggests:
- getName() returns the instance variable #name
- setName(name) sets or overwrites the instance variable #name with the value passed in and returns nothing
In Java, this is a type of POJO, specifically Java Beans (excluding the part about needing to be serializable) Its very common programming practice in several different languages to have public setter/getter methods for the instance variables and to also have a default constructor (one that takes no arguments) and another constructor allowing you to set the instance variables upon instantiation of the Object.

using #instance directly from another class is a good way how to get into troubles. Each class should have it's own variables and anything you would like to process or return back should be assigned/returned directly.. that means that way
#instance = my_class.get_name(person_summary_info)
and not
my_class.get_name
Just try to imagine how to test that code using #instance variables and chance to reuse that piece of code..
just my 2c

Related

Ruby: When defining a class is "initialize" for convenience?

I'm new to programming and am trying to figure out the purpose of "initialize" in creating a class.
Here's an example:
class Person
def initialize(name)
#name = name
#pet = nil
#home = 'NYC'
end
end
So initializing is to create a bunch of attributes that I can pull out directly by saying Person.name and Person.pet and Person.home right? Is "initialize" just to compact a bunch of variables into one place? Would I accomplish the same thing doing this:
class Person
pet = nil
home = 'NYC'
#not so sure how to replicate the #name here.
end
Wouldn't I be able to access the values with Person.pet and Person.home the same way as I would in the first code?
This is a little tricky in Ruby (as opposed to, say, Java) since both classes and instances of classes are actual objects at runtime. As such, a class has its own set of variables, and each instance of that class also gets its own set of variables (distinct from the class's variables).
When you say
class Person
pet = nil
end
You're setting a variable, pet, which is local only to the class object called Person.
The way to manipulate the variables of an instance of a class is to use the variables in methods:
class Person
def initialize
pet = nil
end
end
Here, pet refers to a local variable of a given instance of Person. Of course, this pet variable is pretty useless as defined, since it's just a local variable that goes away after the initialize function completes. The way to make this variable persist for the lifetime of the instance is to make it an instance variable, which you accomplish by prefixing it with a #. And thus we arrive at your first initialize:
class Person
def initialize
#pet = nil
# And so on
end
end
So, as to why you need initialize. Since the only way to set the instance variables of instances of Person is within methods of Person, this initialization needs to be in some method. initialize is just the convenient name for a method which is automatically called when your instance is first created.
Initialize is a method usually referred as an object constructor. It is used when you call Person.new("Bob") and it will give you an instance of that Person object. The # symbol you see before the variables in the initialize makes the variable an instance variable meaning that variable will only be accessed once you have an instance of that object and it will stay there for the lifetime of that instance.
For example
person = Person.new("Bob")
person.name #Will output Bob
person.home #Will output NYC
Classes are objects and doing this:
class Person
pet = nil
home = 'NYC'
end
is just creating local variables pet and home and will be outside of the scope of the class. This means calling Person.pet and Person.home will just give you an error. I would suggest do a little reading on Object Oriented Programming (OOP) and if you have any more questions throw them in stackoverflow :D

What convention should I use for instance variables?

Is there any instance variable convention used in Ruby code? Namely, I noticed that, in the examples section, instance variable are initialized using the '#' and then the code uses a variable name without the '#':
class BookInStock
attr_reader: isbn
attr_accessor: price
def initialize (isbn, price)
#isbn = isbn
#price = Float (price)
end
def price_in_cents
Integer (price * 100 + 0.5)
end
end
And there are examples where the code, the instance variable is used all the time with the prefix '#'
class BookStock
def set_price (price)
#price = price
end
def get_price
#price
end
end
What is the difference these records? When should I use '#' only in the initialization of an object and when in all the class methods?
You can only use the instance variable name without # if you have defined an attribute reader for this variable (with attr_reader or attr_accessor). This is a lightweight helper to create a method that returns the instance variable's value.
The difference is a question between using the public interface of your class or accessing private data. It is recommended to use the public interface in subclasses or included modules, for example. This ensures encapsulation. So you should not access instance variables from within subclasses or modules.
In the class itself, ask yourself this question: if the implementation of the attribute changes, should my usage the attribute change as well? If the answer is yes, use the instance variable directly; if it's no, use the public attribute.
An example. Suppose we have a Person class that represents a person and should contain that person's name. First there is this version, with only a simple name attribute:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def blank?
!#name
end
def to_s
name
end
end
Note how we use the instance variable for the blank? method, but the public attribute for the to_s method. This is intentional!
Let's say that in a refactoring step we decide that the Person class should keep track of a first_name and last_name seperately.
class Person
def initialize(first_name, last_name)
#first_name, #last_name = first_name, last_name
end
def name
"#{#first_name} #{#last_name}"
end
def blank?
!#first_name and !#last_name
end
def to_s
name
end
end
We now change the name attribute to a regular method, which composes the name from a person's first and last name. We can now see that it is possible to keep the same implementation for to_s. However, blank? needs to be changed because it was dependent on the underlying data. This is the reason why to_s used the attribute, but blank? used the instance variable.
Of course, the distinction is not always easy to make. When in doubt, choose the public API over accessing private data.
In your first example, #price is an instance variable.
In the method definition for price_in_cents, price is actually a method call to price(). Because of Ruby's syntactic sugar, () is omitted.
It looks like there's no explicit definition for price(), but attr_accessor :price defined both price() and price=(value) a.k.a. getter and setter. You can try to comment out the attr_accessor :price line, you will get an "undefined local variable or method" exception when the BookInStock's instance calls price_in_cents.
An instance variable is always have an # prefix. If no accessor methods defined, you can not use that name without # prefix.
Using name = value is an error, because that creates a local variable named name. You must use self.name = value.
As for convention, you can only get away with using #name if you can guarantee that the accessors will always be lightweight attr_accessors. In all other cases, using #name over self.name will violate encapsulation and give yourself a headache. You gave the exact reason in your question — if there is extra logic in the getter/setter, you must duplicate it if you access the instance variable directly.

What is the conventional way to access instance variables within an object in Ruby?

Consider the following code:
class Dog
attr_accessor :name, :color
def initialize(name, color)
end
end
Within a Ruby object, is the convention to access the instance variable directly (ie #name = name) or to use the setter/getter methods (ie name = name)?
The former is more clear to me, but if you implement your own setter/getter methods (eg to increment a class variable at the same time) then you end up having to use both approaches (ie #name = name ; color = color).
What's the convention within the Ruby community? How should I write my code to make it clear to others who will read it?
Using name = value is an error, because that creates a local variable named name. You must use self.name = value.
As for convention, you can only get away with using #name if you can guarantee that the accessors will always be lightweight attr_accessors. In all other cases, using #name over self.name will violate encapsulation and give yourself a headache. You gave the exact reason in your question — if there is extra logic in the getter/setter, you must duplicate it if you access the instance variable directly.
It would be #name. It's more than just a convention. The # sign defines the scope of the variable to within an instance of the object of which it's defined.
What you have done is the most conventional way, I believe.
Actually attr_accessor creates two instance methods for read and write. It creates 2 methods like:
def color() #getter
#color
end
and
def color=(color) #setter
#color = color
end
And something like color = color wont work, because then color will be treated as a local variable if it's found on the left of an assignment. You can use self.color = color though.
Again, if the color is not in the left side of any expression, it'll work fine just like the following:
def show_color
puts color
end

What is attr_accessor in Ruby?

I am having a hard time understanding attr_accessor in Ruby.
Can someone explain this to me?
Let's say you have a class Person.
class Person
end
person = Person.new
person.name # => no method error
Obviously we never defined method name. Let's do that.
class Person
def name
#name # simply returning an instance variable #name
end
end
person = Person.new
person.name # => nil
person.name = "Dennis" # => no method error
Aha, we can read the name, but that doesn't mean we can assign the name. Those are two different methods. The former is called reader and latter is called writer. We didn't create the writer yet so let's do that.
class Person
def name
#name
end
def name=(str)
#name = str
end
end
person = Person.new
person.name = 'Dennis'
person.name # => "Dennis"
Awesome. Now we can write and read instance variable #name using reader and writer methods. Except, this is done so frequently, why waste time writing these methods every time? We can do it easier.
class Person
attr_reader :name
attr_writer :name
end
Even this can get repetitive. When you want both reader and writer just use accessor!
class Person
attr_accessor :name
end
person = Person.new
person.name = "Dennis"
person.name # => "Dennis"
Works the same way! And guess what: the instance variable #name in our person object will be set just like when we did it manually, so you can use it in other methods.
class Person
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
person = Person.new
person.name = "Dennis"
person.greeting # => "Hello Dennis"
That's it. In order to understand how attr_reader, attr_writer, and attr_accessor methods actually generate methods for you, read other answers, books, ruby docs.
attr_accessor is just a method. (The link should provide more insight with how it works - look at the pairs of methods generated, and a tutorial should show you how to use it.)
The trick is that class is not a definition in Ruby (it is "just a definition" in languages like C++ and Java), but it is an expression that evaluates. It is during this evaluation when the attr_accessor method is invoked which in turn modifies the current class - remember the implicit receiver: self.attr_accessor, where self is the "open" class object at this point.
The need for attr_accessor and friends, is, well:
Ruby, like Smalltalk, does not allow instance variables to be accessed outside of methods1 for that object. That is, instance variables cannot be accessed in the x.y form as is common in say, Java or even Python. In Ruby y is always taken as a message to send (or "method to call"). Thus the attr_* methods create wrappers which proxy the instance #variable access through dynamically created methods.
Boilerplate sucks
Hope this clarifies some of the little details. Happy coding.
1 This isn't strictly true and there are some "techniques" around this, but there is no syntax support for "public instance variable" access.
attr_accessor is (as #pst stated) just a method. What it does is create more methods for you.
So this code here:
class Foo
attr_accessor :bar
end
is equivalent to this code:
class Foo
def bar
#bar
end
def bar=( new_value )
#bar = new_value
end
end
You can write this sort of method yourself in Ruby:
class Module
def var( method_name )
inst_variable_name = "##{method_name}".to_sym
define_method method_name do
instance_variable_get inst_variable_name
end
define_method "#{method_name}=" do |new_value|
instance_variable_set inst_variable_name, new_value
end
end
end
class Foo
var :bar
end
f = Foo.new
p f.bar #=> nil
f.bar = 42
p f.bar #=> 42
attr_accessor is very simple:
attr_accessor :foo
is a shortcut for:
def foo=(val)
#foo = val
end
def foo
#foo
end
it is nothing more than a getter/setter for an object
Basically they fake publicly accessible data attributes, which Ruby doesn't have.
It is just a method that defines getter and setter methods for instance variables. An example implementation would be:
def self.attr_accessor(*names)
names.each do |name|
define_method(name) {instance_variable_get("##{name}")} # This is the getter
define_method("#{name}=") {|arg| instance_variable_set("##{name}", arg)} # This is the setter
end
end
If you are familiar with OOP concept, You must familiar with getter and setter method.
attr_accessor does the same in Ruby.
Getter and Setter in General Way
class Person
def name
#name
end
def name=(str)
#name = str
end
end
person = Person.new
person.name = 'Eshaan'
person.name # => "Eshaan"
Setter Method
def name=(val)
#name = val
end
Getter method
def name
#name
end
Getter and Setter method in Ruby
class Person
attr_accessor :name
end
person = Person.new
person.name = "Eshaan"
person.name # => "Eshaan"
Simple Explanation Without Any Code
Most of the above answers use code. This explanation attempts to answer it without using any, via an analogy/story:
Outside parties cannot access internal CIA secrets
Let's imagine a really secret place: the CIA. Nobody knows what's happening in the CIA apart from the people inside the CIA. In other words, external people cannot access any information in the CIA. But because it's no good having an organisation that is completely secret, certain information is made available to the outside world - only things that the CIA wants everyone to know about of course: e.g. the Director of the CIA, how environmentally friendly this department is compared to all other government departments etc. Other information: e.g. who are its covert operatives in Iraq or Afghanistan - these types of things will probably remain a secret for the next 150 years.
If you're outside the CIA you can only access the information that it has made available to the public. Or to use CIA parlance you can only access information that is "cleared".
The information that the CIA wants to make available to the general public outside the CIA are called: attributes.
The meaning of read and write attributes:
In the case of the CIA, most attributes are "read only". This means if you are a party external to the CIA, you can ask: "who is the director of the CIA?" and you will get a straight answer. But what you cannot do with "read only" attributes is to make changes changes in the CIA. e.g. you cannot make a phone call and suddenly decide that you want Kim Kardashian to be the Director, or that you want Paris Hilton to be the Commander in Chief.
If the attributes gave you "write" access, then you could make changes if you want to, even if you were outside. Otherwise, the only thing you can do is read.
In other words accessors allow you to make inquiries, or to make changes, to organisations that otherwise do not let external people in, depending on whether the accessors are read or write accessors.
Objects inside a class can easily access each other
On the other hand, if you were already inside the CIA, then you could easily call up your CIA operative in Kabul because this information is easily accessible given you are already inside. But if you're outside the CIA, you simply will not be given access: you will not be able to know who they are (read access), and you will not be able to change their mission (write access).
Exact same thing with classes and your ability to access variables, properties and methods within them. HTH! Any questions, please ask and I hope i can clarify.
I faced this problem as well and wrote a somewhat lengthy answer to this question. There are some great answers on this already, but anyone looking for more clarification, I hope my answer can help
Initialize Method
Initialize allows you to set data to an instance of an object upon creation of the instance rather than having to set them on a separate line in your code each time you create a new instance of the class.
class Person
def initialize(name)
#name = name
end
def greeting
"Hello #{#name}"
end
end
person = Person.new("Denis")
puts person.greeting
In the code above we are setting the name “Denis” using the initialize method by passing Dennis through the parameter in Initialize. If we wanted to set the name without the initialize method we could do so like this:
class Person
attr_accessor :name
# def initialize(name)
# #name = name
# end
def greeting
"Hello #{name}"
end
end
person = Person.new
person.name = "Dennis"
puts person.greeting
In the code above, we set the name by calling on the attr_accessor setter method using person.name, rather than setting the values upon initialization of the object.
Both “methods” of doing this work, but initialize saves us time and lines of code.
This is the only job of initialize. You cannot call on initialize as a method. To actually get the values of an instance object you need to use getters and setters (attr_reader (get), attr_writer(set), and attr_accessor(both)). See below for more detail on those.
Getters, Setters (attr_reader, attr_writer, attr_accessor)
Getters, attr_reader: The entire purpose of a getter is to return the value of a particular instance variable. Visit the sample code below for a breakdown on this.
class Item
def initialize(item_name, quantity)
#item_name = item_name
#quantity = quantity
end
def item_name
#item_name
end
def quantity
#quantity
end
end
example = Item.new("TV",2)
puts example.item_name
puts example.quantity
In the code above you are calling the methods “item_name” and “quantity” on the instance of Item “example”. The “puts example.item_name” and “example.quantity” will return (or “get”) the value for the parameters that were passed into the “example” and display them to the screen.
Luckily in Ruby there is an inherent method that allows us to write this code more succinctly; the attr_reader method. See the code below;
class Item
attr_reader :item_name, :quantity
def initialize(item_name, quantity)
#item_name = item_name
#quantity = quantity
end
end
item = Item.new("TV",2)
puts item.item_name
puts item.quantity
This syntax works exactly the same way, only it saves us six lines of code. Imagine if you had 5 more state attributable to the Item class? The code would get long quickly.
Setters, attr_writer: What crossed me up at first with setter methods is that in my eyes it seemed to perform an identical function to the initialize method. Below I explain the difference based on my understanding;
As stated before, the initialize method allows you to set the values for an instance of an object upon object creation.
But what if you wanted to set the values later, after the instance was created, or change them after they have been initialized? This would be a scenario where you would use a setter method. THAT IS THE DIFFERENCE. You don’t have to “set” a particular state when you are using the attr_writer method initially.
The code below is an example of using a setter method to declare the value item_name for this instance of the Item class. Notice that we continue to use the getter method attr_reader so that we can get the values and print them to the screen, just in case you want to test the code on your own.
class Item
attr_reader :item_name
def item_name=(str)
#item_name = (str)
end
end
The code below is an example of using attr_writer to once again shorten our code and save us time.
class Item
attr_reader :item_name
attr_writer :item_name
end
item = Item.new
puts item.item_name = "TV"
The code below is a reiteration of the initialize example above of where we are using initialize to set the objects value of item_name upon creation.
class Item
attr_reader :item_name
def initialize(item_name)
#item_name = item_name
end
end
item = Item.new("TV")
puts item.item_name
attr_accessor: Performs the functions of both attr_reader and attr_writer, saving you one more line of code.
I think part of what confuses new Rubyists/programmers (like myself) is:
"Why can't I just tell the instance it has any given attribute (e.g., name) and give that attribute a value all in one swoop?"
A little more generalized, but this is how it clicked for me:
Given:
class Person
end
We haven't defined Person as something that can have a name or any other attributes for that matter.
So if we then:
baby = Person.new
...and try to give them a name...
baby.name = "Ruth"
We get an error because, in Rubyland, a Person class of object is not something that is associated with or capable of having a "name" ... yet!
BUT we can use any of the given methods (see previous answers) as a way to say, "An instance of a Person class (baby) can now have an attribute called 'name', therefore we not only have a syntactical way of getting and setting that name, but it makes sense for us to do so."
Again, hitting this question from a slightly different and more general angle, but I hope this helps the next instance of class Person who finds their way to this thread.
Simply put it will define a setter and getter for the class.
Note that
attr_reader :v is equivalant to
def v
#v
end
attr_writer :v is equivalant to
def v=(value)
#v=value
end
So
attr_accessor :v which means
attr_reader :v; attr_writer :v
are equivalant to define a setter and getter for the class.
Simply attr-accessor creates the getter and setter methods for the specified attributes
Another way to understand it is to figure out what error code it eliminates by having attr_accessor.
Example:
class BankAccount
def initialize( account_owner )
#owner = account_owner
#balance = 0
end
def deposit( amount )
#balance = #balance + amount
end
def withdraw( amount )
#balance = #balance - amount
end
end
The following methods are available:
$ bankie = BankAccout.new("Iggy")
$ bankie
$ bankie.deposit(100)
$ bankie.withdraw(5)
The following methods throws error:
$ bankie.owner #undefined method `owner'...
$ bankie.balance #undefined method `balance'...
owner and balance are not, technically, a method, but an attribute. BankAccount class does not have def owner and def balance. If it does, then you can use the two commands below. But those two methods aren't there. However, you can access attributes as if you'd access a method via attr_accessor!! Hence the word attr_accessor. Attribute. Accessor. It accesses attributes like you would access a method.
Adding attr_accessor :balance, :owner allows you to read and write balance and owner "method". Now you can use the last 2 methods.
$ bankie.balance
$ bankie.owner
Despite the large number of existing answers, none of them seems to me to explain the actual mechanism involved here. It's metaprogramming; it takes advantage of the following two facts:
You can modify a module / class on the fly
A module / class declaration is itself executable code
Okay, so imagine the following:
class Nameable
def self.named(whatvalue)
define_method :name do whatvalue end
end
end
We are declaring a class method named which, when called with a value, creates an instance method called name which returns that value. That is the metaprogramming part.
Now we'll subclass that class:
class Dog < Nameable
named "Fido"
end
What on earth did we just do? Well, in the class declaration, executable code executes with reference to the class. So the bare word named is actually a call to the class method named, which we inherited from Nameable; and we are passing the string "Fido" as the argument.
And what does the class method named do? It creates an instance method called name, which returns that value. So now, behind the scenes, Dog has a method that looks like this:
def name
"Fido"
end
Don't believe me? Then watch this little move:
puts Dog.new.name #=> Fido
Why did I tell you all that? Because what I just did with named for Nameable is almost exactly what attr_accessor does for Module. When you say attr_accessor you are calling a class method (inherited from Module) that creates instance methods. In particular, it creates a getter and setter method for the instance property whose name you provide as argument, so that you don't have to write those getter and setter methods yourself.
Defines a named attribute for this module, where the name is symbol.id2name, creating an instance variable (#name) and a corresponding access method to read it. Also creates a method called name= to set the attribute.
module Mod
attr_accessor(:one, :two)
end
Mod.instance_methods.sort #=> [:one, :one=, :two, :two=]
To summarize an attribute accessor aka attr_accessor gives you two free methods.
Like in Java they get called getters and setters.
Many answers have shown good examples so I'm just going to be brief.
#the_attribute
and
#the_attribute=
In the old ruby docs a hash tag # means a method.
It could also include a class name prefix...
MyClass#my_method
I am new to ruby and had to just deal with understanding the following weirdness. Might help out someone else in the future. In the end it is as was mentioned above, where 2 functions (def myvar, def myvar=) both get implicitly for accessing #myvar, but these methods can be overridden by local declarations.
class Foo
attr_accessor 'myvar'
def initialize
#myvar = "A"
myvar = "B"
puts #myvar # A
puts myvar # B - myvar declared above overrides myvar method
end
def test
puts #myvar # A
puts myvar # A - coming from myvar accessor
myvar = "C" # local myvar overrides accessor
puts #myvar # A
puts myvar # C
send "myvar=", "E" # not running "myvar =", but instead calls setter for #myvar
puts #myvar # E
puts myvar # C
end
end
Attributes and accessor methods
Attributes are class components that can be accessed from outside the object. They are known as properties in many other programming languages. Their values are accessible by using the "dot notation", as in object_name.attribute_name. Unlike Python and a few other languages, Ruby does not allow instance variables to be accessed directly from outside the object.
class Car
def initialize
#wheels = 4 # This is an instance variable
end
end
c = Car.new
c.wheels # Output: NoMethodError: undefined method `wheels' for #<Car:0x00000000d43500>
In the above example, c is an instance (object) of the Car class. We tried unsuccessfully to read the value of the wheels instance variable from outside the object. What happened is that Ruby attempted to call a method named wheels within the c object, but no such method was defined. In short, object_name.attribute_name tries to call a method named attribute_name within the object. To access the value of the wheels variable from the outside, we need to implement an instance method by that name, which will return the value of that variable when called. That's called an accessor method. In the general programming context, the usual way to access an instance variable from outside the object is to implement accessor methods, also known as getter and setter methods. A getter allows the value of a variable defined within a class to be read from the outside and a setter allows it to be written from the outside.
In the following example, we have added getter and setter methods to the Car class to access the wheels variable from outside the object. This is not the "Ruby way" of defining getters and setters; it serves only to illustrate what getter and setter methods do.
class Car
def wheels # getter method
#wheels
end
def wheels=(val) # setter method
#wheels = val
end
end
f = Car.new
f.wheels = 4 # The setter method was invoked
f.wheels # The getter method was invoked
# Output: => 4
The above example works and similar code is commonly used to create getter and setter methods in other languages. However, Ruby provides a simpler way to do this: three built-in methods called attr_reader, attr_writer and attr_acessor. The attr_reader method makes an instance variable readable from the outside, attr_writer makes it writeable, and attr_acessor makes it readable and writeable.
The above example can be rewritten like this.
class Car
attr_accessor :wheels
end
f = Car.new
f.wheels = 4
f.wheels # Output: => 4
In the above example, the wheels attribute will be readable and writable from outside the object. If instead of attr_accessor, we used attr_reader, it would be read-only. If we used attr_writer, it would be write-only. Those three methods are not getters and setters in themselves but, when called, they create getter and setter methods for us. They are methods that dynamically (programmatically) generate other methods; that's called metaprogramming.
The first (longer) example, which does not employ Ruby's built-in methods, should only be used when additional code is required in the getter and setter methods. For instance, a setter method may need to validate data or do some calculation before assigning a value to an instance variable.
It is possible to access (read and write) instance variables from outside the object, by using the instance_variable_get and instance_variable_set built-in methods. However, this is rarely justifiable and usually a bad idea, as bypassing encapsulation tends to wreak all sorts of havoc.
Hmmm. Lots of good answers. Here is my few cents on it.
attr_accessor is a simple method that helps us in cleaning(DRY-ing) up the repeating getter and setter methods.
So that we can focus more on writing business logic and not worry about the setters and getters.
The main functionality of attr_accessor over the other ones is the capability of accessing data from other files.
So you usually would have attr_reader or attr_writer but the good news is that Ruby lets you combine these two together with attr_accessor. I think of it as my to go method because it is more well rounded or versatile.
Also, peep in mind that in Rails, this is eliminated because it does it for you in the back end. So in other words: you are better off using attr_acessor over the other two because you don't have to worry about being to specific, the accessor covers it all. I know this is more of a general explanation but it helped me as a beginner.
Hope this helped!

How to access instance variables from one class while inside another class

I'm really new to Ruby. And by new - less than 16 hours, but my boss gave me some Ruby code to add to. However, I found it was one giant file and not modular at all, so I decided to clean it up. Now that I've broken it up into several files/classes (generally speaking, 1 class per file,) I'm having problems piecing it together for it to work again. Originally everything was part of the same class, so the calls worked, but it looked ugly and it took an entire work day just to figure it out. I want to avoid that for the future as this code will grow much larger before it is done.
My main issue looks like the following (simplified, obviously):
class TestDevice
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(identifier, level, section, message)
...
#loghash[identifier] = { level => { section => message }}
...
end
end
device = TestDevice.new
After that, it calls out to other class methods, and those class methods reference back to the class Log for their logging needs. Of course, Log needs to access "device.loghash" somehow to log the information in that hash. But I can't figure out how to make that happen outside of passing the contents of "loghash" to every method, so that they, in turn, can pass it, and then return the value back to the origination point and then logging it at the end, but that seems really clumsy and awkward.
I'm hoping I am really just missing something.
To create accessors for instance variables the simple way, use attr_accessor.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
You can also manually define an accessor.
class TestDevice
def loghash
#loghash
end
def loghash=(val)
#loghash = val
end
end
This is effectively what attr_accessor does behind the scenes.
how about passing the device object as a parameter to the msg function? (I'm assuming that there can be many devices in your program, otherwise you can use singleton pattern).
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(device, identifier, level, section, message)
...
device.loghash[identifier] = { level => { section => message }}
...
end
end
So you need to learn the rules of ruby scoping.
Ruby variables have different scope, depending on their prefix:
$global_variables start with a $, and are available to everyone.
#instance_variables start with a single #, and are stored with the current value of self. If two
scopes share the same value of self (they're both instance methods, for example),
then both share the same instance variables
##class_variable start with ##, and are stored with the class. They're
shared between all instances of a class - and all instances of subclasses
of that class.
Constants start with a capital letter, and may be all caps. Like class
variables, they're stored with the current self.class, but they also
trickle up the hierarchy - so if you have a class defined in a module,
the instances of the class can access the module's constants as well.
Constants defined outside of a class have global scope.
Note that a constant variable means that which object is bound to the constant
won't change, not that the object itself won't change internal state.
local_variables start with a lowercase letter
You can read more about scope here.
Local variables scoping rules are mainly standard - they're available in
all subscopes of the one in which they are defined except when we move into
a module, class, or method definition. So if we look at your code from your
answer
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
device = TestDevice.new
class Somethingelse
def self.something
device.loghash='something here' # doesn't work
end
end
The scope of the device local variable defined at the toplevel does not include the Somethingelse.something
method definition. So the device local variable used in the Somethingelse.something method definition is a different (empty) variable. If you want the scoping to work that way, you should use a constant or a global variable.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
DEVICE = TestDevice.new
$has_logged = false
class Somethingelse
def self.something
DEVICE.loghash='something here'
$has_logged = true
end
end
p DEVICE.loghash # prints `{}`
p $has_logged # prints `false`
Somethingelse.something
p DEVICE.loghash # prints `"something here"`
p $has_logged # prints `true`

Resources