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?
Related
I'm trying to pass some context (binding? To a block, since I'm wrapping a block into another. No idea how to do this.
Here is the code that demonstrates this. The problem happens at the wrap - when I don't wrap the proc gets the context as it should.
require 'sinatra'
class MyWebApp < Sinatra::Base
##help = {}
def processing_policy(policytag)
## do special stuff here that might end in halt()
end
def self.api_endpoint(http_method, uri, policytag, helptext)
##helptext[uri] = { policy: policytag, help: helptext }
if policytag.nil?
## It's an open endpoint. Create as-is. This part works
send(http_method, uri, &block)
else
## This is an endpoint with policy processing
send(http_method, uri) do |*args|
processing_policy(uri,policytag,request)
# I probably need to do some kind of binding passthru for passed block
# How do I do that???
block.call(*args) # Block doesn't get context things like request etc
end
end
end
api_endpoint(:post, '/open_endpoint', nil, 'Some open endpoint') do
"Anyone can view this - you posted #{request.body.read}"
end
api_endpoint(:post, '/close_endpoint', 'mypolicytag', 'Closed endpoint') do
"This is closed = #{request.body.read}"
# Doesn't work - block.call doesn't know about request since
# it doesn't have context
end
api_endpoint(:get, '/help', nil, "Help") do
"Help:\n\n" +
##help.map do |uri, data|
" #{uri} - policytag: #{data[:policy]} - #{data[:help]}\n"
end.join()
end
end
run MyWebApp
Any insights?
OK so I found the answer.
Instead of block.call(*args) I can use
instance_exec(*args, &block) and it works.
I need to clone an existing object and change that cloned object.
The problem is that my changes change original object.
Here's the code:
require "httparty"
class Http
attr_accessor :options
attr_accessor :rescue_response
include HTTParty
def initialize(options)
options[:path] = '/' if options[:path].nil? == true
options[:verify] = false
self.options = options
self.rescue_response = {
:code => 500
}
end
def get
self.class.get(self.options[:path], self.options)
end
def post
self.class.post(self.options[:path], self.options)
end
def put
self.class.put(self.options[:path], self.options)
end
def delete
self.class.put(self.options[:path], self.options)
end
end
Scenario:
test = Http.new({})
test2 = test
test2.options[:path] = "www"
p test2
p test
Output:
#<Http:0x00007fbc958c5bc8 #options={:path=>"www", :verify=>false}, #rescue_response={:code=>500}>
#<Http:0x00007fbc958c5bc8 #options={:path=>"www", :verify=>false}, #rescue_response={:code=>500}>
Is there a way to fix this?
You don't even need to clone here, you just need to make a new instance.
Right here:
test = Http.new({})
test2 = test
you don't have two instances of Http, you have one. You just have two variables pointing to the same instance.
You could instead change it to this, and you wouldn't have the problem.
test = Http.new({})
test2 = Http.new({})
If, however, you used a shared options argument, that's where you'd encounter an issue:
options = { path: nil }
test = Http.new(options)
# options has been mutated, which may be undesirable
puts options[:path] # => "/"
To avoid this "side effect", you could change the initialize method to use a clone of the options:
def initialize(options)
options = options.clone
# ... do other stuff
end
You could also make use of the splat operator, which is a little more cryptic but possibly more idiomatic:
def initialize(**options)
# do stuff with options, no need to clone
end
You would then call the constructor like so:
options = { path: nil }
test = Http.new(**options)
puts test.options[:path] # => "/"
# the original hasn't been mutated
puts options[:path] # => nil
You want .clone or perhaps .dup
test2 = test.clone
But depending on your purposes, but in this case, you probably want .clone
see What's the difference between Ruby's dup and clone methods?
The main difference is that .clone also copies the objects singleton methods and frozen state.
On a side note, you can also change
options[:path] = '/' if options[:path].nil? # you don't need "== true"
I am working on a Ruby course and I came across an error when running one of the examples. Here is my Ruby class:
require 'json'
class User
attr_accessor :email, :name, :permissions
def initialize(*args)
#email = args[0]
#name = args[1]
#permissions = User.permisisons_from_template
end
def self.permisisons_from_template
file = File.read 'user_permissions_template.json'
JSON.load(file, nil, symbolize_names: true)
end
def save
self_json = {email: #email, name: #name, permissions: #permissions}.to_json
open('users.json', 'a') do |file|
file.puts self_json
end
end
end
My runner file code looks like this:
require 'pp'
require_relative 'user'
user = User.new 'john.doe#example.com', 'John Doe'
pp user
user.save
I get this error when I run this command "ruby runner.rb":
/usr/local/rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/json/common.rb:156:in `initialize': options :symbolize_names and :create_additions cannot be used in conjunction (ArgumentError)
from /usr/local/rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/json/common.rb:156:in `new'
from /usr/local/rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/json/common.rb:156:in `parse'
from /usr/local/rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/json/common.rb:335:in `load'
from /home/ec2-user/environment/section2_project/user.rb:15:in `permisisons_from_template'
from /home/ec2-user/environment/section2_project/user.rb:10:in `initialize'
from runner.rb:4:in `new'
from runner.rb:4:in `<main>'
I looked for help on the site and the suggested fix was to remove the nil parameter. Now, I am from a .Net background and I surmised that I could use proc: nil and that would work as well, which it did. My assumption is that it didn't like the mixing of named parameters and positional parameters, but this isn't .Net, so I may just have gotten lucky with my guess. The moderator for the site wasn't sure why the code failed and why removing nil fixed the issue. So, my question is:
Why did the line JSON.load(file, nil, symbolize_names: true) fail, but JSON.load(file, proc: nil, symbolize_names: true) work? Thanks.
Wade
What's happening here is that the arguments aren't being parsed in the way you're expecting. There's a feature in Ruby where any key: value at the end of the argument list are made into a Hash without the need to put the {} around them.
For example if you write a method:
def load(source, options = {})
end
This can be called as load(source) in which case options will be {}, or as something like load(source, foo: 5, bar: true) in which case options will be {foo: 5, bar: true}
The other detail is that optional parameters with default values are filled left to right.
Why is this relevant?
Well, in the case of JSON.load(file, proc: nil, symbolize_names: true) the proc: nil, symbolize_names: true becomes the Hash {proc: nil, symbolize_names: true} and this then fills the proc position in the argument list, leaving the options parameter with its default value. i.e. you aren't actually setting symbolize_names: true when you thought you were.
In the case of JSON.load(file, nil, symbolize_names: true), the nil fills the value of the proc parameter, and symbolize_names: true becomes options. This gets combined with the default options in the JSON library to give the full set of options, {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true, :symbolize_names=>true} which then contains the conflict that the error message is referencing.
It is related on how arguments are passed in ruby methods.
Here you can find the json module source for load method.
And here below there is my version ;) for a sort of explanation.
def load(source, proc = nil, options = {})
# firs parameter required, second and third are optional
puts "source: #{source}"
puts "proc: #{proc}"
puts "options: #{options}"
puts "- "*20
end
my_dummy_proc = Proc.new{|e| e}
load('filename_1',my_dummy_proc , {option1: :option1, option2: :option2}) # the 3rd is a hash
load('filename_2', my_dummy_proc, option1: :option1, option2: :option2) # the 3rd as a hash but with no braces
load('filename_3') # you can pass just the first argument
load('filename_4', my_dummy_proc) # you can pass just the first and the second
load('filename_5', option1: :option1, option2: :option2) # but not just the first and the third, unless you set the 2nd to nil (a sort of placeholder) if you skip nil as 2nd parameter, the hash is assigned to the second argument
load('filename_6', nil, option1: :option1, option2: :option2) # if no 2nd argument is passed, you need to set the second parameter to nil (as a placeholder)
About the error message you got :symbolize_names and :create_additions cannot be used in conjunction,if you try, this should work:
JSON.load(file, nil, symbolize_names: true, create_additions: false)
Change symbolize_names: true to symbolize_names: false.
I'm trying to build a chat server in ruby using EventManager. Needless to day, I'm new to Ruby and feeling a little over my head with the current error I am getting, as I have no clue what it means and a search doesn't return anything valuable. Here's some of the logistics-
(ive only implemented LOGIN and REGISTER so I'll only include those..)
user can enter-
REGISTER username password - registers user
LOGIN username password - logins user
I'm taking in the string of data the user sends, splitting it into an array called msg, and then acting on the data based on msg[0] (as its the command, like REGISTER, LOGIN, etc)
Here is my code, all contained in a single file- chatserver.rb (explanation follows):
require 'rubygems'
require 'eventmachine'
class Server
attr_accessor :clients, :channels, :userCreds, :userChannels
def initialize
#clients = [] #list of clients connected e.g. [192.168.1.2, 192.168.1.3]
#users = {} #list of users 'logged in' e.g. [tom, sam, jerry]
#channels = [] #list of channels e.g. [a, b, c]
#userCreds = {} #user credentials hash e.g. { tom: password1, sam: password2, etc }
#userChanels = {} #users and their channels e.g. { tom: a, sam: a, jerry: b }
end
def start
#signature = EventMachine.start_server("127.0.0.1", 3200, Client) do |con|
con.server = self
end
end
def stop
EventMachine.stop_server(#signature)
unless wait_for_connections_and_stop
EventMachine.add_periodic.timer(1) { wait_for_connections_and_stop }
end
end
# Does the username already exist?
def has_username?(name)
#userCreds.has_key?(name)
end
# Is the user already logged in?
def logged_in?(name)
if #users[name] == 1
true
else
false
end
end
# Did the user enter the correct pwd?
def correct_pass?(pass)
if #userCreds[name] == pass
true
else
false
end
end
private
def wait_for_connections_and_stop
if #clients.empty?
EventMachine.stop
true
else
puts "Waiting for #{#clients.size} client(s) to stop"
false
end
end
end
class Connection < EventMachine::Connection
attr_accessor :server, :name, :msg
def initialize
#name = nil
#msg = []
end
# First thing the user sees when they connect to the server.
def post_init
send_data("Welcome to the lobby.\nRegister or Login with REGISTER/LOGIN username password\nOr try HELP if you get stuck!")
end
# Start parsing incoming data
def receive_data(data)
data.strip!
msg = data.split("") #split data by spaces and throw it in array msg[]
if data.empty? #the user entered nothing?
send_data("You didn't type anything! Try HELP.")
return
elsif msg[0] == "REGISTER"
handle_register(msg) #send msg to handle_register method
else
hanlde_login(msg) #send msg to handle_login method
end
end
def unbind
#server.clients.each { |client| client.send_data("#{#name} has just left") }
puts("#{#name} has just left")
#server.clients.delete(self)
end
private
def handle_register(msg)
if #server.has_username? msg[1] #user trying to register with a name that already exists?
send_data("That username is already taken! Choose another or login.")
return
else
#name = msg[1] #set name to username
#userCreds[name] = msg[2] #add username and password to user credentials hash
send_data("OK") #send user OK message
end
end
end
EventMachine::run do
s = Server.new
s.start #start server
puts "Server listening"
end
Whew, okay, it's only the beginning, so not that complicated. Since I'm new to Ruby I have a feeling I'm just not declaring variable or using scope correctly. Here's the error output:
chatserver.rb:16:in start': uninitialized constant Server::Client
(NameError) from chatserver.rb:110:inblock in ' from
/Users/meth/.rvm/gems/ruby-1.9.3-p392#rails3tutorial2ndEd/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in
call' from
/Users/meth/.rvm/gems/ruby-1.9.3-p392#rails3tutorial2ndEd/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in
run_machine' from
/Users/meth/.rvm/gems/ruby-1.9.3-p392#rails3tutorial2ndEd/gems/eventmachine-1.0.3/lib/eventmachine.rb:187:in
run' from chatserver.rb:108:in<\main>'
ignore the slash in main in that last line.
line 108 is the last function- EventMachine::run do etc.
Any help would be appreciated, if I didn't provide enough info just let me know.
I would think that when you call EventMachine::start_server you need to give it your Connection class as the handler. Client is not defined anywhere.
How do I test this tiny part of the module, with super? (superclass is action_dispatch-3.0.1 testing/integration...) The module is included within spec/requests to intercept post:
module ApiDoc
def post(path, parameters = nil, headers = nil)
super
document_request("post", path, parameters, headers) if ENV['API_DOC'] == "true"
end
...
end
I don't want it to run the ActionDispatch::Integration-whatever, but I don't know how to mock or stub super to unit test it.
The module is only used within specs, and will have 100% test coverage, which proves those kinds of metrics as useless. I need to unit test it.
An example, if needed, this is how I use the module ApiDoc
require 'spec_helper'
describe "Products API" do
include ApiDoc ############## <---- This is my module
context "POST product" do
before do
#hash = {:product => {:name => "Test Name 1", :description => "Some data for testing"}}
end
it "can be done with JSON" do
valid_json = #hash.to_json
############### the following 'post' is overriden by ApiDoc
post("/products.json",valid_json,
{"CONTENT_TYPE" => "application/json",
"HTTP_AUTHORIZATION" => ActionController::HttpAuthentication::Basic.encode_credentials("user", "secret")})
response.should be_success
end
end
end
You can check if the method is called on the 'super' class
ActionDispatch::Integration.any_instance.should_receive(:post)
Since ApiDock is only required for your tests you could also overwrite the post method with alias_method_chain:
ActionDispatch::Integration.instance_eval do
def post_with_apidoc(path, parameters = nil, headers = nil)
post_without_apidoc
if ENV['API_DOC'] == "true"
document_request("post", path, parameters, headers)
end
end
alias_method_chain :post, :apidoc
end
This is merely a supplement to the answer. This is how I ended up testing it
require 'spec_helper'
describe 'ApiDoc' do
include ApiDoc
it "should pass the post to super, ActionDispatch" do
#path = "path"
#parameters = {:param1 => "someparam"}
#headers = {:aheader => "someheaders"}
ActionDispatch::Integration::Session.any_instance.expects(:post).with(#path, #parameters, #headers)
post(#path, #parameters, #headers)
end
end
class DummySuper
def post(path, parameters=nil, headers=nil)
#How to verify this is called?
end
end
class Dummy < DummySuper
include ApiDoc
end
describe Dummy do
it "should call super" do
subject.expects(:enabled?).once.returns(true)
#how to expect super, the DummySuper.post ?
path = "path"
parameters = {:param1 => "someparam"}
headers = {:aheader => "someheaders"}
subject.expects(:document_request).with("post", path, parameters, headers)
subject.post(path, parameters, headers)
end
end
and the slightly modified ApiDoc.
module ApiDoc
def enabled?
ENV['API_DOC'] == "true"
end
def post(path, parameters = nil, headers = nil)
super
document_request("post", path, parameters, headers) if enabled?
end
private
def document_request(verb, path, parameters, headers)
...
end
end
I could verify the super.post in the first test, but I still can't figure out how to do just that with my Dummy class specs.