Array for errors in sinatra - ruby

To manage errors in my address book app i initialize an array like this
err = Array.new
and then when i post something it checks if there are empty fields. If yes, for each empty field it adds a record in the array, and then redirect to /add page, like this
post '/' do
if params[:fname] == ""
err.push "Insert a valid first name"
end
if params[:lname] == ""
err.push "insert a valid last name"
end
if params[:phone] == ""
err.push "insert a valid phone number"
end
if params[:mail] == ""
err.push "insert a valid e-mail address"
end
if err.empty?
c = Contatto.new
c.fname = params[:fname]
c.lname = params[:lname]
c.phone = params[:phone]
c.mail = params[:mail]
c.save
redirect '/'
else
redirect '/add'
end
end
then the add page reads if the array has any record and if yes, cycles it to print each message
get '/add' do
#err = err
#title = 'Aggiungi'
erb :aggiungi
end
<% if #err.any? %>
<div class="error">
<% #err.each do |err| %>
<%= err %><br>
<% end %>
</div>
<% end %>
i think the error is that it re-initialize the array every time it changes from post '/' to get '/add' and so the result is an empty array...
How can i solve? thank you everyone!

If you want data for a specific visitor to persist between requests you need to be storing the error array in either a session or a cookie (session probably makes the most sense).
Luckily sessions in Sinatra are pretty easy: http://www.sinatrarb.com/intro#Using%20Sessions . Once enabled you can put pretty much anything you want into the session hash, so initializing with session[:errors] = [] and pushing with session[:errors] << "An error" should give you the persistence you are looking for.

You're creating a local variable and expecting it to persist between requests. How is this supposed to happen? Sinatra is not psychic, it will only remember what you tell it to remember, and that's usually done through some kind of database or a client-side cookie.
Generally you should render a response page on failure, making use of the errors you've collected, or redirect on success, where the empty errors array is not relevant.
As a matter of style, the more Ruby way to do things like this is:
err = [ ] # Equivalent to Array.new
err << "Example error" # Equivalent to err.push

Related

Am I logically reiterating the same code block in Ruby?

What is the difference between these two lines of Ruby code?
if params.values.any? { |value| value == "" }
and
#post = current_user.posts.build(title: params[:post][:title], content: params[:post][:content])
The contexts in which they are used are as follows, respectively:
post '/builds' do
redirect_if_not_logged_in
if params.values.any? {|value| value == ""}
erb :'builds/new', #locals: {message: "Unable to Continue!"}
else
user = User.find(session[:user_id])
#build = Build.create(title: params[:title], budget: params[:budget], user_id: params[:user.id])
redirect to "/builds/#{#build.id}"
end
end
and
post "/builds" do
redirect_if_not_logged_in
#build = current_user.builds.build(title: params[:post][:title], content: params[:build][:content])
if #build.save
redirect "/builds"
else
erb :"/builds/new.html"
end
end
if params.values.any? {|value| value == ""}
erb :'builds/new', #locals: {message: "Unable to Continue!"}
What you're doing here is returning an error message if any of the parameter values are empty. This can happen if the user didn't fill out one of the form fields on the page.
#post = current_user.posts.build(title: params[:post][:title], content: params[:post][:content])
This creates a new post object using the given parameters. If you didn't have that first code block, this might possibly set one of the values to an empty string ("").
There are other ways to do this (specifically, model-level validations), but hopefully that helps you figure out what's going on here.

Ruby If statement

