Closed ianterrell closed 1 year ago
I might note that the "README format" might more appropriately be described as "Markdown"; hence the .md file extension.
Noted! Markdown file type it is. Named as a counterpoint to hypertext "markup language" I'm guessing. It's for making text files readable and publishable in this format, or two conver plain text formatting to HTML's. And I can also have Ruby assigned like this:
ruby
def some_method
return "hello, example!"
end
It would probably be most idiomatic to just say that they're different instances of the same type.
That makes sense. It's not that it's wrong to say they have differnet object_ids, but that it's either: not super useful to know that in the context I was writing about it, or: it's not conventional to say it that way, since we care more about where the two objects exist in the hierarchy?
The parameters are truly just local variables, and they do disappear. You record their values by assigning them to the instance variables (rather than "pass them into instance variables as arguments", which I think is mixing different ideas improperly).
I always want to make sure I'm getting things right, so I'm sure I'd fall into the second category of people on the receiving end of your pedantry. So, when the book says that argument variables don't "disappear", what the text is really illustrating is that the local variable itself disappears at the end of the method (like any local variable would), but its value is preserved and referenced using the corresponding instance variable. Is that right?
(Just encouraging good habits. And I was curious.)
Every time I access it, the documentation is written in a much more accessible way than I anticipated. It's in my bookmarks folder, just gotta use it more now.
There are a few really interesting lessons underneath this, which culminate in: everything is stores in binary.
I see a definition of a "type" as "the common features of a set of objects with the same characteristics" for OOP. So the type influences how the 0s and 1s data is processed? Which is why (with the exception of exception supressing (that's a fun phrase)), exceptions are thrown if you try to send the wrong message to the wrong type of receiving object?
Apologies if you already know all of this.
I almost always know only parts of what I'm learning right now, so please feel free to throw basics at me as if I'm new to it all. Worst case is that it's a refresher, and the best case is that I learn something unexpected.
For instance, it was cool to learn that Ruby is recommended by the NSA for writing secure programs. So security can have different meanings depending on the context. The text wasn't suggesting data privacy for Ruby programs across networks was poor, it meant that objects in a Ruby program aren't perfectly private in their isolated context. Right?
def price_in_cents # CsvReader depends on this line...
(price * 100).round # But not on this one!
end
Regarding the comments on this snippet. I thiiink I can see what you're saying a little more now. So, because price_in_cents
is being called, CsvReader
only needs to find the method name that matches it. What happens inside the method is just going to return a value, and that value could be different for another instance that redefined what price_in_cents
calculates to return. This has an even more removed relationship to an unrelated class whose own price_in_cents
method is not being called. What matters is how the types communicate this data, since sometimes types are compatible, but other times they are not. Is that right?
The code is the artifact that has to do the communication.
I'm rearranging my engine with this in mind the last few days. Trying to break up unrelated expressions in the same methods to instead call a method to return a value. It's making things easier, like the puzzles have less pegs the more I break it down.
It is, however, alarming how overly complicated some parts are. Renaming some variables has also helped me keep better track of some that were ambiguously named before. I see that how the code is written can suggest (or elude) how one should interpret its modeling. Pretending it's new every time I look at it is helping me break it down.
There's a corollary there, which is that restricting things to the least amount of behavior also prevents bugs!
Better encapsulation of some of these complicated areas is causing less unexpected behavior. I'm finding that I can imagine new method implementations that fit seamlessly with the rest of the puzzle before coding it. It's empowering to dream up a feature and just add it like that.
And it didn't do anything, whereas you may have expected it to call your def price= method defined by attr_accessor and set the price that way. Is that right?
Yes! So, specifying the receiver changes the game in that kind of context? Instead of price = cents / 100
only assigning the value to an isolated variable in an instance method, saying self.price = cents / 100
invokes the setter method for the instance variable. The latter is functionally similar to @price = cents / 100
. Is that right?
It would probably be most idiomatic to just say that they're different instances of the same type.
That makes sense. It's not that it's wrong to say they have differnet object_ids, but that it's either: not super useful to know that in the context I was writing about it, or: it's not conventional to say it that way, since we care more about where the two objects exist in the hierarchy?
That's right — it's not wrong to say they have two different object_id
s, but what that means is that they live in two different memory locations (and therefore may have different data), and what that means is that they're two different instances.
We don't really think about memory locations or object_id
s — we tend to think slightly higher, in terms of "are they the same instance" or the related "are they equal".
So, when the book says that argument variables don't "disappear", what the text is really illustrating is that the local variable itself disappears at the end of the method (like any local variable would), but its value is preserved and referenced using the corresponding instance variable. Is that right?
That's right!
("Referenced" is a tricky word and sometimes a term of art with a specific meaning, but you understand what's happening.)
Every time I access it, the documentation is written in a much more accessible way than I anticipated. It's in my bookmarks folder, just gotta use it more now.
Yes!
Sometimes documentation is terrible. It can be outright missing, or worse than that it can be out of date. It can also be difficult to parse sometimes.
But on average people put a lot of love and care into it. And the more you read it, the more you learn how to read it.
There are a few really interesting lessons underneath this, which culminate in: everything is stores in binary.
I see a definition of a "type" as "the common features of a set of objects with the same characteristics" for OOP. So the type influences how the 0s and 1s data is processed? Which is why (with the exception of exception supressing (that's a fun phrase)), exceptions are thrown if you try to send the wrong message to the wrong type of receiving object?
From the perspective of OOP, you're spot on. A type (a class) is that common set of features you describe. I might reinforce "data and behavior" as that set of features, although you can sort of get away with classes with only data and sort of get away with classes with only behavior. But if one class has the behavior you want and another doesn't, the one without will raise the exception on that message.
For full understanding of what I meant, there's the wrinkle of the "primitive types". It's important to understand a little of, eventually, but I wouldn't worry about it much for now. But if it interests you, here's a small example. Skip between the lines if it's too much.
I'm sure it's possible to explore this in Ruby, but I don't know how! :) It's easier in a language that lets you get closer to the hardware, like C. Here's a small C program:
#include <stdio.h>
int main() {
int i = -1;
printf("%d %u\n", i, i);
return 0;
}
You can run it here: https://replit.com/languages/c
It's very different from Ruby, but parts are probably readable. You have a variable i
which is an integer with the value -1
. Then we print it out, twice, but interpreting it differently.
The first argument to printf
is a format string, which says how to print things out. The subsequent arguments are the values to substitute into the string. %d
says interpret it as a signed integer; that is, an integer that can be positive or negative. %u
says interpret it as an unsigned integer; that is, an integer that can only be positive. Then we pass it i
twice, saying use the same value for both.
The output is -1 4294967295
.
The thing to understand here is that the data is the same in both cases. They have the same underlying 0s and 1s — we just chose to interpret them differently!
There are other ways you can interpret the raw 0s and 1s: as a floating point number, as a character in a language, as a reference to a particular type, or even as multiple pieces of data at the same time.
Here's an example that shows what the binary representation of pi looks like as an integer:
#include <stdio.h>
#include <math.h>
typedef union {
int i;
float f;
} u;
int main() {
u u1;
u1.f = M_PI;
printf("%d %f\n", u1.i, u1.f);
return 0;
}
It outputs: 1078530011 3.141593
.
The same binary representation underneath!
The text wasn't suggesting data privacy for Ruby programs across networks was poor, it meant that objects in a Ruby program aren't perfectly private in their isolated context. Right?
Yes. It just means that private
is a "recommendation" rather than an enforced language construct. Other languages are more strict.
class Foo
private def secret
"s3cret"
end
end
f = Foo.new
f.secret # private method `secret' called for #<Foo:0x000000010b20b000> (NoMethodError)
f.send :secret
# => "s3cret"
That's all it really means. You can't do that as easily in something like Java.
Regarding the comments on this snippet. I thiiink I can see what you're saying a little more now. So, because price_in_cents is being called, CsvReader only needs to find the method name that matches it. What happens inside the method is just going to return a value, and that value could be different for another instance that redefined what price_in_cents calculates to return. This has an even more removed relationship to an unrelated class whose own price_in_cents method is not being called. What matters is how the types communicate this data, since sometimes types are compatible, but other times they are not. Is that right?
Two related concepts to keep in your head:
First, encapsulation. Here's a terrible example, but say you have an Adder
that computes a sum:
class Adder
def initialize(a, b)
@a = a
@b = b
end
def sum
@a + @b
end
end
Then you can have a small program that prints out sums:
def print_sum(adder)
puts adder.sum
end
print_sum(Adder.new(1, 2)) # => 3
Now, later, you decide... well gosh, my Adder
should be able to sum any number of addends, not just two! So you refactor it like so:
class Adder
def initialize(*args)
@args = args
end
def sum
@args.sum
end
end
You've changed the implementation of Adder
, pretty dramatically! And yet, because you didn't change the API or the contract — meaning, you can call .new
and .sum
in the exact same way, the other program you wrote runs exactly the same!
def print_sum(adder)
puts adder.sum
end
print_sum(Adder.new(1, 2)) # => 3
No changes were necessary to it! That's what I meant by the code depends on the method rather than the method's implementation.
But there's also the related duck typing:
class AdderTheSnakeKind
def initialize(*args); end
def sum; "hiss"; end
end
def print_sum(adder)
puts adder.sum
end
print_sum(Adder.new(1, 2))
print_sum(AdderTheSnakeKind.new(1, 2))
In this case, AdderTheSnakeKind
behaves with the same API as Adder
, so you can use it everywhere. Well, sort of. If you were expecting a numeric value to sum
you'll crash eventually on the string. :)
We say "duck typing" because "if something quacks like a duck you can use it like a duck".
class Duck
def quack; "quack"; end
end
class Goose
def quack; "honk"; end
end
They both "quack like a duck".
I'm rearranging my engine with this in mind the last few days. Trying to break up unrelated expressions in the same methods to instead call a method to return a value. It's making things easier, like the puzzles have less pegs the more I break it down.
It is, however, alarming how overly complicated some parts are. Renaming some variables has also helped me keep better track of some that were ambiguously named before. I see that how the code is written can suggest (or elude) how one should interpret its modeling. Pretending it's new every time I look at it is helping me break it down.
That is all super great! Good job!
So, specifying the receiver changes the game in that kind of context? Instead of price = cents / 100 only assigning the value to an isolated variable in an instance method, saying self.price = cents / 100 invokes the setter method for the instance variable. The latter is functionally similar to @price = cents / 100. Is that right?
The thing to think about here is how does Ruby know what you mean? It has to make choices along the way as it reads your code. foo = 1
looks like it could be either calling the foo=
method or assigning a local variable foo
. Ruby chooses the latter option, probably because it's simply more common.
The latter is functionally similar to @price = cents / 100. Is that right?
Well.... yes and no!
If you call self.price = cents / 100
then if the implementation of price=
changes, you're always kept up to date with it!
Lots to think about — hope some of this is helpful. Please ignore if it's too much and just barrel onward!
We don't really think about memory locations or object_ids — we tend to think slightly higher, in terms of "are they the same instance" or the related "are they equal".
When do you think would be an appropriate time to focus on object_ids? Debugging?
I might reinforce "data and behavior" as that set of features, although you can sort of get away with classes with only data and sort of get away with classes with only behavior.
A little googling says that a "feature" is a prominent attribute (something that we want to be accessible). Just making sure I have this right? I'm familiar with behavior and data, but "feature" isn't something I've heard outside of casual conversation and I wanna make sure I know it well.
The output is -1 4294967295. The thing to understand here is that the data is the same in both cases. They have the same underlying 0s and 1s — we just chose to interpret them differently!
So, for this C example, the value is -1 regardless-- but viewing -1 through a positive-integer-expectant lens, it becomes 4294967295? Wouldn't it just throw an error? I understand that it's a representation of -1 but I'm a little thrown.
Encapsulation — implementation can change as long as contract doesn't
Sorry, full of quesitons today. What is a contract in terms of OOP? I tried searching it and only found a medical abstract about placentas. I don't think it's the link I needed.
If you call self.price = cents / 100 then if the implementation of price= changes, you're always kept up to date with it!
Last one, I promise (related to the most recent question)! What do you mean by "if the implementation of price=" changes? If we already defined it, that's the implementation of it, as I understand it now. So, redefining it, means that if I use price=, I'll always have the newest "version" of that method. Is that what implementation you're referring to?
Thanks for your thoughts as always! Hope you're having a good week.
When do you think would be an appropriate time to focus on object_ids? Debugging?
😅 Uh... sure! :) I use them almost never. The only question they answer is "are these two things the literal same instance?"
If that's a useful question, that can provide an answer. But the answer is also usually in the object's inspect
method, which is often printed out.
irb(main):007:0> a = Foo.new
=> #<Foo:0x00000001530e8c20>
irb(main):008:0> b = Foo.new
=> #<Foo:0x00000001530e0908>
irb(main):009:0> c = a
=> #<Foo:0x00000001530e8c20>
The big long hex values in the output are the location of the items in memory, which is equivalent to .object_id
(and can be converted back and forth in some versions of Ruby). So when I'm debugging that information is usually available and I don't have to resort to object_id
.
A little googling says that a "feature" is a prominent attribute (something that we want to be accessible). Just making sure I have this right? I'm familiar with behavior and data, but "feature" isn't something I've heard outside of casual conversation and I wanna make sure I know it well.
I think you wrote originally:
I see a definition of a "type" as "the common features of a set of objects with the same characteristics" for OOP.
And I was responding to that by trying to clarify "features" down to "data and behavior", because "feature" is kind of a loose word with only casual definitions.
I think the only usage of "feature" I have at work day to day is "something the product does".
So, for this C example, the value is -1 regardless-- but viewing -1 through a positive-integer-expectant lens, it becomes 4294967295? Wouldn't it just throw an error? I understand that it's a representation of -1 but I'm a little thrown.
It was a haphazard attempt at explaining something that's probably not useful to know at this point in time! But since I started, and because I find it fun, and because I think there are nuggets of philosophy inside it.
If everything is 0s and 1s, how do we know what any sequence of 0s and 1s means?
Pretend we used the letters abcdefghij
for our current digits 0-9. If I wrote cab
down on a piece of paper and handed it to you, would you think I meant a taxi, or would I be telling you the number 201? The truth is, there's not enough information for you to know! The answer depends on how you read it. "How you read it" can be encoded as the data type. If I wrote instead string: cab
you would know it's a taxi; if I wrote number: cab
you would know it's 201. But the data cab
is the same in both cases.
That's the core insight. In the c example, the same data is either the value -1
or the value 4294967295
, in the same way that cab
is either the value "taxi"
or 201
— depending on how you interpret it.
Encapsulation — implementation can change as long as contract doesn't
Sorry, full of quesitons today. What is a contract in terms of OOP? I tried searching it and only found a medical abstract about placentas. I don't think it's the link I needed.
Hmm... not about placentas. :) It's about agreement! It has a formal definition in some languages, but used a bit informally what I mean is: what does the code promise to do?
Other language make this more clear than Ruby does. In C for instance you define a function like this:
int foo(char c)
That says: "I am a method named foo
that takes a single argument of type char
(character), and returns an int
(integer)."
That's a "contract". Other code can "rely" on that. Other code can call foo
with any char
and always expect to get back an int
.
What's important about that is that we know that's true no matter what is inside foo
. It could always return 1:
int foo(char c) { return 1; }
Or it could convert the character into an integer:
int foo(char c) { return (int)c; }
Or it could do anything else — whatever it does, we know it obeys the contract described by the function definition.
So when you are writing foo
you can change the body without requiring changes to wherever you're using foo
.
Granted, there are large semantic differences between different implementations, so you might want to stick to changes that also "mean the same thing".
If you call self.price = cents / 100 then if the implementation of price= changes, you're always kept up to date with it!
Last one, I promise (related to the most recent question)! What do you mean by "if the implementation of price=" changes? If we already defined it, that's the implementation of it, as I understand it now. So, redefining it, means that if I use price=, I'll always have the newest "version" of that method. Is that what implementation you're referring to?
Yes, I think you've got it.
It's hard to come up with an amazing example on the spot here, but, say you have a book with a price and you can apply a discount:
class Book
def initialize(price)
self.price = price
end
def price
@price
end
def price=(price)
@price = Float(price)
end
def apply_discount(percent)
@price = @price - (@price * percent)
end
end
book = Book.new(10.50)
puts "Before Discount: #{book.price}"
book.apply_discount(0.1)
puts " After Discount: #{book.price}"
That outputs
❯ ruby price.rb
Before Discount: 10.5
After Discount: 9.45
Now... say you change your internal representation of price to an array of dollars and cents, but you forget to change your implementation of apply_discount
, which is directly reading and writing the internal representation:
class Book
def initialize(price)
self.price = price
end
def price
@price[0] + @price[1] / 100.0
end
def price=(price)
price = Float(price)
@price = [price.to_i, (price * 100 % 100).to_i]
end
def dollars
@price[0]
end
def cents
@price[1]
end
def apply_discount(percent)
@price = @price - (@price * percent)
end
end
book = Book.new(10.50)
puts "Before Discount: #{book.price}"
book.apply_discount(0.1)
puts " After Discount: #{book.price}"
Now we have...
❯ ruby price2.rb
Before Discount: 10.5
After Discount: 10.5
that doesn't seem right... But had we implemented it with the getter and setter, relying on their implementations to do the work, we'd have:
class Book
def initialize(price)
self.price = price
end
def price
@price[0] + @price[1] / 100.0
end
def price=(price)
price = Float(price)
@price = [price.to_i, (price * 100 % 100).to_i]
end
def dollars
@price[0]
end
def cents
@price[1]
end
def apply_discount(percent)
self.price = self.price - (self.price * percent)
end
end
book = Book.new(10.50)
puts "Before Discount: #{book.price}"
book.apply_discount(0.1)
puts " After Discount: #{book.price}"
Which outputs
❯ ruby price3.rb
Before Discount: 10.5
After Discount: 9.44
(Which is, in this case, still a bit different, but due to rounding and conversions and float behavior.)
This is contrived — and within a type it's okay to write stuff directly, when it makes sense to do so, which is most of the time — but I was trying in the original comment and in this example to show how you can leverage the building blocks of other methods, even readers and writers, to remain consistent across changing implementations.
I think the only usage of "feature" I have at work day to day is "something the product does".
That makes sense!
If everything is 0s and 1s, how do we know what any sequence of 0s and 1s means?
I briefly took a stab at learning binary and was able to write some simple sentences with it. So, if you know the rules of a language, you can exchnage meaningful information through its syntax. The lens of how that information is referenced can change its semantics though? Is that fair? What looks ike something in one context can look wildly different in another.
It has a formal definition in some languages, but used a bit informally what I mean is: what does the code promise to do?
A little more literal than I expected, but that makes sense.
So when you are writing foo you can change the body without requiring changes to wherever you're using foo.
I can see how that would be advantageous. Like, in a project where each role's details might evolve over time, as long as the role itself remains cohesive and true to its intended purpose, the event occurs as it should.
Granted, there are large semantic differences between different implementations, so you might want to stick to changes that also "mean the same thing".
Which is why you said this, I think. Right?
So, if you know the rules of a language, you can exchnage meaningful information through its syntax. The lens of how that information is referenced can change its semantics though? Is that fair? What looks ike something in one context can look wildly different in another.
I wish I had a better understanding of this in the general sense — I should finally read my Chomsky, I guess — but I'd say from a programming point of view you've got it exactly. I might say that data is insufficient by itself to be semantically meaningful; it requires a context. Here we might say a variable requires both a value (data) and a type (context).
I can see how that would be advantageous. Like, in a project where each role's details might evolve over time, as long as the role itself remains cohesive and true to its intended purpose, the event occurs as it should.
Yes! That's exactly it!
Getters and setters are not the best example, although this technique is occasionally applied to them. That happens more often in other languages than Ruby; Ruby is friendly and usually reading and writing is straightforward.
Granted, there are large semantic differences between different implementations, so you might want to stick to changes that also "mean the same thing".
Which is why you said this, I think. Right?
Yes!
The most usual way this technique is applied is called "refactoring." That's simply when you take working code that does what you want and make it "better" while keeping its behavior the same.
"Better" can mean a few things, including objective measurements like performance. But it's more often subjective and the aspects to focus on are clarity, readability, and maintainability.
It's what you're doing with your game engine: slowly making it better while it does the same thing. The more you can encapsulate bits of behavior into the right size chunks of classes and methods, the more you have the freedom to improve those classes and methods with confidence that nothing else is impacted.
QUICK UPDATES
That's great practice! I might note that the "README format" might more appropriately be described as "Markdown"; hence the
.md
file extension. There are plenty of guides online, but these two references are perhaps a tiny bit less obvious from Googling:I'll also note just one more bit: you can assign a language for a code block so that it highlights better when viewed in GitHub and many tools. See example 112 and the text just above it.
DEFINING CLASSES
That seems spot on!
I probably overemphasized
object_id
during that very first bug we looked at in bonecrawl. It would probably be most idiomatic to just say that they're different instances of the same type.Just minor language notes. The parameters are truly just local variables, and they do disappear. You record their values by assigning them to the instance variables (rather than "pass them into instance variables as arguments", which I think is mixing different ideas improperly). Again, just little language things, and I'm a bit of a pedant for it, which some people hate and which other people have thanked me for years later.
You can also read exactly what it does and how directly! https://ruby-doc.org/3.2.1/Kernel.html#method-i-Float
(Just encouraging good habits. And I was curious.)
There are a few really interesting lessons underneath this, which culminate in: everything is stores in binary, 1s and 0s, and we have to interpret them in various ways to get our data, and some things are represented more easily/more difficultly or better/worse or not at all in various ways!
And you might know all of this already. But ultimately some numbers aren't representable in some formats. We can't represent 1/3 perfectly in decimal, and must approximate it: 0.3333333333... Similarly, binary can represent some numbers very well and other numbers must be approximated. You see that later:
The common wisdom is to represent money in cents, since integers are exactly representable in binary. However, some financial stuff needs to go to sub-cent calculations, and
BigDecimal
is a good choice in Ruby.The introduction is good! https://ruby-doc.org/3.2.1/exts/bigdecimal/BigDecimal.html
Apologies if you already know all of this.
OBJECTS AND ATTRIBUTES
To answer the question, I don't think this really relates to security in a straightforward sense. The public/private aspect here refers to access within a program, rather than any security from outside of your program. There are other concerns around languages and security, including how easy or hard languages make it to write bugs, but generally Ruby is fine for security! Even:
https://www.securityweek.com/nsa-publishes-guidance-mitigating-software-memory-safety-issues/#:~:text=To%20prevent%20or%20mitigate%20the,memory%20safe%20actions%20or%20libraries.
It generally means "calling code", or code that works with your object. Generally this means entirely separate classes.
If you had a
Store
that had a collection ofBookInStock
objects, then if you choose eventually to change the representation of price, e.g. change the@price
from a singlefloat
to aBigDecimal
or to an integer representing cents or two variables, or a string, or anything else, as long as yourprice
method behaves the same (returned afloat
representation) thenStore
doesn't need to know or care or change anything!Another concrete example is your
CsvReader
. It has this code:And your book type has:
What the book is talking about is that:
Later you might do this:
Now the internal representation is different, and the implementation is different, but... to the "outside world" (or "other objects"), it looks and behaves exactly the same!
This is part of the "separation of concerns" idea. The types can evolve and change independently of each other, without causing problems — as long as they agree on how to talk to each other.
WRITING TO ATTRIBUTES
Your type has:
From the outside world, they look the same:
One is an accessor method for an attribute, the other is a method that computes a value. But they look the same syntactically. If you showed me that code and asked me which is which, I could not tell you.
That's not true of every language. For starters, most languages require parentheses for method calls and not for attributes. They might make it look like:
You're on the right track here. A few notes maybe.
There's no such thing as "the same people" working on a program, even if it's only you working on a program. In 6 months time you might come back to debug something and you've completely forgotten everything you had in your head at the time. The code is the artifact that has to do the communication. After you've forgotten writing that class, coming back and reading
attr_accessor :isbn
says, "Well, I don't get it now, but I guess I needed that to be mutable for some reason, better keep an eye out."Secondly, part of encapsulation is determining and restricting what other things can do!
attr_reader :isbn
says, "Phew, I don't have to worry, I can trust that no one else in this program is modifying this data. Once I assign it during initialization, I can trust that it's staying the same."There's a corollary there, which is that restricting things to the least amount of behavior also prevents bugs! Say you copy paste a bunch of code and edit it, and you mess it up (common). You meant to write
book.price = 5
but you accidentally wrotebook.isbn = 5
. If it'sattr_reader :isbn
you'll get, right there on that line,undefined method
isbn='. Then you see that and say, duh, oops, typo. But if it's
attr_accessor :isbn`, then you'll just assign it, and then maybe in your inventory report you see:And you look at that and you think... wtf. Now your debugging task is a lot harder than having the error immediately on the line where it occurred!
I'd forgotten what that meant and had to look it up. Useful reminder.
ATTRIBUTES ARE JUST METHODS WITHOUT ARGUMENTS
I assume you mean something like...
and you experimented changing it to
And it didn't do anything, whereas you may have expected it to call your
def price=
method defined byattr_accessor
and set the price that way.Is that right? If so, it's a bit of a common trap with Ruby.
price = cents / 100
gets interpreted as "calculate the value in the varaiable cents divided by the literal 100 and assign it to the local variable identified byprice
."To tell the interpreter to use the setter rather than assigning to a local varaiable, you have to specify the receiver:
self.price = cents / 100
. That says "calculate the value... and call the methodprice=
onself
with it as a parameter".