Strange local variable behavior in Ruby [duplicate] - ruby

This question already has answers here:
Why can I refer to a variable outside of an if/unless/case statement that never ran?
(3 answers)
Closed 2 years ago.
I noticed a strange behavior with Ruby local variables in the code below. It seems that Ruby runs into the false part and sets params to nil. The code in irb is below:
2.1.2 :001 > def params
2.1.2 :002?> {a:1}
2.1.2 :003?> end
2.1.2 :014 > def go1!
2.1.2 :015?> p params
2.1.2 :016?> if false
2.1.2 :017?> params = 1
2.1.2 :018?> end
2.1.2 :019?> p params
2.1.2 :020?> end
=> :go1!
2.1.2 :021 > go1!
{:a=>1}
nil
=> nil
Can anyone explain this?

Ruby determines the lifetime of local variables while it parses the code, so even if params = 1 assignment wouldn't be reached, params will be interpreted as local variable (and set to nil by default) in this scope.
Here's the link to documentation:
http://docs.ruby-lang.org/en/2.1.0/syntax/assignment_rdoc.html#label-Local+Variables+and+Methods

Related

Undeclared instance variables default to nil?

Due to some sloppy coding on my part, I've noticed that undeclared instance variables seem to evaluate nil where undeclared local variables do not. Is this default nill value for instance variables intended behavior that I can exploit (like with a conditional to check whether the variable has been set true) or is this just a quirk that should be left alone?
2.6.6 :001 > puts #test
=> nil
2.6.6 :002 > puts test
Traceback (most recent call last):
2: from (irb):2
1: from (irb):2:in `test'
ArgumentError (wrong number of arguments (given 0, expected 2..3))
Per the Ruby doc:
Instance variables of ruby do not need declaration. This implies a flexible structure of objects. In fact, each instance variable is dynamically appended to an object when it is first referenced.
https://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/instancevars.html
You can also get a list of all previously defined instance variables:
ClassName.instance_variables
or
a = ClassName.new
a.instance_variables #specific to instance
tl;dr Yes, this is documented in Assignment.
Whether a thing is defined,and what it is defined as, can be had with the defined? keyword.
2.6.5 :003 > defined?(fnord)
=> nil
2.6.5 :004 > fnord = 42
=> 42
2.6.5 :005 > defined?(fnord)
=> "local-variable"
2.6.5 :006 > defined?($fnord)
=> nil
2.6.5 :007 > $fnord = 42
=> 42
2.6.5 :008 > defined?($fnord)
=> "global-variable"
2.6.5 :009 > defined?(#fnord)
=> nil
2.6.5 :010 > #fnord = 42
=> 42
2.6.5 :011 > defined?(#fnord)
=> "instance-variable"
2.6.5 :012 > defined?(FNORD)
=> nil
2.6.5 :013 > FNORD = 42
=> 42
2.6.5 :014 > defined?(FNORD)
=> "constant"
This is useful for debugging, but I'd discourage "exploiting" it in application code.
First, let's clear up your example. test is a method of main. It is inherited from Kernel#test.
2.6.5 :004 > defined?(test)
=> "method"
2.6.5 :005 > method(:test)
=> #<Method: main.test>
A proper example looks like this.
2.6.5 :008 > puts #fnord
=> nil
2.6.5 :009 > puts fnord
Traceback (most recent call last):
4: from /Users/schwern/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `<main>'
3: from /Users/schwern/.rvm/rubies/ruby-2.6.5/bin/irb:23:in `load'
2: from /Users/schwern/.rvm/rubies/ruby-2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):9
NameError (undefined local variable or method `fnord' for main:Object)
Local variables
The error hints at what's happening: ambiguity. fnord could be the local variable fnord or self.fnord. Neither have been declared, Ruby won't guess at what you meant, so you get a NameError.
In Ruby local variable names and method names are nearly identical. If you have not assigned to one of these ambiguous names ruby will assume you wish to call a method. Once you have assigned to the name ruby will assume you wish to reference a local variable.
Declared local variables can be discovered with local_variables.
2.6.5 :012 > foo = 42
=> 42
2.6.5 :013 > Kernel.local_variables
=> [:foo, :_]
Instance variables
An uninitialized instance variable has a value of nil. If you run Ruby with warnings enabled, you will get a warning when accessing an uninitialized instance variable.
There are various methods for introspecting instance variables like instance_variables and instance_variables_defined?. While there are some instances this might be necessary when writing libraries, I strongly discourage exploiting the distinction between nil and "undefined" in application code; it makes things fragile.
Class variables
Accessing an uninitialized class variable will raise a NameError exception.
Global variables
An uninitialized global variable has a value of nil.

Calling a module function in a ruby module

