As you may know attr_accessor accepts a list of symbols (or strings) and for each one two methods are defined: a so-called a setter (e.g. health=) and getter (health). A list (not an actual type btw.) is one or several expressions separated by a comma, so in your case Ruby expected another expression after :armor because you put a comma thereafter.
(If the trailing comma was intentional, tell us. Some programmers use and recommend them in certain cases (and depending on the language). Though it would make any answer a bit more complicated since Ruby is actually fine with them - just not in this case.)
However that's only one part of your bug's explanation. One of Ruby's most basic features as a programming language is that nearly everything has a value (expressions). For example, try something like x = if ... in other languages! It often won't work as many languages have if-statements, not if-expressions.
In Ruby method definitions are also expressions: defining a method via def (def foo; end) used to return nil. Since Ruby 2.1 method definitions return the method's name (:foo). Now the return value of def-expressions may not seem to be very useful to you and it isn't in many cases but you probably agree the method name (:foo) is certainly more useful than nil.
The primary use case for this are methods like private, public, method_function, etc. which aren't keywords but are used like one (their defining feature is for calls the parentheses are omitted):
private def foo
end
# before 2.1
private # from now on every method in this class context will be private
def foo
end
# or
def foo
end
private :foo # only foo will be private but we have to repeat the name
Our friend attr_accessor is also such a keyword-like method but the value of def expressions is more important here. Remember it's the name of a method as a symbol. You wrote (simplified):
attr_accessor :health, :damage, :armor,
def initialize(health, damage, armor)
end
# which after the method was defined becomes
attr_accessor :health, :damage, :armor, :initialize
To make it a bit more clear - without attr_accessor your code might look like this:
class Battler
def initialize(health, damage, armor) # takes 3 required arguments
end
def health
@health
end
def health=(v)
@health = v
end
# damage, armor
def initialize # takes no arguments
@initialize
end
def initialize=(v)
@initialize = v
end
end
You see you're redefining Battler#initialize and your new one doesn't take arguments because getters usually don't.
Redefinitions aren't treated as errors since they can be intentional however Ruby can emit a warning for you. Run ruby -w test.rb on the command line and it outputs:
test.rb:15: warning: assigned but unused variable - knight
test.rb:16: warning: assigned but unused variable - goblin
test.rb:2: warning: method redefined; discarding old initialize
test.rb:3: warning: previous definition of initialize was here
Traceback (most recent call last):
1: from test.rb:15:in `<main>'
test.rb:15:in `new': wrong number of arguments (given 3, expected 0) (ArgumentError)
Here you can see that in line 2 the initialize method which was originally defined on line 3 is redefined. If you're confused why line 3 comes before line 2 here, then remember that the method definition is part of the list passed to attr_accessor so the def-expr has to be evaluated before the call.
attr_accessor :health, :damage, :armor,and all will be well. - Cary Swovelandattr_accessormethod has one or more arguments that continue on the following line. Why the definition ofinitializedoesn't raise an exception is a mystery to me. However,Battler.instance_method(:initialize).owner #=> Battler; Battler.instance_method(:initialize).parameters #=> []tells us that the methodinitializewas created with no arguments, which is consistent with your error message. Very odd. - Cary Swovelanddef initialize; endis:initializeso OP basically wroteattr_accessor :initialize. - cremno:initializewas created [with three arguments], but was then immediately overwritten by the accessor for@initialize.) As an aside, I can't understand why someone has voted to close this question. It's a great question! - Cary Swoveland