Here is a sample code from a RSpec code:
describe Thing do
def create_thing(options)
thing = Thing.new
thing.set_status(options[:status])
thing
end
it "should do something when ok" do
thing = create_thing(:status => 'ok')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
So my confusion is mostly on this line:
thing.set_status(options[:status])
So create_thing method has an "option" parameter then we are passing status part of that parameter? Can someone explain this syntax in some easier words?
options is just a variable. The part you need to understand is this part
thing = create_thing(:status => 'ok')
You are basically passing a Hash to create_thing and therefore options is a hash. Then you can access the value of the status key by doing options[:status].
If the above mentioned line looked like this
thing = create_thing("Foo")
options would be "Foo" and you could get an error trying to do something like options[:status]
create_thing takes an argument called options.
Options is expected to be a hash (most likely).
You're passing the hash value with the key (a symbol):option to the set_status method.
You've passed an implicit hash to create_thing:
create_thing({ status: 'ok' }) is the same as
create_thing(status: 'ok') is the same as
create_thing(:status => 'ok')
Any way you call it, you access that value via options[:status].
Related
I have a very simple example where sinatra simply returns no output.
The program enters the if clause but the block is not finished and therefore nothing is sent to rack, nothing goes to the browser... not a single character.
require 'sinatra'
get '/' do
var='confirmed'
if var == 'confirmed'
'Confirmed'
end
if var == 'declined'
'Declined'
end
end
The question is now: Is adding a "return" or "next" the way this is usually done? With it, its running... But I never found an example in the net that had to use a next statement...
So, is the "if logic" usually somewhere else and there is only a single erb :xyz at the end of a route?
I am confused...
You have the answer mostly. You always need to send something to rack to get a response.
You probably have a view to show the status on then you add at the end something like this (You can have multiple erb blocks just add for each route a erb call):
get '/' do
var='confirmed'
if var == 'confirmed'
st = 'Confirmed'
end
if var == 'declined'
st = 'Declined'
end
erb :myViewName, :locals => {:status => st}
end
Or just use return like this, if your response is just a string. Be aware that everything after this return isn't executed:
if var == 'confirmed'
return 'Confirmed'
end
It's nothing to do with the way Sinatra works, really. It's more of a Ruby matter. According to Sinatra readme:
The return value of a route block determines at least the response body passed on to the HTTP client, or at least the next middleware in the Rack stack. Most commonly, this is a string, as in the above examples. But other values are also accepted.
The problem in your code is that your last if is a statement itself. If your var variable isn't "declined", then the if block evaluates to nil, and as it is the last value in your route block, this is what gets returned by Sinatra.
When you use explicit return, you don't get to the second if and don't have this issue, which is why it works with explicit return.
You would not need an explicit return with a if/elsif block like this:
# This is one single statement that would return either confirmed or declined
# Request will always return a non-nil value
get '/' do
...
if var == 'confirmed'
'Confirmed'
elsif var == 'declined'
'Declined'
end
end
Or a case/when block:
# This is one single statement that would return either confirmed or declined
# Request will always return a non-nil value
get '/' do
...
case var
when 'confirmed' then 'Confirmed'
when 'declined' then 'Declined'
end
end
irb(main):001:0> a="run: yes"
=> "run: yes"
irb(main):002:0> require 'yaml'
=> true
irb(main):003:0> YAML.load a
=> {"run"=>true}
irb(main):004:0> YAML.load(a, handlers => {'bool#yes' = identity})
SyntaxError: (irb):4: syntax error, unexpected '=', expecting =>
YAML.load(a, handlers => {'bool#yes' = identity})
^
from /usr/bin/irb:11:in `<main>
I want the yaml val is yes and i google find the handler will help.
But seems i do not use correct syntax.
I try to search related docs but fail.
The problems with the listed code are
that handlers isn't defined anywhere, you likely wanted :handlers
that identity isn't defined anywhere, maybe wanted :identity that
you are missing a > on your hash rocket (=>).
So to get this code to run it should (likely) look like
YAML.load("run: yes", :handlers => {'bool#yes' => :identity})
However, so far as I know the second parameter to YAML.load is a filename.
If you are able to change the input YAML, simply quoting the value "yes" will cause it come through as a string
YAML.load("a: 'yes'")
# => {"a"=>"yes"}
If you require the un-quoted string 'yes' in the YAML to be treated as 'yes', not true in ruby after parsing. I cobbled this together (with help from this question), using Psych::Handler and Pysch::Parser. Though I'm not sure if there's another easier/better way to do this without having to hack this all together like this.
require 'yaml'
class MyHandler < Psych::Handlers::DocumentStream
def scalar(value, anchor, tag, plain, quoted, style)
if value == 'yes'
super(value, anchor, tag, plain, true, style)
else
super(value, anchor, tag, plain, quoted, style)
end
end
end
def my_parse(yaml)
parser = Psych::Parser.new(MyHandler.new{|node| return node})
parser.parse yaml
false
end
my_parse("a: yes").to_ruby
# => {"a"=>"yes"}
my_parse("a: 'yes'").to_ruby
# => {"a"=>"yes"}
my_parse("a: no").to_ruby
# => {"a"=>false}
Sidenote in the console (and the source):
YAML
# => Psych
I have a ruby script that has a hash.
Example:
animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }
I want to add 'snake' => 'hiss'
Example:
myscript.rb --addsound "'snake' => 'hiss'"
Then in my script have it add it to animal_sound.
Example:
animal_sound.merge! 'snake' => 'hiss'
=> {"dog"=>"bark", "cat"=>"meow", "snake"=>"hiss"}
Is there a way to do this?
Here is the whole script:
#!/usr/bin/env ruby
require 'rubygems'
require 'micro-optparse'
options = Parser.new do |p|
p.option :addsound, "add sound"
end.process!
animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }
if options[:add_sound]
newsound = options[:add_sound]
animal_sound.merge! newsound
end
puts animal_sound
When I run my script I get:
$ bin/myscript.rb --addsound "'snake' => 'hiss'"
bin/myscript.rb:14:in `merge!': can't convert true into Hash (TypeError)
from bin/myscript.rb:14:in `<main>'
SOLVED:
Using PSkocik's solution I got the script to work using animal, sound = options[:addsound].split(' => '); animal_sound[animal] = sound
I also used Simone Carletti's idea to simplify the CLI command. FYI it also works if I want to pass in hash format, like myscript.rb --addsound "'snake' => 'hiss'". Of course the split has to be changed back to split(' => '). I like the simpler CLI using the :.
Example:
myscript.rb --addsound snake:hiss
Final Code:
#!/usr/bin/env ruby
require 'rubygems'
require 'micro-optparse'
options = Parser.new do |p|
p.option :addsound, "add sound", default: ""
end.process!
animal_sound = { 'dog' => 'bark', 'cat' => 'meow' }
if options[:addsound]
animal, sound = options[:addsound].split(':')
animal_sound[animal] = sound
end
puts animal_sound
Command line:
$ bin/myscript.rb --addsound snake:hiss
{"dog"=>"bark", "cat"=>"meow", "snake"=>"hiss"}
I never could get the merge to work.
Each post was helpful. Thanks.
It's a good idea to keep the CLI interface detached from the underlying implementation. In fact, you may decide to switch the script in the future from Ruby to another language, and you don't really want to change the way the code is invoked.
My suggestion is to pass a serialized value, for example
myscript.rb --addsound snake:hiss
In the code, simply decompose the content and merge it.
if options[:add_sound]
animal, sound = options[:add_sound].split(":")
animal_sound.merge!(animal => sound)
end
p.option :addsound, "add sound"
^ this makes it a flag (true or false)
What you want is make it into a switch whose value is the next argument:
p.option :addsound, "add sound", default: ""
^ this makes it a switch, the string value will be assigned to options[:addsound]
newsound = options[:addsound]
^ Here you need to drop the underscore and parse the string into a hash.
Eval is evil.
For example, you could split it on ' => ' and forget about quoting:
newsound = [ options[:addsound].split(' => ') ].to_h #and then merge it
(Passing the argument like so --addsound snake:hiss and then splitting on ':' instead of ' => ' is another good option.)
^splitting on ' => ' should yield a two-member array. Here I put it into another array (arrays of two-member arrays are convertible to hashes) to make it convertible into a hash.
Or you do completely without merging and constructing another hash:
animal, sound = options[:addsound].split(' => ')
animal_sound[animal] = sound
In regards to your error
Notice the line if options[:add_sound]. That basically evaluates to if true. You are getting your error because you are setting newsound to true, and trying to merge a Boolean into a hash. To my knowledge, the .merge only works like so: hash1.merge(hash2).
Passing command line argument
Rather than passing the argument "'snake' => 'hiss'", I suggest making this a comma-delineated list, like so: "snake,hiss". From there, in your if options[:add_sound] block, you can split the string into an array, using a comma as a splitter. Finally, rather than using .merge, you can add your key:value as you normally would for any hash in Ruby. animal_sound[arr[0]] = arr[1].
Mind you, this method will work best with a single key:value pair. I am sure you can submit multiple pairs, but you would need to (by this method) split into more arrays by an additional character(like / maybe).
Here is the exact problem;
$ hash
=> {:createAuthenticationTokenRequest=>{:playerSessionID=>"111"}}
$ hash[:attributes!]
=> "" (here is the crazy result)
$ hash.class
=> Hash
$ hash.keys
=> [:createAuthenticationTokenRequest]
what is going on here? Am i not supposed to get nil for non existent hash keys ?
Detailed problem:
I am using savon to send a webservice request and getting "can't convert Symbol into Integer" error all the time, debugging the error with pry showed me that this line is getting executed as empty string which it shouldn't.
attributes = hash[:attributes!] || {}
Help me out here!
thanks in advance, cheers!
Update:
Answer for how the hash is created;
class Gyoku::Hash
def self.iterate_with_xml(hash)
xml = Builder::XmlMarkup.new
attributes = hash[:attributes!] || {}
Update2:
This is the request i am sending
request(
createAuthenticationTokenRequest: {
playerSessionID: "111"
}
)
As i mentioned before this is savon gem code that gets executed. I tried to write the question as less boring as possible, and don't get why it gets downvoted :/
here is the source code that gets debugged.
https://github.com/savonrb/gyoku/blob/master/lib/gyoku/hash.rb
i guess i deserved to be downvoted......
Don't do this in your code,
def default_request_parameters
#default_request_parameters || Hash.new('')
end
You can create a hash like this
way 1:
new_hash = Hash.new{|h,k| h[k] = ""}
new_hash['unknown_key']
it returns
=> ""
new_hash.keys
=> ['unknown_key']
this adds 'unknown_key' key to the hash.
way 2:
hash2 = Hash.new("")
hash2['unknown_key']
it returns
=> ""
but no keys are added.
hash2.keys
it returns
=> []
I'm working on a URL shortener and attemtping to convert the URL ID, which is a number, into a string, using base 36.
I'm receiving the error listed below the code:
def self.create_link(original)
url = Url.create(:original => original)
if Link.first(:indentifier => url.id.to_s(36)).nil? or !DIRTY_WORDS.include? url.id.to_s(36)
link = Link.new(:identifier => url.id.to_s(36))
link.url = url
link.save
return link
else
create_link(original)
end
end
I'm receiving the following error:
wrong number of arguments(1 for 0) file: tinyclone.rb location: to_s line: 91
When I researched the error, I found someone who mentioned that this error is common when you attempt to pass in parameter values when a method doesn't accept them. The error is specifically referring the following line.
if Link.first(:indentifier => url.id.to_s(36)).nil? or !DIRTY_WORDS.include? url.id.to_s(36)
What's the type of url.id?
I think your expecting it to be a FixNum whose to_s method accepts a radix, but you're getting something else instead... maybe a string containing a number? (e.g. "1234")
Anyway, the method seems to require no arguments and you are passing 36 nevertheless
EDIT:
Can't find the reference to the class you pointed out (Serial), but this might be worth a try:
url.id.to_i.to_s(36)
One thing I see right away is:
if Link.first(:indentifier => url.id.to_s(36)).nil? or !DIRTY_WORDS.include? url.id.to_s(36)
link = Link.new(:identifier => url.id.to_s(36))
Notice that in the first line you have :indentifier and in the second it's :identifier.
Otherwise, I agree with #Pablo Fernandez's answer that it's probably tied to the type of id.
you have 2 models, but take full responsibility on the one of them only. please take a look at code separated logic:
# Link model
def self.create_link(original)
url = Url.create(:original => original)
url_id = url.encoded_id
find_or_create_by_identifier!(:identifier => url_id)
end
# Url model
def before_validate_on_create
if url.id.to_s.include? DIRTY_WORDS
self.errors.add(:base, 'the url is invalid')
end
end
def encoded_id
url.id.to_s(36)
end