How can I pass function parameters to an erb view with sinatra? - ruby

I currently have this:
get '/myapp/get/:func' do
erb :server, :locals => {:func => params[:func]}
end
And then in my server.erb file I have this:
if (func == "myFunc1")
myFunc1
elsif (func == "myFunc2")
myFunc2
etc...
The ruby functions called in server.erb are defined.
Now I want to define a new function, and I want to pass a variable to it. So what I want in my server.erb is this:
def myNewFunc(param1)
# do stuff with param1
end
How do I pass param1 to sinatra?
Note: The parameter I want to pass in is just an integer between 0 and 6.

You don't have to pass params as locals, you can se them anywhere in your code – if that is what you mean.

Related

ruby not understanding arguments provided to a method

I come across this piece of ruby code :
def link_to(link_text, url, mode=:path_only)
# You should add "!!" at the beginning if you're directing at the Sinatra url
if(url_for(url,mode)[0,2] == "!!")
trimmed_url = url_for(url,mode)[2..-1]
"<a href=#{trimmed_url}> #{link_text}</a>"
else
"<a href=#{url_for(url,mode)}> #{link_text}</a>"
end
end
def url_for url_fragment, mode=:full_url
case mode
when :path_only
#cut for brievity. The rest of the function gets rack params and renders full url (or not)
I have no clue what this line of code does : (url_for(url,mode)[0,2] == "!!")
This code:
url_for(url,mode)[0,2] == "!!"
Checks that the first (offset 0) two characters (,2) are equivalent to "!!". This is now something you can express as:
url_for(url,mode).start_with?("!!")
Which might make it easier to understand.
The String#[] method has two forms relevant to understanding this:
"hello"[0] # Character index
# => "h"
"hello"[0,1] # Equivalent to above
# => "h"
"hello"[0,2] # Su
# => "he"

Sinatra Route returns nothing

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

Sinatra: params hash cannot be merged

I want to merge a hash with default parameters and the actual parameters given in a request. When I call this seemingly innocent script:
#!/usr/bin/env ruby
require 'sinatra'
get '/' do
defaults = { 'p1' => 'default1', 'p2' => 'default2' }
# params = request.params
params = defaults.merge(params)
params
end
with curl http://localhost:4567?p0=request then it crashes with
Listening on localhost:4567, CTRL+C to stop
2016-06-17 11:10:34 - TypeError - no implicit conversion of nil into Hash:
sinatrabug:8:in `merge'
sinatrabug:8:in `block in <main>'
When I access the Rack request.params directly it works. I looked into the Sinatra sources but I couldn't figure it out.
So I have a solution for my actual problem. But I don't know why it works.
My question is: Why can I assign param to a parameter, why is the class Hash but in defaults.merge params it throws an exception?
Any idea?
This is caused by the way Ruby handles local variables and setter methods (i.e. methods that end in =) with the same name. When Ruby reaches the line
params = defaults.merge(params)
it assumes you want to create a new local variable named params, rather than use the method. The initial value of this variable will be nil, and this is the value that the merge method sees.
If you want to refer to the method, you need to refer to it as self.params=. This is for any object that has such a method, not just Sinatra.
A better solution, to avoid this confusion altogether, might be to use a different name. Something like:
get '/' do
defaults = { 'p1' => 'default1', 'p2' => 'default2' }
normalized_params = defaults.merge(params)
normalized_params.inspect
end
Your code is throwing an error because params is nil when you make this call defaults.merge(params). I assume you are trying to merge defaults with request.params, which should contain the parameters from your GET.
Change this line
params = defaults.merge(params)
to this
params = defaults.merge(request.params)
I found this in rack gem
http://www.rubydoc.info/gems/rack/Rack/Request#params-instance_method
It seems you can retrieve GET and POST data by params method but you can't write in it. You have to use update_param and delete_param instead.

Multiple Parameters

