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.
Related
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 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?
I can't seem to find any way to successfully set a decimal field to no value from a form, since the form is returning an empty string. Here's a super-simple test case that manually sets the field to ''
require 'dm-core'
require 'dm-mysql-adapter'
require 'dm-migrations'
DataMapper.setup(:default, 'mysql://localhost/test')
class Product
include DataMapper::Resource
property :id, Serial
property :list_price, Decimal
end
DataMapper.finalize
DataMapper.auto_migrate!
p = Product.new
puts p.save #=> true
p = Product.new(:list_price => '')
puts p.save #=> false
p = Product.new(:list_price => nil)
puts p.save #=> true
As you can see, the list_price field will happily save when it's not set, or when it's set to nil. However, when I use a blank string, it won't save at all -- it's seemingly not being typecast.
It seems like I must be missing something obvious here, since this is a pretty basic use-case for an ORM.
You could create your own setter method for the property to check its type. See the section "Over-riding Accessors" on the Datamapper properties documentation page.
Adding:
def list_price=(new_price)
new_price = nil if new_price == ''
super
end
to your Product class will cause any empty string being set as the value of list_price to be converted to nil, and allow the resource to be saved.
In Rspec I have the following:
describe "triangle.parameter" do
it "should return nil when it has 0 sides" do
#triangle = Triangle.new({})
#triangle.paramater.should be_nil
end
end
And I have my parameter method like so:
def parameter
return 4
end
I've tried true, false, 4, "apple" for parameter to return and nothing will fail. I also can't get it to fail with nothing in the method. What am I doing wrong?
You have #triangle.paramater instead of #triangle.parameter; since it doesn't know what paramater is, it will always be nil.
I don't know how to create a ruby method that accepts a hash of parameters. I mean, in Rails I'd like to use a method like this:
login_success :msg => "Success!", :gotourl => user_url
What is the prototype of a method that accepts this kind of parameters? How do I read them?
If you pass paramaters to a Ruby function in hash syntax, Ruby will assume that is your goal. Thus:
def login_success(hsh = {})
puts hsh[:msg]
end
A key thing to remember is that you can only do the syntax where you leave out the hash characters {}, if the hash parameter is the last parameter of a function. So you can do what Allyn did, and that will work. Also
def login_success(name, hsh)
puts "User #{name} logged in with #{hsh[:some_hash_key]}"
end
And you can call it with
login_success "username", :time => Time.now, :some_hash_key => "some text"
But if the hash is not the last parameter you have to surround the hash elements with {}.
With the advent of Keyword Arguments in Ruby 2.0 you can now do
def login_success(msg:"Default", gotourl:"http://example.com")
puts msg
redirect_to gotourl
end
In Ruby 2.1 you can leave out the default values,
def login_success(msg:, gotourl:)
puts msg
redirect_to gotourl
end
When called, leaving out a parameter that has no default value will raise an ArgumentError
Use one single argument. Ruby will transform the named values into a hash:
def login_success arg
# Your code here
end
login_success :msg => 'Success!', :gotourl => user_url
# => login_success({:msg => 'Success!', :gotourl => user_url})
If you really want to make sure you get a hash, instead of the default ruby duck typing, then you would need to control for it. Something like, for example:
def login_success arg
raise Exception.new('Argument not a Hash...') unless arg.is_a? Hash
# Your code here
end