0

New in Ruby, trying to figure out why you would choose one variation over the other. Could you please write an example where one would be preferred over the other?

class User
    attr_accessor :username
    def initialize(username)
       self.username = username
    end
end

class User
    attr_accessor :username
    def initialize(username)
        @username = username
    end
end
Alex A
  • 156
  • 1
  • 10
  • Its an opinion-based question, second version seems more concise and also does not require `attr_accessor` to be defined - hence, my guess it is more preferable – Wand Maker Dec 17 '15 at 18:55

3 Answers3

5

They are the same thing.

self.username = calls the username= function which is defined by attr_accessor. That function looks like this:

def username=(value)
  @username = value
end

As you can see, it is identical to the "alternative" you mentioned.

EDIT Using an accessor (i.e. calling the function defined by attr_accessor/reader/etc) is significantly faster than other forms of access. There are a few links in the comments which elaborate more on this.

Leo Brito
  • 2,053
  • 13
  • 20
  • 1
    And, `@username` can be used even if `attr_accessor` is not defined, `self.username` can be used only if `attr_accessor` is defined – Wand Maker Dec 17 '15 at 18:54
  • It's worth noting that methods generated by `attr_accessor` tend to benchmark a lot faster than those composed manually. If possible, use the generators. If necessary, write your own. – tadman Dec 17 '15 at 19:44
  • So if you have an attr_accessor is it better to call it via @ or self.? or it doesn't make a difference at all? – Alex A Dec 17 '15 at 19:56
  • @AlexAvlonitis using the accessor is usually faster. Here are a few discussions which explain/debate why: [1](http://stackoverflow.com/questions/13494098/ruby-attr-accessor-vs-getter-setter-benchmark-why-is-accessor-faster), [2](https://www.omniref.com/ruby/2.2.0/files/method.h?#annotation=4081781&line=47) – Leo Brito Dec 17 '15 at 20:09
  • Using attr_accessor may be faster then define an accessor method yourself, but is it really faster than directly setting the value, without defining an extra method? Our case seem not to be handled in these benchmarks. – Meier Dec 17 '15 at 21:49
  • 1
    *Actually*, you missed the trailing comma in line 2. Both pieces of code are 100% identical, because the trailing comma will lead Ruby to wait for a second argument to `attr_accessor`. That second argument is the return value of the `def`, which is a symbol denoting the name of the method being defined (`:initialize`). So, `def` will define the `initialize` method, but then `attr_accessor` will *immediately* overwrite that method with a getter for an instance variable named `@initialize`. So, both snippets will end up with the exact same four methods: `username`, `username=`, `initialize`, … – Jörg W Mittag Dec 17 '15 at 23:03
  • … and `initialize=` which do the exact same thing in both cases. The only difference is the content of the *first* `initialize` method, which however gets immediately overwritten with the exact same getter in both cases. – Jörg W Mittag Dec 17 '15 at 23:05
1

One example is when you need read-only properties. Something like this:

class ProjectProcessor
  attr_reader :project # attr_reader, not attr_accessor

  def initialize(project)
    @project = project
  end

  # more code
end

Since attr_reader doesn't define a setter method, external code can't change project once processor is created (not in a convenient way, anyhow).

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
1

Both cases are exactly equivalent because of the comma in line 2: the comma means that you are passing two arguments to attr_accessor, not one.

What is the second argument? Well, it's what comes after the comma, of course, which is the def expression. In older versions of Ruby, the return value of a method definition expression was implementation defined (in some implementations it returned nil, in some the compiled bytecode of the method), but in current versions of Ruby, the return value of a def expression is standardized to be a Symbol denoting the name of the method being defined. So, in this case, the def expression evaluates to :initialize.

Now, because Ruby is a strict language, arguments are evaluated before they are passed, which means the def initialize is evaluated first, which defines an initialize method with one parameter.

However, immediately after that, attr_accessor is called with two arguments, :username and :initialize, and as a result, attr_accessor will create four methods: username=, username, initialize=, and initialize, thus overwriting the method we just defined with one that has no parameter.

That's why the two examples are the same: while the two initialize methods are different initially, they immediately get overwritten with an identical method.

(I guess, technically, you could observe the difference, if you managed to call initialize from a different thread at just the right time, directly after it is being defined the first time but before it is being overwritten. That's an extremely tiny window, though.)

Note that the code you posted will generate a warning to that effect:

user.rb:2: warning: method redefined; discarding old initialize
user.rb:3: warning: previous definition of initialize was here

You can see the effect of the strict evaluation of the arguments here in the line numbers: it complains that the method is being overwritten in line 2 but was previously defined on line 3, which actually comes after 2 but was evaluated before it.

This shows something I have written about over and over and over again: you should actually read the warnings. They were put in there for a reason.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • The comma was a typo sorry. I copied just a part of it for the question and the comma slipped in – Alex A Dec 18 '15 at 00:32