I want to call a module function to define a constant in a utility module in ruby. However, when I try this I get an error message. Here comes the code and the error:
module M
ABC = fun
module_function
def self.fun
"works"
end
end
Error message:
NameError: undefined local variable or method `fun' for M:Module
Any ideas? I also tried it without self and with M.fun but no success...
It is just that the method is not defined when you assign fun to ABC. Just change the order:
module M
def self.fun
"works"
end
ABC = fun
end
M::ABC
#=> "works"
If you dislike the order (constants below methods), you might want to consider to have the method itself to memorize its return value. A common pattern looks like:
module M
def self.fun
#cached_fun ||= begin
sleep 4 # complex calculation
Time.now # return value
end
end
end
M.fun
# returns after 4 seconds => 2017-03-03 23:48:57 +0100
M.fun
# returns immediately => 2017-03-03 23:48:57 +0100
Test this in you irb console:
$ irb
2.3.3 :001 > module M
2.3.3 :002?> def self.fun
2.3.3 :003?> "worked"
2.3.3 :004?> end
2.3.3 :005?>
2.3.3 :006 > ABC = fun
2.3.3 :007?> end
=> "worked"
2.3.3 :008 > M
=> M
2.3.3 :009 > M::ABC
=> "worked"
2.3.3 :010 >
The fact is that now you defined self.fun before using it.
In your code you used the method before defining it.

Ruby 2.3.0: assigning to nil as Hash does not raise NoMethodError [duplicate]

This question already has an answer here:
In Ruby, why does nil[1]=1 evaluate to nil?
(1 answer)
Closed 6 years ago.
I've just noticed a very puzzling change in behaviour between Ruby 2.2.4 and Ruby 2.3.0: trying to use [] on nil in an assignment does not raise a NoMethodError anymore.
Ruby 2.2.4:
box:~ jfoeh$ irb
2.2.4 :001 > a = nil
=> nil
2.2.4 :002 > a[:b] = 1
NoMethodError: undefined method `[]=' for nil:NilClass
from (irb):2
from /Users/jfoeh/.rvm/rubies/ruby-2.2.4/bin/irb:11:in `<main>'
In contrast Ruby 2.3.0:
box:~ jfoeh$ irb
2.3.0 :001 > a = nil
=> nil
2.3.0 :002 > a[:b] = 1
=> nil
Is that behaviour expected, or is this a regression of sorts?
We originally noticed this when we found such an assignment seemingly swallowing exceptions in 2.3:
2.3.0 :001 > require 'date'
=> true
2.3.0 :002 > a = nil
=> nil
2.3.0 :003 > a[:b] = Date.parse(nil)
=> nil
whereas Ruby 2.2 would execute the right-hand side first and raise a TypeError as one would expect.
This was a bug introduced in ruby version 2.3.0. It has since been fixed, as of version 2.3.1.
Here is the original issue that was raised, on ruby-lang.org, and here is the commit which resolves the issue.

Running eval without creating a block scope

I am trying to understanding eval and binding contexts in Ruby.
Consinder the following in irb
irb(main):001:0> eval "a = 42"
=> 42
irb(main):002:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):2
from /Users/niels/.rbenv/versions/2.1.3/bin/irb:11:in `<main>'
irb(main):003:0>
Why is a not defined?
If I declare a prior to evalling, the value 42 is assigned to a.
It appears to me that some sort of block scope applies where local variables are available inside the eval context, but any variables declared are only declared in the block scope.
How do I eval code without creating a new scope?
Why is a not defined?
a is defined within the binding of the eval'd code, but not outside of it. That's just how local variables work. They are local to the scope they are defined in. That's why they are called "local" variables, after all.
If I declare a prior to evalling, the value 42 is assigned to a.
Yes, eval's scope nests, just like block scope.
How do I eval code without creating a new scope?
You can't. In Ruby 1.8 and prior, eval would indeed leak variables into the surrounding scope, but that leak was fixed in 1.9 and onwards.
That is because a is not on the same context than irb.
look at this code
2.2.1 :001 > eval "a = 42"
=> 42
2.2.1 :002 > a
NameError: undefined local variable or method `a' for main:Object
from (irb):2
from /home/hbranciforte/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
it's the same as blocks
2.2.1 :001 > 1.times{|a| b=1}
=> 1
2.2.1 :002 > b
NameError: undefined local variable or method `b' for main:Object
from (irb):2
from /home/hbranciforte/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
But...
2.2.1 :001 > a=nil
=> nil
2.2.1 :002 > eval "a = 42"
=> 42
2.2.1 :003 > a
=> 42
it's like
2.2.1 :001 > b=nil
=> nil
2.2.1 :002 > 1.times{|a| b=1}
=> 1
2.2.1 :003 > b
=> 1
2.2.1 :004 >
Instances variables work because it is part of "self"
2.2.1 :001 > eval "#b = 42"
=> 42
2.2.1 :002 > #b
=> 42
2.2.1 :003 >
Take a look how to context could be sent to a method. from http://ruby-doc.org/core-2.2.0/Binding.html
def get_binding(param)
return binding
end
b = get_binding("hello")
b.eval("param") #=> "hello"
I don't know if I was clear but, that I want to say is that the real thing that really matters is the context (or scope) where ruby will allocate/deallocate memory and his access.

