I would like to monkey patch String with #to_ivar.
class String
def to_ivar
instance_variable_get("##{self}")
end
end
So that when I have an environment with instance variables like this:
instance variables:
#__cucumber_runtime #browser #browser_type #debug #sauce_config #user_A
I can do this in a step definition:
Given(/^user (.*) does something$/) do |user|
#user = user.to_ivar
...
end
So I did, but, it seems that String#to_ivar does not know about the instance variables:
13: def to_ivar
=> 14: binding.pry
15: instance_variable_get("##{self}")
16: end
[2] pry("user_A")> instance_variables
=> []
Is there a way for an instance of String to know about the Cucumber world and its instance variables?
Related
I recently read a blog post about Ruby's behaviours with regards to a local variable shadowing a method (different to, say, a block variable shadowing a method local variable, which is also talked about in this StackOverflow thread), and I found some behaviour that I don't quite understand.
Ruby's documentation says that:
[V]ariable 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.
So, given the following example class
# person.rb
class Person
attr_accessor :name
def initialize(name = nil)
#name = name
end
def say_name
if name.nil?
name = "Unknown"
end
puts "My name is #{name.inspect}"
end
end
and given what I now know from reading the information from the links above, I would expect the following:
The name.nil? statement would still refer to the name instance method provided by attr_accessor
When the Ruby parser sees the name = "Unknown" assignment line in the #say_name method, it will consider any reference to name used after the assignment to refer to the local variable
Therefore, even if the Person had a name assigned to it on initialisation, the name referenced in the final line of #say_name method would be nil
And it looks like this can be confirmed in an irb console:
irb(main):001:0> require "./person.rb"
true
# `name.nil?` using instance method fails,
# `name` local variable not assigned
irb(main):002:0> Person.new("Paul").say_name
My name is nil
nil
# `name.nil?` using instance method succeeds
# as no name given on initialisation,
# `name` local variable gets assigned
irb(main):003:0> Person.new.say_name
My name is "Unknown"
nil
However, if I do some inline debugging and use Pry to attempt to trace how the referencing of name changes, I get the following:
irb(main):002:0> Person.new("Paul").say_name
From: /Users/paul/person.rb # line 13 Person#say_name:
10: def say_name
11: binding.pry
12:
=> 13: p name
14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
[1] pry(#<Person>)> next
"Paul"
Okay, that makes sense as I'm assuming name is referring to the instance method. So, let's check the value of name directly...
From: /Users/paul/person.rb # line 14 Person#say_name:
10: def say_name
11: binding.pry
12:
13: p name
=> 14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
[2] pry(#<Person>)> name
nil
Err... that was unexpected at this point. I'm currently looking at a reference to name above the assignment line, so I would have thought it would still reference the instance method and not the local variable, so now I'm confused... I guess somehow the name = "Unknown" assignment will run, then...?
[3] pry(#<Person>)> exit
My name is nil
nil
Nope, same return value as before. So, what is going on here?
Was I wrong in my assumptions about name.nil? referencing the name instance method? What is it referencing?
Is all this something related to being in the Pry environment?
Something else I've missed?
For reference:
➜ [ruby]$ ruby -v
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]
Edit
The example code in this question is meant to be illustrative of the (I think) unexpected behaviour I'm seeing, and not in any way illustrative of actual good code.
I know that this shadowing issue is easily avoided by re-naming the local variable to something else.
Even with the shadowing, I know that it is still possible to avoid the problem by specifically invoking the method, rather than reference the local variable, with self.name or name().
Playing around with this further, I'm starting to think it's perhaps an issue around Pry's environment. When running Person.new("Paul").say_name:
From: /Users/paul/person.rb # line 13 Person#say_name:
10: def say_name
11: binding.pry
12:
=> 13: p name
14: if name.nil?
15: name = "Unknown"
16: end
17:
18: puts "My name is #{name.inspect}"
19: end
At this point, the p statement hasn't run yet, so let's see what Pry says the value of name is:
[1] pry(#<Person>)> name
nil
This is unexpected given that Ruby's documentation says that since no assignment has been made yet, the method call should be invoked. Let's now let the p statement run...
[2] pry(#<Person>)> next
"Paul"
...and the value of the method name is returned, which is expected.
So, what is Pry seeing here? Is it modifying the scope somehow? Why is it that when Pry runs name it gives a different return value to when Ruby itself runs name?
Once Ruby has decided that name is a variable and not a method call that information applies to the totality of the scope it appears within. In this case it's taking it to mean the whole method. The trouble is if you have a method and a variable with the same name the variable only seems to take hold on the line where the variable has been potentially assigned to and this re-interpretation affects all subsequent lines within that method.
Unlike in other languages where method calls are made clear either by some kind of prefix, suffix or other indicator, in Ruby name the variable and name the method call look identical in code and the only difference is how they're interpreted at "compile" time proior to execution.
So what's happening here is a little confusing and subtle but you can see how name is being interpreted with local_variables:
def say_name_local_variable
p defined?(name) # => "method"
p local_variables # => [:name] so Ruby's aware of the variable already
if name.nil? # <- Method call
name = "Unknown" # ** From this point on name refers to the variable
end # even if this block never runs.
p defined?(name) # => "local-variable"
p name # <- Variable value
puts "My name is #{name.inspect}"
end
I'm quite surprised that, given how obnoxiously particular Ruby can be with the -w flag enabled, that this particular situation generates no warnings at all. This is likely something the'll have to emit a warning for, a strange partial shadowing of methods with variables.
To avoid method ambiguity you'll need to prefix it to force it to be a method call:
def say_name
name = self.name || 'Unknown'
puts "My name is #{name.inspect}"
end
One thing to note here is that in Ruby there are only two logically false values, literal nil and false. Everything else, including empty strings, 0, empty arrays and hashes, or objects of any kind are logically true. That means unless there's a chance name is valid as literal false then || is fine for defaults.
Using nil? is only necessary when you're trying to distinguish between nil and false, a situation that might arise if you have a three-state checkbox, checked, unchecked, or no answer given yet.
What looks like inconsistent return values for name during runtime and while debugging doesn't seem to related to Pry, but more about binding itself encapsulating the entire execution context of a method, versus the progressive change in what shadowed variables reference at runtime. To build on the example method with some more debugging code:
def say_name
puts "--- Before assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"
puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"
puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"
if name.nil?
name = "Unknown"
end
puts "--- After assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"
puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"
puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"
puts "My name is #{name.inspect}"
end
Now, running Person.new("Paul").say_name outputs:
--- Before assignment of name: ---
defined?(name) : "method"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : "Paul"
binding.eval('name') : nil
--- After assignment of name: ---
defined?(name) : "local-variable"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : nil
binding.eval('name') : nil
My name is nil
which shows that binding never references the method call of name and only ever the eventually-assigned name variable.
I was testing a snippet today
unless resource.nil?
resource = resource.becomes(Accounts::Admin)
end
this raises an error
undefined method `becomes' for nil:NilClass
if I do this
unless resource.nil?
a = resource.becomes(Accounts::Admin)
resource = a
end
Everything goes right.
What is the difference if the right part of the =operator is executed first ?
EDIT :
Something nasty is happening, the last line under if false is being executed, but "ALOHA" is never printed.
<%
puts "AAAA #{resource.inspect}"
if false
puts "ALOHA"
# this line is being executed !
# if I comment it out the BBBB output is correct
resource = nil
end
puts "BBBB #{resource.inspect}"
%>
it prints
AAAA User id: nil, nome: nil, endereco_id: nil, created_at: nil,
updated_at: nil, email: ""
BBBB nil
but if I do this
<%
res = resource
puts "AAAA #{res.inspect}"
if false
puts "ALOHA"
res = nil
end
puts "BBBB #{res.inspect}"
%>
it print correctly
AAAA User id: nil, nome: nil, endereco_id: nil, created_at: nil,
updated_at: nil, email: ""
BBBB User id: nil, nome: nil,
endereco_id: nil, created_at: nil, updated_at: nil, email: ""
I already have tried to restart the server. This snippet is at devise/registrations/new.html.erb. The resourcevariable is supposed to be an instance of User, created by devise's RegistrationController
I have checked the text for hidden characters, the snippets I pasted here are the whole text for the file being tested.
This is the content of the controller at ~/.rvm/gems/ruby-2.3.3#mygem/gems/devise-4.2.0/app/controllers/devise/registrations_controller.rb
class Devise::RegistrationsController < DeviseController
prepend_before_action :require_no_authentication, only: [:new, :create, :cancel]
prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy]
prepend_before_action :set_minimum_password_length, only: [:new, :edit]
# GET /resource/sign_up
def new
build_resource({})
yield resource if block_given?
respond_with resource
end
# POST /resource
def create
build_resource(sign_up_params)
resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message! :notice, :signed_up
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
end
# GET /resource/edit
def edit
render :edit
end
# PUT /resource
# We need to use a copy of the resource because we don't want to change
# the current user in place.
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
if is_flashing_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
set_flash_message :notice, flash_key
end
bypass_sign_in resource, scope: resource_name
respond_with resource, location: after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end
# DELETE /resource
def destroy
resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message! :notice, :destroyed
yield resource if block_given?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
def cancel
expire_data_after_sign_in!
redirect_to new_registration_path(resource_name)
end
protected
def update_needs_confirmation?(resource, previous)
resource.respond_to?(:pending_reconfirmation?) &&
resource.pending_reconfirmation? &&
previous != resource.unconfirmed_email
end
# By default we want to require a password checks on update.
# You can overwrite this method in your own RegistrationsController.
def update_resource(resource, params)
resource.update_with_password(params)
end
# Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
end
# Signs in a user on sign up. You can overwrite this method in your own
# RegistrationsController.
def sign_up(resource_name, resource)
sign_in(resource_name, resource)
end
# The path used after sign up. You need to overwrite this method
# in your own RegistrationsController.
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
# The path used after sign up for inactive accounts. You need to overwrite
# this method in your own RegistrationsController.
def after_inactive_sign_up_path_for(resource)
scope = Devise::Mapping.find_scope!(resource)
router_name = Devise.mappings[scope].router_name
context = router_name ? send(router_name) : self
context.respond_to?(:root_path) ? context.root_path : "/"
end
# The default url to be used after updating a resource. You need to overwrite
# this method in your own RegistrationsController.
def after_update_path_for(resource)
signed_in_root_path(resource)
end
# Authenticates the current scope and gets the current resource from the session.
def authenticate_scope!
send(:"authenticate_#{resource_name}!", force: true)
self.resource = send(:"current_#{resource_name}")
end
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up)
end
def account_update_params
devise_parameter_sanitizer.sanitize(:account_update)
end
def translation_scope
'devise.registrations'
end
end
ruby-2.3.3
rails (4.2.7.1)
devise (4.2.0)
Look at this ruby snippet:
if true
foo = "hello"
end
puts foo
#=> hello
And this one:
if false
foo = "hello"
end
puts foo
#=> nil
In many languages, if-statements have their own scope, but in ruby they share the scope of the surrounding function. This means variables declared inside if-statements are accessible outside of if-statements.
The issue here is that variables are declared at compile-time, before ruby knows whether the if-statement is true or false. So in ruby, all local variables are declared and initialized as nil, even if they're in a conditional statement.
This code:
unless resource.nil?
resource = resource.becomes(Accounts::Admin)
end
Causes problems because of another rule in ruby which gives local variables priority over methods. So when you say resource = resource you're actually calling the method resource and saving its value to the local variable resource, which then overshadows the method by the same name.
Ultimately you're getting the error:
undefined method `becomes' for nil:NilClass
because at compile-time, the local variable resource is being created, overshadowing the method. Then, at run-time, the condition is being executed because resource is not yet nil. But at the line where you create the local variable, it instantly comes into scope, making resource = nil and causing this the error.
The error can be reproduced in this generalized example:
def blah
"foo"
end
unless blah.nil?
blah = blah.size
end
puts blah
And the fix for it is to specify the method itself:
def blah
"foo"
end
def blah= value
#do nothing
end
unless blah.nil?
self.blah = blah.size
end
puts blah
That being said, I'm not sure whether devise actually implements resource=(). If it doesn't, then your best solution is the one you already came up with- use a local variable:
unless resource.nil?
res = resource.becomes(Accounts::Admin)
end
puts res
After doing some research, I found that local variables in ruby are defined from top to bottom and left to right, based on position in the file, not their position in program flow. For example:
if x="foo"
puts x
end
#=> "foo"
puts y if y="foo"
#NameError: undefined variable or method 'y'
This is part of the ruby spec, according to matz.
If using ruby Ruby 2.3.0 and above use Safe Navigation Operator (&.)
resource = resource&.becomes(Accounts::Admin)
Try this in IRb, it should be fine:
a = 'hello'
unless a.nil?
a = a.upcase
end
a
# => "HELLO"
The second example is equivalent to the first, the expression on the right side of the = is assigned to the var on the left.
Maybe some hidden characters or a mis-spelling?
Are you sure the stacktrace points to that line, are you using becomes elsewhere in the file?
This is what my class looks like:
require 'oga'
require 'net/http'
require 'pry'
module YPCrawler
class PageCrawler
def initialize(url)
#url = 'http://www.someurl.com'
end
def get_page_listings
body = Net::HTTP.get(URI.parse(#url))
document = Oga.parse_html(body)
bizlistings = document.css('div.result')
binding.pry
end
end
end
Yet when I get thrown into pry, I see this:
[1] pry(YPCrawler::PageCrawler)> #url
=> nil
[2] pry(YPCrawler::PageCrawler)> body
NameError: undefined local variable or method `body' for YPCrawler::PageCrawler:Class
from (pry):2:in `<class:PageCrawler>'
[3] pry(YPCrawler::PageCrawler)> document
NameError: undefined local variable or method `document' for YPCrawler::PageCrawler:Class
from (pry):3:in `<class:PageCrawler>'
[4] pry(YPCrawler::PageCrawler)> bizlistings
NameError: undefined local variable or method `bizlistings' for YPCrawler::PageCrawler:Class
from (pry):4:in `<class:PageCrawler>'
[5] pry(YPCrawler::PageCrawler)> url
NameError: undefined local variable or method `url' for YPCrawler::PageCrawler:Class
Did you mean? URI
from (pry):5:in `<class:PageCrawler>'
[6] pry(YPCrawler::PageCrawler)> #url
=> nil
Why can I not access #url that was initialized in my def initialize method?
Edit 1
Added Screenshots of what my code and the terminal PRY session really look like, since there was some disbelief about the position of my binding.pry.
Edit 2
My main lib/yp-crawler.rb file looks like this:
require_relative "yp-crawler/version"
require_relative "yp-crawler/page-crawler"
require_relative "yp-crawler/listing-crawler"
module YPCrawler
end
So the code that is run above is my yp-crawler/page-crawler.rb file, which I included in my lib/yp-crawler.rb file.
Edit 3
Here is a recording of my entire workflow. Please tell me what I am missing:
https://www.dropbox.com/s/jp1abthfkiplb4p/Pry-not-cooperating.webm?dl=0
I bet your code looks as follows:
module YPCrawler
class PageCrawler
attr_reader :url
def initialize(url)
#url = 'http://www.someurl.com'
end
def get_page_listings
body = Net::HTTP.get(URI.parse(#url))
document = Oga.parse_html(body)
bizlistings = document.css('div.result')
end
binding.pry
end
end
Even though you might have moved the binding.pry into method, most likely you did not reload the console, so it executes the "wrong" version.
From your screenshots it is clear that either file is not reloaded or you just made changes to wrong file.
I'm trying to write unit tests for my code using rspec. I keep getting a "wrong number of arguments" error:
class MyClass
attr_accessor :env, :company,:size, :role, :number_of_hosts,:visability
def initialize(env, company, size, role, number_of_hosts, visability)
#env, #company, #size, #role, #number_of_hosts, #visability = env, company, size, role, number_of_hosts, visability
end
end
And here are my tests:
require_relative "../lib/MyClass.rb"
describe MyClass do
it "has an environment" do
MyClass.new("environment").env.should respond_to :env
end
it "has a company" do
MyClass.new("company").company.should respond_to :company
end
...
When I run rspec I get:
1) MyClass has an environment
Failure/Error: MyClass.new("environment").env.should respond_to :env
ArgumentError:
wrong number of arguments (1 for 6)
# ./lib/MyClass.rb:4:in `initialize'
# ./spec/MyClass_spec.rb:5:in `new'
# ./spec/MyClass_spec.rb:5:in `block (2 levels) in <top (required)>'
...
What am I missing?
EDIT
Sergio helped thanks...however
Sergio's answer worked...although I still have a further question:
Given the Class:
class Team
attr_accessor :name, :players
def initialize(name, players = [])
raise Exception unless players.is_a? Array
#name = name
raise Exception if #name && has_bad_name
#players = players
end
def has_bad_name
list_of_words = %w{crappy bad lousy}
list_of_words - #name.downcase.split(" ") != list_of_words
end
def favored?
#players.include? "George Clooney"
end
end
and spec...
require_relative "../lib/team.rb"
describe Team do
it "has a name" do
Team.new("random name").should respond_to :name
end
it "has a list of players" do
Team.new("random name").players.should be_kind_of Array
end
...
The tests pass without the same error...(This works fine: Team.new("random name"))
Any explanation?
Here is the error MyClass.new("environment"). As you have written def initialize(env, company, size, role, number_of_hosts, visability). So you should pass 6 parameters when you are calling MyClass#new method. But in practice you pass only one which is "environment". Thus you got the legitimate error - wrong number of arguments (1 for 6).
I am creating a Word class and I am getting an error:
TypeError: superclass mismatch for class Word
Here is the irb code:
irb(main):016:0> class Word
irb(main):017:1> def palindrome?(string)
irb(main):018:2> string == string.reverse
irb(main):019:2> end
irb(main):020:1> end
=> nil
irb(main):021:0> w = Word.new
=> #<Word:0x4a8d970>
irb(main):022:0> w.palindrome?("foobar")
=> false
irb(main):023:0> w.palindrome?("level")
=> true
irb(main):024:0> class Word < String
irb(main):025:1> def palindrome?
irb(main):026:2> self == self.reverse
irb(main):027:2> end
irb(main):028:1> end
TypeError: superclass mismatch for class Word
from (irb):24
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.2.1/lib/rails/commands/console.rb:47:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.2.1/lib/rails/commands/console.rb:8:in `start'
from C:/Ruby193/lib/ruby/gems/1.9.1/gems/railties-3.2.1/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
A thumb rule for irb (either way irb or rails console)
If you are creating the same class twice with inheritance (superclass), exit the irb instance and create it again. Why this? Because otherwise class conflicts will happen.
In your case, you are using Windows (found from the question), so just type exit on DOS prompt and again type irb or rails console and create your Word class and it should work. Please let me know if it doesn't work for you.
The reason it gives you a superclass mismatch error is because you have already defined the Word class as inheriting from Object
class Word
...
end
In Ruby (like in most dynamic languages) you can monkey-patch classes by reopening the definition and modifying the class. However, in your instance, when you are reopening the class you are also attempting to redefine the class as inheriting from the super class String.
class Word < String
...
end
Once a class and it's inheritance structure have been defined, you cannot define it again.
As a few people have said, exiting and restarting irb will allow you to start from scratch in defining the Word class.
link664 has clearly explained the problem.
However, there's an easier fix without quitting irb (and losing all your other work).
You can delete an existing class definition this way.
irb(main):051:0> Object.send(:remove_const, :Word)
and you can verify with:
irb(main):052:0> Word.public_instance_methods
which should return:
NameError: uninitialized constant Word
from (irb):52
An easy way to bypass this issue is to encapsulate both classes between different modules:
> module M
> class Word
> def palindrome?(string)
> string == string.reverse
> end
> end
> end
=> nil
> w = M::Word.new
=> #<Word:0x4a8d970>
> w.palindrome?("foobar")
=> false
> w.palindrome?("level")
=> true
> module N
> class Word < String
> def palindrome?
> self == self.reverse
> end
> end
> end
> N::Word.new("kayak").palindrome?
=> true