Reputation: 39
I'm trying to generate stats for a character created by a form. The user inputs the name, race, class, alignment, and whether or not the stats will be generated randomly, or prioritized (values being assigned from highest to lowest). The form works flawlessly, as I can see the output in a view.
What I am now trying to do is call a method from a class in /lib in the model that will generate the stats; however, I keep getting the following error (I can't post pictures):
NoMethodError in CharactersController#create
undefined method `[]' for nil:NilClass
Extracted source (around line #14):
12 before_save do
13 generate_stats
14 self.strength = @character_stats[:strength]
15 self.dexterity = @character_stats[:dexterity]
16 self.constitution = @character_stats[:constitution]
17 self.intelligence = @character_stats[:intelligence]
Here is a copy of some of my code:
In controllers\characters_controller.rb
class CharactersController < ApplicationController
def create
@character = Character.new(character_info_params)
@character.name = params[:character][:name].capitalize
@character.alignment = "#{params[:character][:alignment_lr]} #{params[:character][:alignment_ud]}"
if @character.save
redirect_to @character
else
render 'new'
end
end
private
def character_info_params
params.require(:character).permit(:name, :race, :class_, :alignment)
end
end
In models\character.rb
class Character < ActiveRecord::Base
require 'random_stats_generator'
attr_accessor :rand_stat_gen
def generate_stats
if @rand_stat_gen == true
@character_stats_inst = RandomStatGenerator.new
@character_stats = @character_stats_inst.generate
end
end
before_save do
generate_stats
self.strength = @character_stats[:strength]
self.dexterity = @character_stats[:dexterity]
self.constitution = @character_stats[:constitution]
self.intelligence = @character_stats[:intelligence]
self.wisdom = @character_stats[:wisdom]
self.charisma = @character_stats[:charisma]
end
#validation passed this point
end
In initializers\stat_builders.rb
require "./lib/random_stat_generator.rb"
In lib/random_stat_generator.rb
class RandomStatGenerator
def initialize
@strength = :strength
@dexterity = :dexterity
@constitution = :constitution
@intelligence = :intelligence
@wisdom = :wisdom
@charisma = :charisma
@character_stats = HashWithIndifferentAccess.new()
end
def self.generate
roll_stats
end
def roll(stat)
@roll_value_1 = (1 + (rand(6)))
@roll_value_2 = (1 + (rand(6)))
@roll_value_3 = (1 + (rand(6)))
@roll_value_4 = (1 + (rand(6)))
@roll_array = [@roll_value_1,@roll_value_2,@roll_value_3,@roll_value_4]
@roll_array = @roll_array.sort_by {|x| x }
@roll_array = @roll_array.reverse
stat = @roll_array[0] + @roll_array[1] + @roll_array[2]
end
def roll_stats
@strength = roll(@strength)
@dexterity = roll(@dexterity)
@constitution = roll(@constitution)
@intelligence = roll(@intelligence)
@wisdom = roll(@wisdom)
@charisma = roll(@charisma)
@character_stats[:strength] = @strength
@character_stats[:dexterity] = @dexterity
@character_stats[:constitution] = @constitution
@character_stats[:intelligence] = @intelligence
@character_stats[:wisdom] = @wisdom
@character_stats[:charisma] = @charisma
return @character_stats
end
end
To me, it looks like the method isn't returning anything, or isn't being called at all.
I've tried a lot of solutions that I've come across online, none of them working. There may be some things that don't really make sense that are left over from these solutions. I'm only just starting with rails, so I'm still trying to get used to everything.
Thanks a lot for your help.
Upvotes: 0
Views: 1572
Reputation: 102250
Ruby has really powerful functions for manipulating both hashes and arrays. Typing out duplicate assignments like:
self.strength = @character_stats[:strength]
self.dexterity = @character_stats[:dexterity]
self.constitution = @character_stats[:constitution]
Is pretty dull. So instead we can simply rewrite the methods to pass hashes around.
class RandomStatGenerator
# This is just a constant containing all the stats we want to generate.
STATS = [:strength, :dexterity, :constitution, :intelligence, :wisdom, :charisma]
# Create a hash with random roll values for each stat
def self.roll_stats
# This is kind of scary looking but actually just creates an
# hash from an array of keys
Hash[STATS.map {|k| [k, self.roll ] } ]
end
private
def self.roll
# Create an array with 4 elements (nil)
ary = Array.new(4)
# We then replace the nil value with a random value 1-6
ary = ary.map do
(1 + (rand(6)))
end
# sort it and drop the lowest roll. return the sum of all rolls.
ary.sort.drop(1).sum
# a ruby ninja writes it like this
Array.new(4).map { 1 + rand(6) }.sort.drop(1).sum
end
end
Output:
irb(main):032:0> RandomStatGenerator.roll_stats
=> {:strength=>14, :dexterity=>14, :constitution=>14, :intelligence=>13, :wisdom=>10, :charisma=>9}
But if you don't intend to actually create instances of a class, than you should use a module instead.
Rails models can either be created with a hash or you can replace its values with a hash:
Character.new(RandomStatGenerator.roll_stats)
@character.assign_attributes(RandomStatGenerator.roll_stats)
So we can use this in Character#generate_stats
:
def generate_stats
assign_attributes(RandomStatGenerator.roll_stats)
end
You should use ActiveModel callbacks with extreme prejudice. It is often quite a challenge to regulate where in your application and when in the model lifetime. Since before_save
runs after validations means that any validations like validates_presence_of :constitution
will fail.
In your case it might be better to simply do it in the controller or use:
before_validation :generate_stats, if: -> { new_record? && @rand_stat_gen }
Upvotes: 1
Reputation: 14029
I would like to suggest the following organisation fo your library
# Use a module at top level
module RandomStatGenerator
STATS = [:strength, :dexterity, :constitution, :intelligence, :wisdom, :charisma]
# Use a class Stats if you need to but I don't see why...
class Stats
def initialize
RandomStatGenerator::STATS.each do |stat|
# Below line will do @stat = :stat
instance_variable_set("@#{stat.to_s}", stat)
@character_stats = HashWithIndifferentAccess.new()
end
def roll_stats
@character_stats = RandomStatGenerator.roll_stats
end
end
module_function
# below lines will be considered as module functions
# => call RandomStatGenerator.function_name
def roll
roll_value_1 = (1 + (rand(6)))
roll_value_2 = (1 + (rand(6)))
roll_value_3 = (1 + (rand(6)))
roll_value_4 = (1 + (rand(6)))
roll_array = [roll_value_1,roll_value_2,roll_value_3,roll_value_4]
roll_array = roll_array.sort_by {|x| x }
roll_array = roll_array.reverse
roll_array[0] + roll_array[1] + roll_array[2]
end
def roll_stats
character_stats = {}
STATS.each do |stat|
character_stats[stat] = RandomStatGenerator.roll
end
return character_stats
end
end
Then in your character.rb
def generate_stats
@character_stats = RandomStatGenerator.roll_stats
end
Upvotes: 1