Instance Variables in ruby

 

Dogs have many shared characteristics, like the abilities to wag their tails and drink water from a bowl, but they also have information about them that is variable,

like their breed or their name.

Similarly, when designing an application, your users will have common traits, like the ability to log in and out, but some information will be variable,

like a username or email address. Let’s start by setting up a new User class. You can follow along in either irb or by running a script from the command line.

class User
    # stuff will go here
end

By the end of this lesson, you’ll be able to define a User class that can track its own user information.

 

Say you wanted to assign a user’s username. You might try something like this.

julia = User.new
julia.username = "coolgirl2000"
# NoMethodError: undefined method `username' for #<User:0x007fc6fa034148>

Let’s pause to take a look at that error bit by bit.

  • NoMethodError: If I had to guess, I’d say this likely has something to do with a method not existing.
  • undefined method 'username': Suspicions confirmed. It looks like our code can’t find a 'username' method. That makes sense, we never defined one.

But you weren't trying to create a new method! You were thinking about this username like a variable, right?

Ruby has no way of distinguishing between variables and methods in this case, since they both come after the ., so it only supports instance methods.

😱What do we do now?! To treat username as a variable, you’re going to need to fake this a bit. Let’s write just enough code to stop this pesky error.

class User
  def username=(value)
  end
end

If you recall, most characters are fair game with methods, including =. In this case, the valueargument stands in place of the value you want to assign to username.

julia.username=("coolgirl2000")
# => "coolgirl2000"

That does the trick… sort of. There’s an awful lot of syntax here, and it’s a bit of a stretch to say this looks anything like the julia.username = “coolgirl2000” we were going for.

Luckily, we can leave off the parentheses since those are optional for methods. These little shortcuts are known as syntactic sugar, since they make writing code with it just a bit sweeter 🍭. Let’s see how that looks.

julia.username= "coolgirl2001"
# => "coolgirl2001"

Wow, I already feel so much less stressed looking at that. The fact that the = is pressed up againstusername is still bothering me. I can’t stop looking at it.

It’s going to bother me forever if you don’t do something about it. Please, do something about it.

Syntactic sugar to the rescue again. When a method ends with =, Ruby rightfully assumes that you’re creating a method for variable assignment. To make your life sweeter, Ruby lets you put a space before the = so it reads more like typical variable assignment, like so:

julia.username = "coolgirl2002"
# => "coolgirl2002"

💥Boom. Check that out. I can sleep easily again. You’ve just successfully created (the start of) asetter method (sometimes called a mutator method).

A setter method is one which sets, or changes, the value of a variable belonging to a particular object.

 

Alright, let’s check in on your username. If my math is correct, it should be set to “coolgirl2002”right now.

julia.username
# NoMethodError: undefined method `username' for #<User:0x007fc6fa034148>

This error again. You’ve seen this one before, but didn’t you already create this instance method?

Let’s go ahead and write the bare minimum amount of code to make this error go away.

class User
  def username=(value)
  end

  def username
  end
end

This is the opposite of a setter method, a getter method. It exists to get the value of a variable belonging to an object. Let’s see if this works.

julia.username
# => nil

There’s no error 🎉, but it’s also returning nil instead of “coolgirl2002”. You may have already caught on to this…

neither your username nor your username= methods actually do anything.

 

To make your getter and setter methods start working, you’re going to need to capture theusername values somewhere.

The problem here has to do with scope. How can the values you capture in the username= setter method be accessed in the username getter method?

Much like instance methods, you can also use instance variables. Instance variables are variables that are scoped to the entire class.

This means that once an instance variable is defined, it can be used anywhere in that object.

An instance variable looks just like a regular variable, with one minor difference: it starts with @.

class User
  def username=(value)
    @username = value
  end

  def username
    @username
  end
end

Let’s see if this works before moving on.

julia.username = "coolgirl3000"
# => "coolgirl3000"
julia.username
# => "coolgirl3000"

🙌 Nice!

The @username instance variable is now available to use by any of the methods inside of a Userobject.

You can go ahead and make other User objects now, and each one will be able to track their own information.

walter = User.new
# => #<User:0x007fc6fa034328>
walter.username = "cooldude17"
# => "cooldude17"
walter.username
# => "cooldude17"

And one last check with Julia to make sure her information is still accurate.

