Regex global variables are not being set - ruby-on-rails

I came across something that seems unusual and I was wondering if anyone could explain why.
1.8.7 :001 > some_str = "Hello World"
=> "Hello World"
1.8.7 :002 > some_str.try(:match, /^(\w*)/)
=> #<MatchData "Hello" 1:"Hello">
1.8.7 :003 > $1
=> nil
1.8.7 :004 > some_str.match(/^(\w*)/)
=> #<MatchData "Hello" 1:"Hello">
1.8.7 :005 > $1
=> "Hello"
I'm not sure why the global variable $1 is not being set the first time, but is set the second. Any insights?

Let me show you how try is implemented. If you want to see it yourself, then take a look at the activesupport source. It's defined in /lib/active_support/core_ext/object/try.rb
class Object
def try(*a, &b)
if a.empty? && block_given?
yield self
else
public_send(*a, &b)
end
end
end
What this basically does, is just sending the method name and the complete arguments to the Object. public_send is the same as send, but can only be used to call public methods.
So I rewrote this, to debug your issue:
class Object
def try(*a)
result = public_send(*a)
puts $1.inspect
result
end
end
string = "Hello"
string.try(:match, /^(\w*)/)
puts $1.inspect
This outputs
"Hello"
nil
So the great question arises: Is this a bug in the ruby interpreter?. Maybe. At least it's not documented in any official source. I found a reference that tells the following (See Global variables.)
[...], $_ and $~ have local scope. Their names suggest they should be global, but they are much more useful this way, and there are historical reasons for using these names.
So it seems like $1 is not a global variable as well, even though it is reported by the Kernel as a global variable:
1.9.3-p194 :001 > global_variables
=> [:$;, :$-F, :$#, :$!, :$SAFE, :$~, :$&, :$`, :$', :$+, :$=, :$KCODE, :$-K,
:$,, :$/, :$-0, :$\, :$_, :$stdin, :$stdout, :$stderr, :$>, :$<, :$.,
:$FILENAME, :$-i, :$*, :$?, :$$, :$:, :$-I, :$LOAD_PATH, :$",
:$LOADED_FEATURES, :$VERBOSE, :$-v, :$-w, :$-W, :$DEBUG, :$-d, :$0,
:$PROGRAM_NAME, :$-p, :$-l, :$-a, :$binding, :$1, :$2, :$3, :$4, :$5, :$6,
:$7, :$8, :$9]
To make sure, I forwarded this incosistency to the Ruby Bug Tracker. See Ruby Bug #6723

