Undefined local variable based on syntax in Ruby
TL;DR
This is actually interpreter-specific. The problem shows up in MRI Ruby 2.1.2 and JRuby 1.7.13, but works as expected in Rubinius. For example, with Rubinius 2.2.10:
x = true
z = y if y = x
#=> true
In MRI, a little exploration with Ripper shows that Ruby treats the post-condition differently even though the AST assignments are similar. It actually uses different tokens for post-conditions when building the AST, and this appears to have an effect on the evaluation order of assignment expressions. Whether or not this should be the case, or whether it can be fixed, is a question for the Ruby Core Team.
Why It Works with a Logical And
x = true
y = x and z = y
This succeeds because it's really two assignments in sequence, because true
is assigned to x and therefore evaluates as truthy. Since the first expression is truthy, the next expression connected by the logical and is also evaluated and likewise evaluates as truthy.
y = x
#=> true
z = y
#=> true
In other words, x is assigned the value true
, and then z is also assigned the value true
. At no point is the right-hand side of either assignment undefined.
Why It Fails with a Post-Condition
x = true
z = y if y = x
In this case, the post-condition is actually evaluated first. You can see this by looking at the AST:
require 'pp'
require 'ripper'
x = true
pp Ripper.sexp 'z = y if y = x'
[:program,
[[:if_mod,
[:assign,
[:var_field, [:@ident, "y", [1, 9]]],
[:vcall, [:@ident, "x", [1, 13]]]],
[:assign,
[:var_field, [:@ident, "z", [1, 0]]],
[:vcall, [:@ident, "y", [1, 4]]]]]]]
Unlike your first example, where y was assigned true
in the first expression, and therefore resolved to true
in the second expression before being assigned to z, in this case y is evaluated while still undefined. This raises a NameError.
Of course, one could legitimately argue that both expressions contain assignments, and that y wouldn't really be undefined if Ruby's parser evaluated y = x
first as it does with a normal if statement (see AST below). This is probably just a quirk of post-condition if statements and the way Ruby handles the :if_mod token.
Succeed With :if Instead of :if_mod Tokens
If you reverse the logic and use a normal if statement, it works fine:
x = true
if y = x
z = y
end
#=> true
Looking at Ripper yields the following AST:
require 'pp'
require 'ripper'
x = true
pp Ripper.sexp 'if y = x; z = y; end'
[:program,
[[:if,
[:assign,
[:var_field, [:@ident, "y", [1, 3]]],
[:vcall, [:@ident, "x", [1, 7]]]],
[[:assign,
[:var_field, [:@ident, "z", [1, 10]]],
[:var_ref, [:@ident, "y", [1, 14]]]]],
nil]]]
Note that the only real difference is that the example that raises NameError uses :if_mod, while the version that succeeds uses :if. It certainly seems like the post-condition is the cause of the bug, quirk, or misfeature that you're seeing.
What to Do About It
There may be a good technical reason for this parsing behavior, or there may not. I'm not qualified to judge. However, if it looks like a bug to you, and you're motivated to do something about it, the best thing to do would be to check the Ruby Issue Tracker to see if it's already been reported. If not, maybe it's time someone brought it up formally.
Rails 6 - undefined local variable when passed to a partial but it is defined when I put a debugger in the erb file
you should be using local_assigns[:discount_rule]
to access the local variables.
one could use collection: @programs.discount_rules, as: :discount_rule
as well and call discount_rule
in the view.
At the same time make sure that the data is not nil in your controller and that you are able to print the required attribute like name before passing it to partial with something like .each do |instance| p.name
Undefined local variable coerced to nil when being passed as argument
From "The Ruby Programming Language" by David Flanagan:
Ruby treats an identifier as a local variable if it has seen any previous assignment to the variable. It does this even if that assignment was never executed.
This means that variables are implicitly declared by the Ruby parser, even before any code gets executed. As soon as ruby sees the =
operator, it immediately makes a new variable called yellow
with a default value of nil
. Then when that statement is executed, every reference to that variable share it's default value.
The book goes on to show an example of similar, perhaps surprising, behavior of variable declaration in ruby that is similar to this:
>> x # NameError: undefined local variable or method `x'
>> x = 0 if false # parsed, but never executed
>> p x # displays nil
The significance of this code is that is shows that variables are declared as soon as code is parsed, and since your two references to yellow
happen on the same line, the assignment of yellow must be parsed, and therefore declared, before it's passed as an argument to green
ruby: undefined local variable or method 'car' -- fixing syntax errors
A few pieces of advice.
Run Ruby with the -w
option :
$ ruby -w cars.rb
cars.rb:17: warning: mismatched indentations at 'end' with 'for' at 16
cars.rb:34: warning: mismatched indentations at 'end' with 'class' at 1
cars.rb:41: warning: mismatched indentations at 'end' with 'for' at 39
and eliminate the cause of warnings.
$ ruby -w cars.rb
How many cars do you want to create? 2
cars.rb:2:in `initialize': wrong number of arguments (given 0, expected 3) (ArgumentError)
from cars.rb:13:in `new'
from cars.rb:13:in `<main>'
new
calls initialize
, so new
must have the same number of arguments
as parameters in initialize
. Hence a car can be created only after you have asked all the information.
Don't work in the class. As written, your code is executed when Ruby reads
the class definition. For this exercise, you can leave it in the main level outside the class definition, or put it into a method.
for i in 1..num_cars
end
This loop is empty and does nothing. And prefer powerful iterators instead of this C, Perl, Java style (for, while, etc).
I define strings with apostrophes and keep double quotes when interpolation is needed (even if it's a question of nano seconds and personal choice). See here and there.
If you want to be comfortable with Ruby programming, I recommend The Pickaxe.
There are many ways of doing things in Ruby. The following is one solution.
class Car
attr_reader :make, :model, :year
def initialize(make, model, year)
@make = make
@model = model
@year = year
end
def self.make_car # class method (more precisely : singleton method)
print 'How many cars do you want to create? '
array_of_cars = Array.new
num_cars = gets.to_i
num_cars.times do | i |
real_index = i + 1
puts
print "Enter make for car #{real_index}: "
make = gets.chomp
print "Enter model for car #{real_index}: "
model = gets.chomp
print "Enter year for car #{real_index}: "
year = gets.to_i
=begin
c = Car.new(make, model, year)
array_of_cars << c
=end
# some will tell you to avoid unnecessary variables ...
array_of_cars << Car.new(make, model, year)
end
puts
puts 'You have the following cars:' # sorted by year for fun
array_of_cars.sort_by{ | car | car.year }.each do | car |
puts "#{car.year} #{car.make} #{car.model}"
end
end
end # class Car
Car.make_car
How to do exception handling for undefined local variables?
You can write
d = a + e rescue nil
which would catch the exception and assigns d = nil
.
Or
begin
d = a + e
rescue
end
Which only catches the exception and does nothing else because of the empty rescue block.
But I would consider this a bad practice. Because apart from in the console you should never run into this issue in real-life. This error is trivial and should be noticed and fixed before the app gets into production.
Ruby programming error (undefined local variable)
Your issue revolves around variable scope. payment
has a local scope, and therefore the function calc_payment
cannot "see" it. Here I modified your program so that you pass payment
, balance
, and apr
into the calc_payment
function. I also moved m_counter
into the function as well.
def calc_payment(payment, balance, apr)
m_counter = 0
payment_percentage = payment / balance * 100
monthly_apr = apr / 12
while balance > 0
m_counter = m_counter + 1
balance = balance / 100 * monthly_apr
balance = balance - payment
end
puts
puts "Monthly payment: $" + payment
puts "Balance payoff: " + m_counter + " months"
end
puts "Welcome to your credit card payment calculator!"
puts
puts "Please tell me your credit card balance."
balance = gets.chomp.to_f
puts "Please enter your interest rate %."
apr = gets.chomp.to_f
puts "How much $ would you like to pay every month?"
payment = gets.chomp.to_f
calc_payment(payment, balance, apr)
Undefined local variable in rails form
In your controller:
def show
@character = Character.find(params[:id])
@new_comment = @character.comments.build
end
Assuming there is a has_many
relation between character
and comment
.
In your view:
render partial: 'comments/form', locals: { new_comment: @new_comment }
or
render 'comments/form', new_comment: @new_comment
In your partial:
= simple_form_for new_comment, remote: true do |f|
Spot the Ruby syntax error: NameError: undefined local variable or method ` '
Do you perhaps have a Unicode space character in your code (as the result of a copy-paste from elsewhere)? Ruby will interpret that as a valid variable name! Proof:
script = <<-EOF
#{"\u00A0"} = "foo"
puts #{"\u00A0"}
EOF
puts "The contents of the script are:"
puts script
puts "The output of the script is:"
eval script
And output:
The contents of the script are:
= "foo"
puts
The output of the script is:
foo
I'd use a hex editor or some other scrubber to check for unicode characters in your source code. I can produce the same error message like so:
> eval "puts #{"\u00A0"}"
NameError: undefined local variable or method ` ' for main:Object
You can scan a file for non-ASCII characters like so:
def find_nonascii(file)
p_line = 1; p_char = 0
open(file, "r").each_char do |char|
if char.ord == 10
p_line += 1
p_char = 0
end
p_char += 1
puts "Found character #{char.ord} (#{char.inspect}) at line #{p_line}, character #{p_char}" if char.ord > 126
end
end
This will give you the position of the first non-ascii character in the script.
You can find a list of Unicode space characters here.
Related Topics
Inherit Class-Level Instance Variables in Ruby
Using SASS with User-Specified Colors
Time Gt Query Not Working with Mongoid and Ruby on Rails
Splitting Routes File into Multiple Files
In Ruby, What Are the Vertical Lines
How the Anchor \Z and \G Works in Ruby
Ruby Equivalent of PHP's ".=" (Dot Equals) Operator
How to Handle Method Order in Ruby
How to Make a Class Whose Constructor Looks Like the Constructor of a Built-In Class
Best Way to Split Arrays into Multiple Small Arrays in Ruby
/Config/Initializers/Secret_Token.Rb Not Being Generated. Why Not
Best Way to Use Twitter Bootstrap Icons as Links in Ruby on Rails 3
Generate Nested Hashes from Strings and Deep Merging in Ruby
How to Change the Locale Through Url
Cannot Load Such File -- MySQL2/2.4/Mysql2 (Loaderror) - Windows