Verify version of a gem with bundler from inside Ruby

Is there a way to verify that I have the latest version of a gem from inside a Ruby program? That is, is there a way to do bundle outdated #{gemname} programmatically?
I tried looking at bundler's source code but I couldn't find a straight-forward way. Currently I'm doing this, which is fragile, slow and so inelegant:
IO.popen(%w{/usr/bin/env bundle outdated gemname}) do |proc|
output = proc.readlines.join("\n")
return output.include?("Your bundle is up to date!")
end
A way to avoid external execution:
For bundler 1.2.x
require 'bundler/cli'
# intercepting $stdout into a StringIO
old_stdout, $stdout = $stdout, StringIO.new
# running the same code run in the 'bundler outdated' utility
Bundler::CLI.new.outdated('rails')
# storing the output
output = $stdout.string
# restoring $stdout
$stdout = old_stdout
For bundler 1.3.x
require 'bundler/cli'
require 'bundler/friendly_errors'
# let's cheat the CLI class with fake exit method
module Bundler
class CLI
desc 'exit', 'fake exit' # this is required by Thor
def exit(*); end # simply do nothing
end
end
# intercepting $stdout into a StringIO
old_stdout, $stdout = $stdout, StringIO.new
# running the same code run in the 'bundler outdated' utility
Bundler.with_friendly_errors { Bundler::CLI.start(['outdated', 'rails']) }
# storing the output
output = $stdout.string
# restoring $stdout
$stdout = old_stdout
There is no programmatic way to use outdated command in bundler, because the code is in a Thor CLI file which prints output to the user. Bundler's tests are also issuing the command to the system and checking the output (Link to outdated tests).
It should be fairly simple to write your own method to mirror what the outdated method in cli.rb is doing, though. See the highlighted code here : Link to outdated method in Bundler source. Remove lines with Bundler.ui and return true/false based on the value of out_count
Update: I've extracted 'bundle outdated' into a reusable method without the console output and the exits. You can find the gist here : link to gist. I have tested this on bundler 1.3 and it seems to work.
bundle check list the gems that are out to date, you might want to use it.
Hmmm, sounds like you might want bundle show or gem env
Disappointing, this looks surprisingly difficult.
There are a couple of open issues in bundler where the official line appears to be:
At this point in time, there isn't a documented ruby API.
It's something that's on our list, though.
Looking through the bundler source code cli.rb, it's fairly clear that it's going to be tricky to call from ruby, or reproduce the code in a sensible manner.
Calling methods from CLI will be difficult because they're sprinkled with calls to exit.
Reproducing the code doesn't look fun either because there is quite a lot of bundler logic in there.
Good luck!
checking the source code of latest bundler source code
I could come up with this
https://github.com/carlhuda/bundler/blob/master/lib/bundler/cli.rb#L398
$ irb
1.9.3p327 :001 > require 'bundler'
=> true
1.9.3p327 :002 > def outdated_gems(gem_name,options={})
1.9.3p327 :003?> options[:source] ||= 'https://rubygems.org'
1.9.3p327 :004?> sources = Array(options[:source])
1.9.3p327 :005?> current_spec= Bundler.load.specs[gem_name].first
1.9.3p327 :006?> raise "not found in Gemfile" if current_spec.nil?
1.9.3p327 :007?> definition = Bundler.definition(:gems => [gem_name], :sources => sources)
1.9.3p327 :008?> options["local"] ? definition.resolve_with_cache! : definition.resolve_remotely!
1.9.3p327 :009?> active_spec = definition.index[gem_name].sort_by { |b| b.version }
1.9.3p327 :010?> if !current_spec.version.prerelease? && !options[:pre] && active_spec.size > 1
1.9.3p327 :011?> active_spec = active_spec.delete_if { |b| b.respond_to?(:version) && b.version.prerelease? }
1.9.3p327 :012?> end
1.9.3p327 :013?> active_spec = active_spec.last
1.9.3p327 :014?> raise "Error" if active_spec.nil?
1.9.3p327 :015?> outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
1.9.3p327 :016?> {:outdated=>outdated,:current_spec_version=>current_spec.version.to_s,:latest_version=>active_spec.version.to_s}
1.9.3p327 :017?> end
=> nil
1.9.3p327 :018 >
1.9.3p327 :019 >
1.9.3p327 :020 >
1.9.3p327 :021 >
1.9.3p327 :022 > outdated_gems('rake')
=> {:outdated=>true, :current_spec_version=>"10.0.3", :latest_version=>"10.0.4"}
This may not work with earlier version of bundler.

Resources