try is defined as
def try(method, *args, &block)
send(method, *args, &block)
end
except of course on nil where it just returns nil. Why does this matter? Because the regexp globals aren't real globals: they're maintained on a per method and per thread basis (it's easy enough to see this by perusing the ruby source). When you call match via try the globals are set in the scope for try but in the next case they are set at the top level. It's easy to verify this
def do_match string, regexp
string =~ regexp
$1
end
do_match "Hello World", /^(\w*)/ #=> returns 'Hello'
$1 #=> returns nil

Related

RUBY: defined?(klass) seeing a local-variable instead of constant

I am running into an issue, where I need to check if a class exists. However, I am passing the class to a variable and trying to check it from there.
My issue is I need to pass the actual constant for defined?() to work, but I'm passing a variable, so instead of seeing a constant, it sees a method or variable.
obj is a rails model instance, for example, a specific User, or a specific Car.
def present(obj, presenter_class=nil, view_context=nil)
klass = presenter_class || "#{obj.class}Presenter".constantize
if defined?(klass) == 'constant' && klass.class == Class
klass.new(obj, view_context)
else
warn("#{self}: #{klass} is not a defined class, no presenter used")
obj
end
end
Pry Output:
[1] pry(ApplicationPresenter)> defined?(klass)
=> "local-variable"
I tried the below, but I get a method back...
[18] pry(ApplicationPresenter)> defined?("UserPresenter".constantize)
=> "method"
How can I fix this issue?
Well, apparently Object#defined? does not the thing that you hoped it would do.
tests whether or not expression refers to anything recognizable (literal object, local variable that has been initialized, method name visible from the current scope, etc.). The return value is nil if the expression cannot be resolved. Otherwise, the return value provides information about the expression.
Your goal looks like you are rebuilding what the draper gem is doing with .decorate... Don't forget that most of the gems are open source and you can use that for trying things on your own. See for example the decorator_class method from them
decorator_name = "#{prefix}Decorator"
decorator_name_constant = decorator_name.safe_constantize
return decorator_name_constant unless decorator_name_constant.nil?
They use the method safe_constantize and this apparently returns nil when the constant is not available.
2.6.5 :007 > class UserPresenter; end;
=> nil
2.6.5 :008 > 'UserPresenter'.safe_constantize
=> UserPresenter
2.6.5 :009 > 'ForgottenPresenter'.safe_constantize
=> nil
To me that looks exactly like what you need, and it also safer than using constantize
def present(obj, presenter_class=nil, view_context=nil)
klass = presenter_class || "#{obj.class}Presenter".safe_constantize
if klass != nil
klass.new(obj, view_context)
else
warn("#{self}: #{klass} is not a defined class, no presenter used")
obj
end
end

Ruby: interpolated string to variable name [duplicate]

This question already has answers here:
How to dynamically create a local variable?
(4 answers)
Closed 5 years ago.
In Ruby, how can I interpolate a string to make a variable name?
I want to be able to set a variable like so:
"post_#{id}" = true
This returns a syntax error, funnily enough:
syntax error, unexpected '=', expecting keyword_end
I believe you can do something like:
send("post_#{id}=", true)
That would require, of course, that you have appropriate setter/getter. Which, since you're doing this dynamically, you probably don't.
So, perhaps you could do:
instance_variable_set("#post_#{id}",true)
To retrieve the variable:
instance_variable_get("#post_#{id}")
BTW, if you get tired of typing instance_variable_set("#post_#{id}",true), just for fun you could do something like:
class Foo
def dynamic_accessor(name)
class_eval do
define_method "#{name}" do
instance_variable_get("##{name}")
end
define_method "#{name}=" do |val|
instance_variable_set("##{name}",val)
end
end
end
end
In which case you could:
2.3.1 :017 > id = 2
=> 2
2.3.1 :018 > f = Foo.new
=> #<Foo:0x00000005436f20>
2.3.1 :019 > f.dynamic_accessor("post_#{id}")
=> :post_2=
2.3.1 :020 > f.send("post_#{id}=", true)
=> true
2.3.1 :021 > f.send("post_#{id}")
=> true
2.3.1 :022 > f.send("post_#{id}=", "bar")
=> "bar"
2.3.1 :023 > f.send("post_#{id}")
=> "bar"
This concerns the getting and setting of local variables. Suppose
id = 1
s = "post_#{id}"
#=> "post_1"
Since Ruby v1.8 it has not been possible to create local variables dynamically. Therefore, if a local variable post_1 does not exist, the only way you can create it is by using an assignment statement:
post_1 = false
If the local variable post_1 exists, you can retrieve its value dynamically with
b = binding
b.local_variable_get(s)
#=> false
(or b.local_variable_get(s.to_sym)) and set its value dynamically with
b.local_variable_set(s, true)
#=> true
post_1
#=> true
(or b.local_variable_set(s.to_sym, true)).
See Binding#local_variable_get and Binding#local_variable_set.

clear all variables in rails console

can any body tell me what command is used to clear all variables in rails console?
e.g.
1.9.1 :001 > permissions = {:show => true}
=> {:show=>true}
1.9.1 :001 > foo = "bar"
=> "bar"
I need a command that can get all variables reset to nil without a restart of rails console itself.
Any advice would be very much appreciated.
local_variables.each { |e| eval("#{e} = nil") }
local_variables returns the list of symbols of all local variables in the current scope
a, b = 5, 10
local_variables # => [:b, :a]
Using each you can iterate over this list an use eval to assign their values to nil.
You can also do the same thing with instance_variables and global_variables. For example
(local_variables + instance_variables).each { |e| eval("#{e} = nil") }
By the way, if you are going to use it more than once, it might be helpful to define such method in ~/.irbrc file to make it accessible for all irb sessions (didn't test it in rails console).
class Binding
def clear
eval %q{ local_variables.each { |e| eval("#{e} = nil") } }
end
end
Then, inside irb session
a = 5
binding.clear
a # => nil
Do one thing, type
irb 'another'
and then press ctrl+l
now check the values of your variables.
It works.

Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]

This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]

YAML Encoding of Malformed String, Model Serialization Issues