I am trying to do a post and run some if statement. What I want to do is:
check all fields are filled
if all fields are filled move on to next step, or else reload page
check if already in data base
add if not already in data base
post "/movies/new" do
title = params[:title]
year = params[:year]
gross = params[:gross]
poster = params[:poster]
trailer = params[:trailer]
if title && year && gross && poster && trailer
movie = Movie.find_by(title: title, year: year, gross: gross)
if movie
redirect "/movies/#{movie.id}"
else
movie = Movie.new(title: title, year: year, gross: gross, poster: poster, trailer: trailer)
if movie.save
redirect "/movies/#{movie.id}"
else
erb :'movies/new'
end
end
else
erb :'movies/new'
end
end
I don't think my if statement is correct. It works even if all my fields are not filled
Your code is doing a lot of work in one single method. I would suggest to restructure it into smaller chunks to make it easier to manage. I mostly code for Rails, so apologies if parts of these do not apply to your framework.
post "/movies/new" do
movie = find_movie || create_movie
if movie
redirect "/movies/#{movie.id}"
else
erb :'movies/new'
end
end
def find_movie
# guard condition to ensure that the required parameters are there
required_params = [:title, :year, :gross]
return nil unless params_present?(required_params)
Movie.find_by(params_from_keys(required_params))
end
def create_movie
required_params = [:title, :year, :gross, :poster, :trailer]
return nil unless params_present?(required_params)
movie = Movie.new(params_from_keys(required_params))
movie.save ? movie : nil # only return the movie if it is successfully saved
end
# utility method to check whether all provided params are present
def params_present?(keys)
keys.each {|key| return false if params[key].blank? }
true
end
# utility method to convert params into the hash format required to create / find a record
def params_from_keys(keys)
paras = {}
keys.each { |key| paras.merge!(key: params[key]) }
paras
end
Even if you type nothing in the HTML fields, they will still be submitted as empty strings.
You can avoid having empty parameters by, for example, filtering them:
post '/movies/new' do
params.reject! { |key, value| value.empty? }
# rest of your code
end
Also I would rather post to /movies rather than to /movies/new, that's more REST-wise.
Try if condition to check fields are blank like below -
unless [title, year, gross, poster, trailer].any?(&:blank?)
This will check any of the field should not be nil or blank("").

If Statement inside Sinatra template

