Hello I am received a "Stack level too deep" error and I am fairly sure that it is being generated from this model. I know it has something to do with a recursive call, but I have thus far been unable to locate it, thanks.
class Character < ActiveRecord::Base
# Associations
belongs_to :user
# Validations
validates :name, :presence => true, :uniqueness => true, :length =>
{ minimum: 2, maximum: 20 }, format: { with: /\A[a-zA-Z]+\Z/ }
validates :race, :presence => true
validates :class, :presence => true
validates :user, :presence => true
def self.races
["Human", "Dwarf", "Elven", "Orc", "Undead", "Demon"]
end
def self.classes
{
:fighter => {strength: 4, endurance: 3, dexterity: -2, charisma: -2, wisdom: -2, intelligence: -3},
:thief => {strength: -3,endurance: 2, dexterity: 4, charisma: 2, wisdom: -2, intelligence: 0},
:magi => {strength: -3, endurance: -2, dexterity: -2, charisma: 2, wisdom: 3, intelligence: -3},
:ranger => {strength: -2, endurance: 2, dexterity: 2, charisma: 0, wisdom: -3, intelligence: 0},
:cleric => {strength: 2,endurance: 2, dexterity: -3, charisma: -2, wisdom: 3, intelligence: 2}
}
end
def set_class(_class)
_attributes = Character.classes[_class.downcase]
transaction do
self.class = _class.downcase
_attributes.each do |name, value|
self.name += value
end
self.save
end
end
end
Server log:
Started GET "/characters/new" for 127.0.0.1 at 2014-04-04 01:54:14 +0200
[1m[36mActiveRecord::SchemaMigration Load (0.8ms)[0m [1mSELECT "schema_migrations".* FROM "schema_migrations"[0m
Processing by CharactersController#new as HTML
[1m[35mUser Load (1.5ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
Rendered shared/_error_messages.html.erb (3.8ms)
Rendered characters/new.html.erb within layouts/application (38.1ms)
Completed 500 Internal Server Error in 199ms
SystemStackError - stack level too deep:
activerecord (4.0.2) lib/active_record/attribute_methods/read.rb:85:in `'
There are three problems with your code:
class
as an attribute,:symbols
and "String".downcase
,self.name
(attribute) thinking it's a different setter.Don't use class
. It's a reserved keyword.
These lines will leave ruby very confused:
validates :class, :presence => true
self.class = _class.downcase
Also, I assume that you have a class
column in your DB table. You should really rename it to something like character_class
or rpg_class
.
Your self.classes
method returns a Hash
whose keys are symbols. And yet, later you try to access it using a string:
_attributes = Character.classes[_class.downcase]
While it's true that :foobar.downcase
will do nothing, you can't access those values with "Foobar".downcase
.
To fix that, you could:
Character.classes[_class.to_sym]
HashWithIndifferentAccess
In any case, that method could be improved by using memoization.
At the moment you are re-creating a hash of hashes with each call. Instead, you can save it in a variable and create it only the first time.
def self.classes
@char_classes ||= { fighter: { #...
end
Or, even better, use a constant:
CHAR_CLASSES = { #.....
def self.classes
CHAR_CLASSES
end
I see that you have this validation in place:
validates :name, :presence => true, :uniqueness => true, :length => { minimum: 2, maximum: 20 }, format: { with: /\A[a-zA-Z]+\Z/ }
which means you have a name
column in your DB table, and that it's supposed to be a String
matching a specific format (only letters, between 2 and 20).
With this said, let's have a look at this block of code:
_attributes.each do |name, value|
self.name += value
end
In this local scope, name
is a variable holding a Symbol
(e.g. :strength
) and value
is a Fixnum
.
When you do self.name += value
, however, you are assigning the Fixnum
to the object attribute name
.
I can't see where you define methods like strength=
or dexterity=
. I'm assuming they are columns on the table.
In that case, then this should work:
self.public_send("#{name}=", value)
# or just 'send' if it's a private method
When you are setting the attributes on your character, you need to generate the setter dynamically. You can't call a dynamic method by appending a variable. name = 'mike'; self.name
will not call self.mike
.
def set_class(_class)
_attributes = Character.classes[_class.downcase]
transaction do
self.class = _class.downcase
_attributes.each do |name, value|
self.public_send("#{name}=", value)
end
self.save
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With