I've isolated a problem with Ruby on Rails where a model with a serialized column is not properly loading data that has been saved to it.
What goes in is a Hash, and what comes out is a YAML string that can't be parsed due to formatting issues. I'd expect that a serializer can properly store and retrieve anything you give it, so something appears to have gone wrong.
The troublesome string in question is formatted something like this:
message_text = <<END
X
X
END
yaml = message_text.to_yaml
puts yaml
# =>
# --- |
#
# X
# X
puts YAML.load(yaml)
# => ArgumentError: syntax error on line 3, col 0: ‘X’
The combination of newline, indented second line, and non-indented third line causes the parser to fail. Omitting either the blank line or the indentation appears to remedy the problem, but this does seem to be a bug in the serialization process. Since it requires a rather unique set of circumstances, I'm willing to bet this is some strange edge-case that isn't properly handled.
The YAML module that ships with Ruby and is used by Rails looks to delegate a large portion of the processing to Syck, yet does provide Syck with some hints as to how to encode the data it is sending.
In yaml/rubytypes.rb there's the String#to_yaml definition:
class String
def to_yaml( opts = {} )
YAML::quick_emit( is_complex_yaml? ? self : nil, opts ) do |out|
if is_binary_data?
out.scalar( "tag:yaml.org,2002:binary", [self].pack("m"), :literal )
elsif to_yaml_properties.empty?
out.scalar( taguri, self, self =~ /^:/ ? :quote2 : to_yaml_style )
else
out.map( taguri, to_yaml_style ) do |map|
map.add( 'str', "#{self}" )
to_yaml_properties.each do |m|
map.add( m, instance_variable_get( m ) )
end
end
end
end
end
end
There appears to be a check there for strings that start with ':' and could be confused as Symbol when de-serializing, and the :quote2 option should be an indication to quote it during the encoding process. Adjusting this regular expression to catch the conditions described above does not appear to have any effect on the output, so I'm hoping someone more familiar with the YAML implementation can advise.
Yep, that looks like a bug in the C syck library. I checked it out using the PHP syck bindings (v 0.9.3): http://pecl.php.net/package/syck and the same bug is present, indicating it is a bug in the library as opposed to the ruby yaml library or ruby-syck bindings:
// phptestsyck.php
<?php
$message_text = "
X
X
";
syck_load(syck_dump($message_text));
?>
Running this on the cli gives the same SyckException:
$ php phptestsyck.php
PHP Fatal error: Uncaught exception 'SyckException' with message 'syntax error on line 5, col 0: 'X'' in /.../phptestsyck.php:8
Stack trace:
#0 /.../phptestsyck.php(8): syck_load('--- %YAML:1.0 >...')
#1 {main}
thrown in /.../phptestsyck.php on line 8
So, I suppose you could try to fix Syck itself. It appears that the library hasn't been updated since v0.55 in May of 2005 (http://rubyforge.org/projects/syck/), though.
Alternately, there is a pure-ruby yaml parser called RbYAML (http://rbyaml.rubyforge.org/) which originated with JRuby that doesn't appear to have this bug:
>> require 'rbyaml'
=> true
>> message_text = <<END
X
X
END
=> "\n X\nX\n"
>> yaml = RbYAML.dump(message_text)
=> "--- "\\n X\\nX\\n"\n"
>> RbYAML.load(yaml)
=> "\n X\nX\n"
>>
Finally, have you considered another serialization format altogether? Ruby's Marshal library doesn't have this bug either and is faster than Yaml (see http://significantbits.wordpress.com/2008/01/29/yaml-vs-marshal-performance/):
>> message_text = <<END
X
X
END
=> "\n X\nX\n"
>> marshal = Marshal.dump(message_text)
=> "\004\b"\f\n X\nX\n"
>> Marshal.load(marshal)
=> "\n X\nX\n"
You have to give up the easy serialize ActiveRecord::Base method to do so, but it's not hard otherwise to use your own serializing scheme.
For example, to serialize some field called 'person_data':
class Person < ActiveRecord::Base
def person_data
self[:person_data] ? Marshal.load(self[:person_data]) : nil
end
def person_data=(x)
self[:person_data] = Marshal.dump(x)
end
end
## User Person#person_data as normal and it is transparently marshalled
p = Person.find 1
p.person_data = {:color => "blue", :food => "vegetarian"}
(See this ruby forum thread for more)

Resources