scope in Ruby / Sinatra - ruby

I am trying to identify a user ID number, to grab a Student row from an ActiveRecord table, but for some reason the 'post' block won't find my :id.
For the 'get', the url is localhost:9456/update/17. It's this 17 I need to pass to the 'post' block to update the database.
I am not sure how to do this. Parse the URL? It seems like I am missing something obvious.
# update user page
get '/update/:id' do
#student = Student.find(params[:id]) #this returns ID=17
erb :update
end
#update user submit
post '/update' do
#student = Student.find(params[:id]) #this line doesn't work
#student.name = (params[:name])
#student.email = (params[:email])
#student.save
redirect '/'
end
thanks!

Are you actually passing the id back? Are you submitting a form via POST? If so, all you should need to do is add an input whose name attribute is id.
<form action='/student/update' method='post'>
<input type='hidden' name='id' value='17' />
</form>

Related

How do I configure Pony/Sinatra to send data from two different forms?

I've a web page with two forms on it.
There is a general contact form and a shopping cart like response section from customers for sale people to respond to with the customer's choices.
I know nothing about Ruby and I'm having trouble assimilating on just how this is supposed to work with the routes pointing to the Sinatra email template.
Code as follows...
**** Mailer.rb ****
require 'sinatra'
require 'pony'
Pony.options = {
via: :smtp,
via_options: {
openssl_verify_mode: OpenSSL::SSL::VERIFY_NONE,
address: 'mail.myserver.com',
port: '587',
user_name: 'test#myserver.com',
password: '********',
authentication: :plain,
domain: "mail.myserver.com" # the HELO domain provided by the client to the server
}
}
class Mailer < Sinatra::Base
post '/contact' do
options = {
from: "test#myserver.com",
to: 'client#clientaddress.com',
subject: "Contact Form",
body: "#{params['name']} <#{params['email']}> \n" + params['message']
}
Pony.mail(options)
redirect '/'
end
post '/build-tool' do
options = {
from: "test#myserver.com",
to: 'client#clientaddress.com',
subject: "Custom Build Form",
body: "#{params['name']} <#{params['email']}> \n" + params['message']
}
Pony.mail(options)
redirect '/'
end
end
***** HTML Form One *****
<form class="form-horizontal" method="POST" action="/contact">
contact information inputs
</form>
***** HTML Form Two *****
<form class="form-horizontal" method="POST" action="/build-tool">
build tool inputs
</form>
***** Config.rb *****
map '/contact' do
run Mailer
end
map '/build-tool' do
run Mailer
end
Sinatra directly figures out routes, so if you make a POST request to '/contact', it will invoke the code inside the post '/contact' definition, so you don't have to do whatever you're doing in config.rb.
When you redirect '/', it means that the server expects a root route to be defined, which is missing in your class definition.
The HTML tags go into a view which gets rendered via Sinatra.
These are the three things that require changes. First we define a / route which renders the HTML form elements we need:
# mailer.rb
require 'sinatra/base' # note the addition of /base here.
require 'pony'
# pony options code same as before
class Mailer < Sinatra::Base
get '/' do
erb :index
end
# post routes same as before
end
HTML gets rendered from a view template. By default, Sinatra will look up views inside the views/ directory.
# views/index.erb
<form class="form-horizontal" method="POST" action="/contact">
contact information inputs
submit button
</form>
<form class="form-horizontal" method="POST" action="/build-tool">
build tool inputs
submit button
</form>
ERB is a templating languages that's bundled with the Ruby language, so you don't have to install any new gem. There are more languages listed in the documentation.
And the config.ru will look like:
# config.ru
# Note the file change. This is called a Rackup file. config.rb is
# a more general purpose file used by many libraries, so it's better
# to keep this separate.
require_relative 'mailer.rb'
run Mailer
The problem here is that each form corresponds to a specific route, sending the form data to that action. In other words, "form one" sends all input fields it contains in a POST request to /contact and "form two" sends all its input fields in a POSTto /build-tool.
My suggestion would be to incorporate both forms together into one form and just play with the styling of the page to make it look like two. That way you will get all input fields submitted to your Sinatra app together and you can send whatever mail best applies to what was input.

Sinatra controller delete method failing