I'd like to to show a message only if on a specific route/page. Essentially, if on /route display a message.
I tried going through the Sinatra Docs, but I can't find a specific way to do it. Is there a Ruby method that will make this work?
EDIT: Here's an example of what I'd like to do.
get '/' do
erb :index
end
get '/page1' do
erb :page1
end
get '/page2' do
erb :page2
end
*******************
<!-- Layout File -->
<html>
<head>
<title></title>
</head>
<body>
<% if this page is 'page1' do something %>
<% else do something else %>
<% end %>
<%= yield %>
</body>
</html>
No idea what how to target the current page using Ruby/Sinatra and structure it into an if statement.
There are several ways to approach this (and BTW, I'm going to use Haml even though you've used ERB because it's less typing for me and plainly an improvement). Most of them rely on the request helper, most often it will be request.path_info.
Conditional within a view.
Within any view, not just a layout:
%p
- if request.path_info == "/page1"
= "You are on page1"
- else
= "You are not on page1, but on #{request.path_info[1..]}"
%p= request.path_info == "/page1" ? "PAGE1!!!" : "NOT PAGE1!!!"
A conditional with a route.
get "/page1" do
# you are on page1
message = "This is page 1"
# you can use an instance variable if you want,
# but reducing scope is a best practice and very easy.
erb :page1, :locals => { message: message }
end
get "/page2" do
message = nil # not needed, but this is a silly example
erb :page2, :locals => { message: message }
end
get %r{/page(\d+)} do |digits|
# you'd never reach this with a 1 as the digit, but again, this is an example
message = "Page 1" if digits == "1"
erb :page_any, :locals => { message: message }
end
# page1.erb
%p= message unless message.nil?
A before block.
before do
#message = "Page1" if request.path_info == "/page1"
end
# page1.erb
%p= #message unless #message.nil?
or even better
before "/page1" do
#message = "Hello, this is page 1"
end
or better again
before do
#message = request.path_info == "/page1" ? "PAGE 1!" : "NOT PAGE 1!!"
end
# page1.erb
%p= #message
I would also suggest you take a look at Sinatra Partial if you're looking to do this, as it's a lot easier to handle splitting up views when you have a helper ready made for the job.
Sinatra has no "controller#action" Rail's like concept, so you wont find a way to instantiate the current route. In any case, you can check request.path.split('/').last to get a relative idea of what is the current route.
However, if you want something so be shown only if request.path == "x", a much better way is to put that content on the template, unless that content has to be rendered in a different place within your layout. In that case you can use something like Rail's content_for. Check sinatra-content-for.

Array of Ruby objects returning strings on each method. Why?

Useful additional info: I am using the decent_exposure gem so this might be the issue - correcting the code below:
expose(:get_filter_tags) do
if params[:filter_tag_names]
filter_tag_names = Array(params[:filter_tag_names].split(" "))
filter_tags = Array.new
filter_tag_names.each do |f|
t = Tag.find_by_name(f)
filter_tags << t
end
end
end
So, something funny happens when I call this in the view:
query string ?utf8=✓&filter_tag_names=test
<% get_filter_tags.each do |ft| %>
<%= ft.name %>
<% end %>
Error message: undefined method `name' for "test":String
Why is this trying to call name on a string not a Tag object? If I put the following in the view, and have jut one filter_tag_names item
def getfiltertag
Tag.find_by_name(params[:filter_tag_names])
end
#view
<%= getfiltertag.name %>
query string: ?utf8=✓&filter=test
like above then I can call name just fine, so obviously I am doing something wrong to get an array of strings instead of objects. I just don't know what. Any suggestions?
Your problem is that each returns self — so if you write filter_tag_names.each, it returns filter_tag_names. You could fix this by explicitly returning filter_tags, but more idiomatically, you could just rewrite it as:
expose(:get_filter_tags) do
if params[:filter_tag_names]
filter_tag_names = Array(params[:filter_tag_names].split(" "))
filter_tag_names.map {|f| Tag.find_by_name(f) }
end
end
Just as an aside, this method will return nil if there aren't any filter tag names. You may want to do that, or you might want to return an empty collection to avoid exceptions in the calling code.

Workling return store not retrieving value from set

In my apps/controllers/model_controller.rb I have (names of models/methods changed to protect the innocent):
def background_sync
#background_task_uid = Model.async_process_model_cache({:name => 'name'})
#model_sync = ModelSync.new # Adds a new record in the queue of pending jobs
#model_sync.process_id = #background_task_uid # Puts the background process id into the new ModelSync record
#model_sync.save
end
In app/workers/model_worker.rb:
def process_model_cache(options={})
[long background task]
result = Workling::Return::Store.set(options[:uid], 'done')
result = Workling::Return::Store.get(options[:uid]) #=> 'done'
end
Notice that the set and get are functioning properly here within this worker. The problem is later on...
Back in app/views/model/index.html.rb, I have a prototype helper polling a request to the same controller to determine whether the background job is complete:
<%= periodically_call_remote( :url => { :action => :background_complete }, :frequency => 5, :update => 'status_div') %>
And in apps/controllers/model_controller.rb, the function for checking the status of the background job:
def background_complete
#background_task_uid = ModelSync.find(:last)
if #background_task_uid
#background_task_uid.each do |task|
unless task.process_id == "" || task.process_id.nil?
#result = Workling::Return::Store.get(task.process_id) #=> nil
if #result.nil?
task.destroy
end
else
task.destroy
end
unless #result.nil?
render :text => "<span style='font-size:12px;margin-left:20px;'>"+#result+"</span>"
else
#result = "none" if #result.nil?
render :text => "<span style='font-size:12px;margin-left:20px;'>"+#result+"</span>"
end
end
end
end
And finally, in config/environments/development.rb:
Workling::Return::Store.instance = Workling::Return::Store::MemoryReturnStore.new
Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new
(Note that I've tried running this with and without the last line commented. If commented out, Workling reverts to Spawn rather than Starling.)
So the problem is that I get nil from this line in background_complete:
#result = Workling::Return::Store.get(task.process_id) #=> nil
I know it is a year since you asked this question, but just getting into Starling now, myself, so didn't see this till now.
But it looks like your problem was (from development.rb):
Workling::Return::Store.instance = Workling::Return::Store::MemoryReturnStore.new
Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new
It needed to be:
Workling::Return::Store.instance = Workling::Return::Store::StarlingReturnStore.new
Workling::Remote.dispatcher = Workling::Remote::Runners::StarlingRunner.new
At least for the benefit of those google searchers out there... :)
Found the answer to this question. The fix is to remove the Workling::Return::Store.instance line from config/environments/development.rb
Then replace the get AND set calls as follows:
In app/workers/model_worker.rb:
store = Workling::Return::Store::StarlingReturnStore.new
key, value = #uid, #progress
store.set(key, value)
In app/controllers/models_controller.rb:
store = Workling::Return::Store::StarlingReturnStore.new
#result = store.get(task.process_id)
Obviously, there is a way to declare a shortcut in environments.rb to avoid calling a new StarlingReturnStore each time, but I'm out of my depth because I can't make that work.
Anyway, this fix works for me. I'm getting the output from each background job to report via set to the get in the controller, which then gets captured by the AJAX call and reported to the page via RJS.
Nice!

Resources