I have an object that allows users to connect to HDFS. One method allows them to download data, and the other upload data. The initialize method looks something like this:
def initialize(file=nil,set=nil,action)
end
How can I change the way arguments are passed to be more efficient? The action param is required every time, but file and file_set are only required depending on the action. I.e., if they want to upload data they need to pass set, if they want to download data, they just pass file.
Since Ruby 2.0 you can use keyword parameters:
def initialize(action, file: nil,set: nil)
unless file.nil?
# do stuff with file
end
end
So you can call it:
MyClass.new(some_action)
MyClass.new(some_action, set: some_set)
MyClass.new(some_action, file: some_file)
MyClass.new(some_action, set: some_set, file: some_file)
...
First of all you should pass requred parameter first e.g.:
def initialize(action, file = nil, set = nil)
end
Then you may want to use hash to pass optional params:
def initialize(action, options = {})
end
The passing hash is a common way when you need to pass more than one optional parameter.
When you need to pass file, set or both you may call initialize method as follow (assume that this method is defened in the class MyModel):
MyModel.new(action, {file: ''})
MyModel.new(action, {set: ''})
MyModel.new(action, {file: '', set: ''})
Or when you don't want to pass any optional params, simply call:
MyModel.new(action)
In this case you will have empty options hash passed in you initialize method.
Quite common is to use positional parameters when they are mandatory, and an options hash
for the rest. You just need to check action to verify given params are then present:
def initialize(action, opts={})
if action == 'foo'
raise ArgumentError, "requires either :file or :set" unless
([:file, :set] & opts.keys).size == 1
end
...
end

Turn this haml snippet into a helper to DRY up Sinatra

This bit of haml works:
%select{ :name => 'color', :value => #p.color}
- Person.color.options[:flags].each do |colors|
- if #p.color == colors
%option{:value => colors, :selected => "selected"}= colors
- else
%option{:value => colors}= colors
I'm trying to create a helper out of it so I can reuse it:
helpers do
def options(prop, p)
Person.prop.options[:flags].each do |x|
if p.prop == x
"%option{:value => #{x}, :selected => 'selected'}= #{x}"
else
"%option{:value => #{x}}= #{x}"
end
end
end
end
And then calling it with:
%select{ :name => 'color', :value => #p.color}
- options(color, #p)
But I'm getting this error: undefined local variable or method 'color'
Am I far off?
EDIT 2:
Something funky is going on with the loop.
Even a simple example such as this:
helpers do
def options(prop, p)
Person.send(prop).options[:flags].each do |x|
"<p>test</p>"
end
end
end
and = options(:color, #p)
prints an array of the options ( in my case [:red, :blue, :yellow]) and does not insert any html. However, if I do puts <p>test</p> it does go through the loop three times and prints them correctly—they just don't show up in the html.
Use a symbol instead of undefined method:
options(:color, #p)
instead of:
options(color, #p)
and on helper method use send:
if p.send(prop) == x
instead of
if p.prop == x
as well as:
Person.send(prop)
instead of:
Person.prop
Also, i'm in doubt HAML will accept a string like "%option{:value => #{x}" as a tag.
You can use HTML instead or just find another way to DRY, like render a partial or use haml_tag:
def options(prop, p)
Person.send(prop).options[:flags].each do |x|
haml_tag :option, "#{x}", :value=>x
end
end
If you use haml_tag call it with - options(:color, #p)
There are a couple of things going on here. First, the message undefined local variable or method 'color' is caused by the line
- options(color, #p)
in your Haml. Here color is the undefined local variable. If I understand correctly you have a Person class that has various properties, each of which has several possible options and you want to be able to choose which one to use without hardcoding it and needing several helpers method. One way to do that would be to change the line
Person.prop.options[:flags].each do |x|
in your helper to
Person.send(prop).options[:flags].each do |x|
and then pass a symbol specifying the property to use when you call the helper:
- options(:color, #p)
The next problem is writing the generated code to the output. Your helper can either return a string which you can include with =, or it can write directly to the output using helpers like haml_tag and haml_concat. Note that you shouldn’t use the return value of haml_tag or haml_concat, it will generate an error.
So here you can either create the HTML in the helper:
if p.prop == x
"<option value='#{x}' selected='selected'>#{x}</option>"
else
"<option value='#{x}' />#{x}</option>"
end
and then use it with = (if you use - the output will get ignored):
= options(:color, #p)
If you do this you need to make sure the helper returns the string you want to embed in your Haml. In this case the helper returns the value the call to each, which is the array itself. You need to use something like map and join to create the desired string:
def options(prop, p)
Person.send(prop).options[:flags].map do |x|
if p.prop == x
"<option value='#{x}' selected='selected'>#{x}</option>"
else
"<option value='#{x}' />#{x}</option>"
end
end.join("\n")
end
The alternative is to use haml_tag to write the output directly.
if p.prop == x
haml_tag :option, x, :value=>x, :selected => true
else
haml_tag :option, x, :value =>x
end
Note that in Haml, any entry in an attribute hash that has a boolean value will only be output if the value is true, and it will be formatted correctly depending on the output, e.g. selected='selected' got XHTML and just selected for HTML. So this last example can be simplified to
haml_tag :option, x, :value=>x, :selected => (p.prop == x)
(You could always simplify the other example (returning a string) in a similar manner, with something like #{"selected='selected'" if p.prop == x}, but Haml has it built in.)

Resources