julia.username
# => "coolgirl3000"

Remember when we said that $global_variables weren’t the best solution? This is that best solution.

Very rarely will you need a variable that is available to everything inside your application. Often, you just want it available to one or a few objects.

Let me say that again: You will almost never need to use global variables ever again. If you find yourself using one,

there’s a very good chance it can be done some other way using classes.

Getter and setter methods are used so often in Ruby that there are even more shortcuts you can use to make your life sweeter.

Why type more code than you really need to? Remember, Ruby is optimized for developer happiness.

class User
  attr_reader :username
  attr_writer :username
end

attr_reader and attr_writer (“attr” being shorthand for “attribute”) are two methods that take care of creating getter and setter methods, respectively, for you. 

Best of all, you can do this for as many attributes as you’d like. Perhaps you want users to be able to change their usernames, but not their birthdays.

class User
  attr_reader :username, :birthday
  attr_writer :username
end

There’s no need to actually write out all those getter and setter methods yourself anymore!

One thing that confused me at first was the syntax of attr_reader and attr_writer.

It didn’t really look like anything I’d ever seen before, at least not until someone rewrote it for me this way:

attr_reader(:username, :birthday)

These are actually instance methods that are already baked in for you to use.

All you are doing is passing in a Symbol representing the instance variable you’d like to create getter and setter methods for.

Accessors

There’s one last refactor we can do here. It’s pretty common to need both a getter and a setter method for an attribute, so why not define both at once?

That’s exactly what attr_accessordoes.

class User
  attr_accessor :username
end

I know what you’re thinking. Yes, I did just string you along this emotional journey only to end up writing three lines of code.

But think about it: now you know what these three lines of code actually do!

Along the way, you learned about instance variables, some neat syntactic sugar, and getters and setters. Not bad.

Let’s continue with the User example. This is what we’ve got at the moment:

class User
  attr_accessor :username
end

This is useful, but presents a slight issue. We don’t want a user to ever be without a username.

The way things are currently set up, you can easily do something like this:

colt = User.new
# => #<User:0x007fc6fb03ae98>
colt.username
# => nil

Because a User is created without a default username value, Colt is left without a username until he goes in and sets one himself.

🚫This will not do.

There’s a special instance method you can use to make sure an object is properly set up when it’s created, initialize.

When you create a new object, the first thing Ruby will do is look for thatinitialize method and run it.

This makes is super handy for initial setup (for things like… initializing an object with a username, maybe?).

class User
  attr_accessor :username

  def initialize
    @username = "its_colt_outside"
  end
end

Let’s give this another go.

colt = User.new
# => #<User:0x007fc6fb03ae98 @username="its_colt_outside">
colt.username
# => "its_colt_outside"

💅Beautiful.

 

There’s just one issue with the previous example— we’re hard-coding “its_colt_outside” to be the default username for every single user.

That’s not going to help you much when Orit wants to make an account for herself.

How about passing in the username as an argument right from the start, like a method, when creating the new User object?

orit = User.new("supermom")

To do this, you’ll need to modify your User class so that it can accept arguments. It may be tempting to do something like this:

class User(username)
  # ...
end

But as you’ll quickly encounter, this will throw an error. This is a completely reasonable assumption, though.

Instead, you can include the argument as a part of the initialize method.

A good way to remember this is to keep in mind that initialize takes care of everything related to the initial set-up of an object.

Since the username argument deals with the initial set-up of a username, it belongs to the initialize method.

class User
  attr_accessor :username

  def initialize(username)
    @username = username
  end
end

Now you can initialize a User object with a username right when you create it.

orit = User.new("supermom")
# => #<User:0x007fc6fb03ae98 @username="supermom">
orit.username
# => "supermom"

This also safeguards you from creating a new user without a username.

mike = User.new
# ArgumentError: wrong number of arguments (0 for 1)

If you break apart that error, it shouldn’t be too tough to figure out what the issue is.

  • ArgumentError: This probably has something to do with the arguments passed into thisUser object.
  • wrong number of arguments (0 for 1): Zero arguments were passed in, when theUser class is expecting one.

Errors can seem scary, but often they contain vital information to help fix bugs in your code. Good error messages will tell you exactly where you need to make a fix.

 

 

posted @ 2016-05-01 14:16  冰凌花花~  阅读(193)  评论(0编辑  收藏  举报