I can imagine when you want some attribute to be only readable, or be writable and readable, but not when you want an attribute to be writable and not readable.
In some cases, you might want to read some attributes but not be able to change them. For example, if you have an Animal class and want to initialize it with a name, age, and color, you could write
attr_accessor :age, :color
and then for name have
attr_reader :name
because a name doesn't change. A cat can get older or change color, but the name (for the sake of this question) once assigned should not be changed.
On the other hand, I cannot rationalize a scenario (aside form passwords) where you would need attr_writer (as opposed to attr_accessor or attr_reader). Under what circumstances would you want to write something, but not want to read it? What type of scenario or situation calls for attr_writer?
If you can think of a scenario in other programming languages, that is welcome but my immediate scope of Object Orientation knowledge is within Ruby.
What if you want a trivial writer and a custom reader? It's common to want to memoize a default value when calling a reader if the ivar isn't already set.
class Oof
attr_writer :rab
def rab
#rab ||= "rab, of course!"
end
end
oof = Oof.new
oof.rab # => "rab, of course!"
oof.rab = "zab"
oof.rab # => "zab"
Related
Meta info: Found similar posts, zero were for Ruby.
Created title using multiple terms for search accessibility
Okay now onto the question...
I'm trying to generate new variables (moreso) automatically, I wanted to create a mechanism that did so.
class User
attr_accessor :name
def initialize(name)
#name = name
end
end
def CreateUser(name)
name = User.new(name)
end
CreateUser("Arnold")
p Arnold
I used a single argument in the name parameter of method "CreateUser" as both the name of the variable that I want to declare and the information sent into the code creating a member of my class.
I quickly remembered that in Ruby, variables inside of a method are local to only the method. If that weren't the case, and I could spit out the new variable from the method then I could generate my users easily - but this isn't the case. Is there a work around for this? Or is declaring a new variable manually just the name of the game?
Your question is a little confusing. At first glance, it seems like you're looking for some metaprogramming technique to dynamically create instance variables, such as:
class User
def new_variable name, value
instance_variable_set "##{name}", value
end
end
However, upon looking more closely at the problem you're actually trying to solve, it seems like the real issue is that you aren't making your methods part of your original object. This is probably what you really want:
class User
attr_accessor :name
def initialize name
#name = name
end
def create_user
# do whatever you need to do to "create" a user
pp name
end
end
u = User.new 'Arnold'
u.create_user
Enabling an object to call instance methods on data stored within the object is part of encapsulation. That is most likely what you're really after.
There is a question where #coreyward talks about using it alone, but doesn't give an example of when. Other sources on the web allude to some use cases, but I have yet to find any source that lists a real world example. When would you want to write data but not be able to read it?
It isn't always, necessarily, that you don't want to read it; sometimes the default writer is fine, but you want a custom reader. Take, for instance, this person class:
class Person
attr_writer :name
attr_accessor :title
def initialize(name, title)
#name = name
#title = title
end
def name
"#{#title} #{#name}"
end
end
Here, creating a reader through attr_reader or attr_accessor for name would be redundant, since I'm defining my own name method because there is a custom rule, that I should always display the name with a title.
As an aside, one could argue that my name method should be something more along the lines of name_with_title or what have you, but I still wouldn't actually want the default name method from a reader, because I still want all access to the name to go through this other method.
I'm reading Zed Shaw's Ruby introduction book. I came across a piece of code I don't quite understand.
class Person
def initialize(name)
#name = name
#pet = nil
end
attr_accessor :pet
end
class Employee < Person
def initialize(name, salary)
super(name)
#salary = salary
end
attr_accessor :salary, :name
end
I understand the super(name) part very roughly. I believe that means it defers to the parent class? I'm not sure I understand why this would be done in the real world. Why would someone do super(name) rather than just write #name = name?
I'm still learning. Thanks in advance!
Why would someone do super(name) rather than just write #name = name?
Let's look from another angle. Why would someone duplicate [potentially complex] functionality, which already exists in parent class, rather than simply delegate the work to parent?
If you duplicate the functionality and then requirements change, you need to go change all of the copies. This is error-prone and needlessly expensive (in terms of time/efforts).
I see two different programming best-practices being used here.
First is the best-practice of never duplicating code. You'll often see code like this:
class MyClass
def blah
#blah
end
def do_something
puts blah
end
end
You may wonder "why does the programmer call the method blah() when they could just use the variable #blah instead? The answer is that #blah may be a simple variable right now, but what if you decide to save it in a hash later, accessible as myData[:blah]? It would be a pain to go through every line of code, searching for every #blah and changing it to myData[:blah]. Using the method blah() ensures that if you ever change the way #blah works, you only have to change it in one place: the method.
The same can be said for super(name) vs #name = name. Right now, the initialize() method for Person might be simple, but someday it might become really complicated. You wouldn't want to have to change the code of every class inheriting Person, so it's best to call super().
Second is the best-practice of encapsulation. Imagine for a second that your code looks like this instead:
require "person"
class Employee < Person
def initialize(name, salary)
super(name)
#salary = salary
end
attr_accessor :salary, :name
end
In object-oriented programming, it's common to use libraries made by other people, with classes and objects that you didn't write and don't have the ability to change.
Encapsulation is the idea that a class is a self-sufficient and independent machine. You pass it input data and receive output data, but you have no idea what's going on inside. So in this code, even though Employee inherits Person, Employee shouldn't make any assumptions about what the Person class is doing under the hood. Calling super(name) is trusting Person to set itself up, without worrying about the details of how a Person should be set up.
Even if the two classes are written in the same file and you can see the source code for both, it's important to follow the best practice of encapsulation to keep your code clean and robust for your future self and other programmers, because someday those two classes may be in different files or even different libraries.
I've made a constructor like this:
class Foo
def initialize(p1, p2, opts={})
#...Initialize p1 and p2
opts.each do |k, v|
instance_variable_set("##{k}", v)
end
end
end
I'm wondering if it's a good practice to dynamically set instance variables like this or if I should better set them manually one by one as in most of the libs, and why.
Diagnosing the problem
What you're doing here is a fairly simple example of metaprogramming, i.e. dynamically generating code based on some input. Metaprogramming often reduces the amount of code you need to write, but makes the code harder to understand.
In this particular case, it also introduces some coupling concerns: the public interface of the class is directly related to the internal state in a way that makes it hard to change one without changing the other.
Refactoring the example
Consider a slightly longer example, where we make use of one of the instance variables:
class Foo
def initialize(opts={})
opts.each do |k, v|
instance_variable_set("##{k}", v)
end
end
def greet(name)
greeting = #greeting || "Hello"
puts "#{greeting}, name"
end
end
Foo.new(greeting: "Hi").greet
In this case, if someone wanted to rename the #greeting instance variable to something else, they'd possibly have a hard time understanding how to do that. It's clear that #greeting is used by the greet method, but searching the code for #greeting wouldn't help them find where it was first set. Even worse, to change this bit of internal state they'd also have to change any calls to Foo.new, because the approach we've taken ties the internal state to the public interface.
Remove the metaprogramming
Let's look at an alternative, where we just store all of the opts and treat them as state:
class Foo
def initialize(opts={})
#opts = opts
end
def greet(name)
greeting = #opts.fetch(:greeting, "Hello")
puts "#{greeting}, name"
end
end
Foo.new(greeting: "Hi").greet
By removing the metaprogramming, this clarifies the situation slightly. A new team member who's looking to change this code for the first time is going to have a slightly easier time of things, because they can use editor features (like find-and-replace) to rename the internal ivars, and the relationship between the arguments passed to the initialiser and the internal state is a bit more explicit.
Reduce the coupling
We can go even further, and decouple the internals from the interface:
class Foo
def initialize(opts={})
#greeting = opts.fetch(:greeting, "Hello")
end
def greet(name)
puts "#{#greeting}, name"
end
end
Foo.new(greeting: "Hi").greet
In my opinion, this is the best implementation we've looked at:
There's no metaprogramming, which means we can find explicit references to variables being set and used, e.g. with an editor's search features, grep, git log -S, etc.
We can change the internals of the class without changing the interface, and vice-versa.
By calling opts.fetch in the initialiser, we're making it clear to future readers of our class what the opts argument should look like, without making them read the whole class.
When to use metaprogramming
Metaprogramming can sometimes be useful, but those situations are rare. As a rough guide, I'd be more likely to use metaprogramming in framework or library code which typically needs to be more generic (e.g. the ActiveModel::AttributeAssignment module in Rails), and to avoid it in application code, which is typically more specific to a particular problem or domain.
Even in library code, I'd prefer the clarity of a few lines of repetition.
Answers to this question are always going to be based on someone's personal opinion so here's mine.
Clarity v Brevity
If you cannot know the set of options ahead of time then you have no real choice but to do as you have. However if the options are drawn from a known set then I would favour clarity over brevity and have explicit methods to set the options. These would also be a good place to add any rdoc etc.
Safety
From a safety perspective, having methods to handle the setting of an option would allow you to perform validation as required.
When you need to do such thing, the inventory of the parameters varies. In such case, there is already a handy structure within Ruby (as well as most modern languages): array and hash. In this case, you should just save the entire option as a single hash. That would make things simpler.
Instead of creating instance variables dynamically, you could use attr_accessor to declare the available instance variables and just call the setters dynamically:
class Foo
attr_accessor :bar, :baz, :qux
def initialize(opts = {})
opts.each do |k, v|
public_send("#{k}=", v)
end
end
end
Foo.new(bar: 1, baz: 2) #=> #<Foo:0x007fa8250a31e0 #bar=1, #baz=2>
Foo.new(qux: 3) #=> #<Foo:0x007facbc06ed50 #qux=3>
This approach also shows an error if an unknown option is passed:
Foo.new(quux: 4) #=> undefined method `quux=' for #<Foo:0x007fd71483aa20> (NoMethodError)
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.