I have a vendor model and controller where I've implemented the below delete method. Whenever I click the delete button, I get the "doesn't know this ditty" error.
delete '/vendors/:id/delete' do
#vendor = Vendor.find(params[:id])
if logged_in? && #vendor.wedding.user == current_user
#vendor.destroy
redirect '/vendors'
else
redirect "/login", locals: {message: "Please log in to see that."}
end
end
My delete button:
<form action="/vendors/<%=#vendor.id%>/delete" method="post">
<input id="hidden" type="hidden" name="_method" value="delete">
<input type="submit" value="Delete Vendor">
</form>
My config.ru file already has 'use Rack::MethodOverride' and my edit/put forms are working fine so MethodOverride seems to be working.
Any idea why Sinatra is giving me the "Sinatra doesn't know this ditty" message just for deleting?
As suggested by Matt in the comments, you might want to try enabling the method override in your app via set. For example, using the modular Sinatra setup:
class Application < Base
set :method_override, true
# routes here
end
There's a nice example in this writeup as well

How to use ajax in Brython

I am writing a web application using Flask and would like to use browser.ajax functionality in Brython but couldn't find a workable example. It would be very nice if someone demonstrates a short example how to use ajax in Brython. More specifically, how to pass data entered by a user into a textfield to a textarea by clicking submit button. Any help is highly appreciated!
(I am writing this several weeks after I posted the question above). I followed this tutorial on how to implement ajax in Flask (http://runnable.com/UiPhLHanceFYAAAP/how-to-perform-ajax-in-flask-for-python) and tried to replace jquery.ajax by Brython. Unfortunately, I still cannot get it work. Here is my code:
Flask's portion:
#app.route('/')
def index():
return render_template('index.html')
#app.route('/_add_numbers')
def add_numbers():
a = request.args.get('a', 0, type=int)
b = request.args.get('b', 0, type=int)
return jsonify(result=a + b)
Brython/HTML:
<body onload="brython()">
<script type="text/python">
from browser import document as doc
from browser import ajax
def on_complete(req):
if req.status==200 or req.status==0:
doc["txt_area"].html = req.text
else:
doc["txt_area"].html = "error "+req.text
def get(url):
req = ajax.ajax()
a = doc['A'].value
b = doc['B'].value
req.bind('complete',on_complete)
req.open('GET',url,True)
req.set_header('content-type','application/x-www-form-urlencoded')
req.send({"a": a, "b":b})
doc['calculate'].bind('click',lambda ev:get('/_add_numbers'))
</script>
<div class="container">
<div class="header">
<h3 class="text-muted">How To Manage JSON Requests</h3>
</div>
<hr/>
<div>
<p>
<input type="text" id="A" size="5" name="a"> +
<input type="text" id ="B" size="5" name="b"> =
<textarea type="number" class="form-control" id="txt_area" cols="10" rows = '10'></textarea>
<p>calculate server side
</div>
</div>
</body>
</html>
What I get is "result":0. It looks like brython does not send data to the Flask's view function but I don't know how to fix that. So, it would be great if some one could point out what exactly I am doing wrong.
In your example, the Ajax request is sent with the method GET. In this case, the argument of send() is ignored : the data must be sent in the query string appended to the url
The Brython code should be :
def get(url):
req = ajax.ajax()
a = doc['A'].value
b = doc['B'].value
req.bind('complete',on_complete)
# pass the arguments in the query string
req.open('GET',url+"?a=%s&b=%s" %(a, b),True)
req.set_header('content-type','application/x-www-form-urlencoded')
req.send()
If you want to use the POST method, then you can keep the Brython code as is, but the Flask code should be modified : you must specify that the function handles a POST request, and you get the arguments with the attribute "form" instead of "args" :
#app.route('/_add_numbers_post', methods=['POST'])
def add_numbers_post():
a = request.form.get('a', 0, type=int)
b = request.form.get('b', 0, type=int)
return jsonify(result = a+b)
I am working on that - there is nothing ready made, but writing Python code makes it really painless.
I can't post the code I am working on, (and it is far from minimal) - but basically, you write a (Br)Python function to iterate on the HTML, or form DOM, and collect everything that has a "value" in a json-nish structure (a dictionary with nested dicionaries and lists at will) - Them you simply use the browser.ajax object as documented in http://brython.info/doc/en/index.html#, and pass the object with your data as a parameter to the "send" method.
The object data will be URLencoded in the request body. You just have to decode it from there to JSON on the client side.
As an extra hint:
I did not go deep on the question, but I feel like the URLencoding used by default may fail to express everything that is possible in JSON. So imported brython's json module, and do the send like this:
ajax_object.send({"data": json.dumps(json_data)})
This allows me to do this on the client-side:
json_data = json.loads(urllib.unquote(request.body).split(":",1)[-1] )
(the "request.body" is from Pyramid - for flask it is "request.data", but only if the conte-type is not understood by the flask - check How to get data received in Flask request )

No data received Sinatra when editing a blog post

I am following this Sinatra blog post to build my own blog in Ruby Sinatra, the only difference being my templates are in slim and not ERB.
The problem I'm having is in saving edited posts. The posts actually save but it's not redirecting me to the recently edited page and Chrome is giving me a "No data received error", Error code: ERR_EMPTY_RESPONSE.
So my question is how to deal with the No Data Received?
Sinatra Routes
get '/posts/:id/edit' do
#post = Post.find(params[:id])
#title = 'Edit Post'
slim :'posts/edit'
end
put '/posts/:id' do
#post = Post.find(params[:id])
if #post.update_attributes(params[:post])
redirect '/posts/#{#post.id}'
else
slim :'posts/edit'
end
end
Slim Template
h1 Edit Post
form action="/posts/#{#post.id}" method="post"
input type="hidden" name="_method" value="put"
label for="post_title" Title:
input id="post_title" name="post[title]" type="text" value="#{#post.title}"
label for="post_body" Body:
textarea id="post_body" name="post[body]" rows="5" #{#post.body}
input type="submit" value="Save"
I'm using sqlite3 for the blog database [as said in the blog].
Oh, here's your problem: you have #{...} in the redirect, but it's wrapped by single-quote marks: '. Ruby doesn't interpret interpolations within single-quotes, only within " double-quotes. So if you change that line to redirect "/posts/#{#post.id}" it should work.

Ruby - Sinatra: Asynchronously calling method with form input as parameter

I am currently trying to build a little widget that will retrieve a list of artists based on a username.
The Ruby method requires a username parameter after which an API call is made that retrieves the actual array of strings.
The web page has an input field where the user can fill out his/her username. My goal is to immediately call the ruby method and display the list of artists. My problem is being able to use the actual form input as the parameter. I figured this would be relatively easy with params[:user], in the same way it's done in a Sinatra post method. Alas, turns out it isn't.
I tried both a JS approach and directly calling the method after :onkeyup.
Javascript:
userChanged = function() {
var user = document.getElementById("username");
if (user.value.length != 0){
artists = #{RFCore::get_artists(:name => params[:user]).to_json};
art_list.innerHTML = artists
};
};
:onkeyup
:onkeyup => "art_list.innerHTML = #{RFCore::get_artists(:name => params[:user])[0]}"
I have substituted params[:user] with all variations I could think of such as "#{user}" and user.
The errors returned are undefined method []' for params[:user] and undefined local variable or methoduser' for "#{user}" and user.
Perhaps there is an easy solution to this; but the feeling is starting to creep up on me my approach is wrong to begin with. I am open to any other way of achieving this.
As far as I understood, you are generating that JavaScript dynamically. So when your Ruby code produces it, it evaluates that RFCore::get_artists expression when you are generating the JavaScript code, not when the user interacts with the web page.
If that's the case, I recommend:
Use jQuery. It makes your life much easier.
When there's some user interaction (e.g., a key press), use Ajax to communicate with your server to get back a list of artists.
Here is a small Sinatra application that demonstrates this approach:
require 'sinatra'
get '/' do
<<html
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
function userChanged()
{
$.get('/get-artists',
{username: $('#username').val()},
function(data){
$('#artists').html(data);
});
}
</script>
User: <input id="username" type="text">
<button onclick="userChanged();">Look up</button>
<div id="artists"/>
html
end
get '/get-artists' do
"Generate here list for user #{params[:username]}"
end
Please notice that the above code is just an example. The HTML generated is all wrong, no template language is being used, etc.

Resources