Undefined Local Variable Based on Syntax in Ruby

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



Leave a reply



Submit