This question has been rewritten because the way I originally wrote it was unclear.
How do I take the values within #form_params and pass them to get '/show_results'?
class MyApp < Sinatra::Base
configure :development do
register Sinatra::Reloader
end
get '/' do
erb :index
end
post '/form_handler' do
#form_params = params
redirect to("/show_results/?#{#form_params}")
end
get '/show_results' do
erb :display_result
end
end
index.erb
<form method="POST" action="/form_handler">
...
<input type="hidden" name="first" value="John">
<input type="hidden" name="middle" value="Q">
<input type="hidden" name="last" value="Public">
...
</form>
params (result)
{first => "John", middle => "Q", last => "Public"}
the #form_params is a hash in your code. sth like that:
{"first" => "John", "middle" => "Q", "last" => "Public"}.
actually it's a string but can be converted into hash.
when you apend it to the redirect url, it becomes,
/show_results?{"first" => "John", "middle" => "Q", "last" => "Public"}
and it is different from your expectation. as i guess you want something like that 0
/show_results?first=john&middle=Q ...
what you should do is,
take params and parse it. maybe sinatra has a built in method for this (better look documentation) but this can aslo easily be done with plain ruby.
post '/form_handler' do
redirect_url = '/show_results'
params.each { |k,v| redirect_url += "?#{k}=#{v}" }
#don't worry about above line, the rest of ?-s will be converted into &
redirect to(redirect_url)
end
apart from all these, why are you sending post request then redirect it to a get block?
if you submit your form with get, it will automatically will be parsed to
url?k1=v1&k2=v2 ..
i mean this.
index.erb
<form method="get" action="/form_handler">
<input type="text" name="first" value="John">
<input type="text" name="middle" value="Q">
<input type="text" name="last" value="Public">
<input type="submit" name="ok">
</form>
main file.
get '/' do
erb :index
end
get '/form_handler' do
erb :display_result
end
then in your display_result file, or inside get block, you can easily access the params[:first], params[:middle] and use them.
The params object is per-request. When you redirect, unless you explicitly pass them with the url they will be lost. By the way, you can use sessions, which passes data as a cookie but can be configured to use a database instead.
Here's an option if you require 'active_support/all'
redirect to("/show_name?#{my_hash.to_param}"):
In ActiveSupport (bundled with Rails, not Sinatra) the Hash#to_param method works like this:
hash = { a: 1, b: 2 }
hash.to_param
# => "a=1&b=2"
If you have learned about the components of a URL, you will know that query params can be passed after the ? in a url, which is how "/show_name?#{my_hash.to_param}" works.
It's nice that this method also works with hash and array params, which use a special syntax in the url, i.e. /path?my_list[]=1&my_list[]=2 makes param[:my_list] == ['1','2'].
Except that in some circumstances the & [ ] characters need to be escaped (replaced with special character sequences).
puts ({a: 1, b: [1,2]}.to_param)
# => a=1B%5D=1&b%5B%5D=2
URI.unescape CGI.unescape({a: 1, b: [1,2,3]}.to_param)
# => "a=1&b[]=1&b[]=2&b[]=3"
Related
I'm writing Shop using Sinatra. I implemented adding to Basket, but I can't make deleting from Basket work.
My class App:
get "/basket" do #working
products_in_basket = FetchBasket.new.call
erb :"basket/show", locals: { basket: products_in_basket }
end
post "/basket" do #working
AddToBasket.new(params).call
redirect "/"
end
delete "basket/:id" do # doesn't work
DeleteBasket.new(params).call
redirect "/"
end
My DeleteBasket:
module Shop
class DeleteBasket
attr_reader :product_id, :id
def initialize(params)
#id = params.fetch("id").to_i
#product_id = params.fetch("product_id").to_i
end
def call
basket = FetchBaskets(id) # finds Basket instance with given id
return unless basket
reduce_basket_quantity(basket)
def reduce_basket_quantity(basket)
if basket.quantity >= 1
basket.quantity -= 1
#warehouse = FetchWarehouseProduct.new.call(product_id)
#warehouse.quantity += quantity
else
BASKET.delete(basket)
end
end
end
end
end
Delete in views:
<td> <form action="/basket/<%=b.id%>" method="post">
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="product_id" value=<%= b.product_id %>>
<input type="hidden" name="id" value=<%= b.id %>>
<button type="submit">Delete</button>
</form>
It doesn't redirect to home page as it should, and it doesn't change basket quantity by 1. It simply does nothing.
I think to most obvious reason is you are not calling the delete http method, but post instead:
<form action="/basket/<%=b.id%>" method="post">
Normally you would fix this by using
<form action="/basket/<%=b.id%>" method="delete">
but this is not yet supported according to this answer.
I think your best bet is to define your delete route as a post
post "delete-basket/:id" do
DeleteBasket.new(params).call
redirect "/"
end
and then write
<form action="/delete-basket/<%=b.id%>" method="post">
Remember how we said that in an HTML form we can specify the HTTP verb that is supposed to be used for making the request like so:
<form action="/monstas" method="post">
...
</form>
This makes the form POST to /monstas, instead of the default GET.
Now, it’s probably fair to say that every sane person in the world would expect that it is also possible to make that a PUT, or DELETE request. Like so:
<form action="/monstas" method="delete">
...
</form>
Except that … it’s not. Today’s browsers still do not allow sending HTTP requests using any other verb than GET and POST.
The reasons for why that still is the case in 2015 are either fascinating or sad, depending how you look at it [1] But for now we’ll just need to accept that, and work around it.
Sinatra (as well as Rails, and other frameworks) therefore support “faking” requests to look as if they were PUT or DELETE requests on the application side, even though in reality they’re all POST requests.
This works by adding a hidden form input tag to the form, like so:
<input name="_method" type="hidden" value="delete" />
Source: https://webapps-for-beginners.rubymonstas.org/resources/fake_methods.html
I have a form that contains 3 div for addresses (Main Address, Secondary Address, Add Address).
Each div contains the following elements (text_field): Name, Address, Phone.
Depending on the scenario, I want to edit a text field for a specific div.
So I have this scenario:
Given I am at the form for editing addresses
When the div "main address" is visible
Then fill the "name" element for the "main address" div
And erase the "address" for the "main address" div
And the following steps definitions:
Then(/^fill the "([^"]+)" element for the "([^"]+)" div$/) do |element, div|
on_page(AddressPage).fill_element element div
end
And now the part I'm not sure about - the Address Page
class AddressPage
include PageObject
include DataMagic
div(:main_address, :id => 'main_address')
div(:secondary_address, :id => 'secondary_address')
div(:add_address, :id => 'add_address')
def fill_element(element, div)
current_div = self.send("#{div}_element")
# now the part I don't know how to do:
current_element = div.search_within(current_div, element)
fill_with_correct_data current_element
end
def search_within(div, element)
# How to do this?
end
end
How can I do this so that I don't have to define all the elements for all the divs (the number of div is dynamic)?
What I need to know is if there's a way to search an element to be inside another one.
Thanks :)
Edit The html will look something like this:
<div id='main_address'>
<input id='name'>
</input>
<input id='address'>
</input>
<input id='phone'>
</input>
</div>
<div id='secondary_address'>
<input id='name'>
</input>
<input id='address'>
</input>
<input id='phone'>
</input>
</div>
<div id='add_address'>
<input id='name'>
</input>
<input id='address'>
</input>
<input id='phone'>
</input>
</div>
Second Edit The point was to also declare this:
select_list(:name, :id => 'name')
select_list(:address, :id => 'address')
select_list(:phone, :id => 'phone')
#Is this possible?
current_name_element = main_address.name
#?
#Also, at the end the point is to call the Datamagic populate_page_with method
populate_page_with data_for 'new_user'
Where the 'default.yml" looks like this:
new_user:
name: ~first_name
address: ~address
phone: ~phone
Where I can choose which div this will be filled. Is this possible?
You can find an element within an element by using the element locators. These methods use the same type of locator you would use in the accessor methods.
For example, for a given current_div, you could get its related text fields:
current_element = current_div.text_field_element(:id => 'name')
current_element = current_div.text_field_element(:id => 'address')
current_element = current_div.text_field_element(:id => 'phone')
Note: You might need to change these locators. Presumably these ids are not exactly correct since ids should be unique on the page.
Applying the above to your page object, you could define the fill_element method as:
def fill_element(element, div)
current_div = self.send("#{div}_element")
current_element = current_div.text_field_element(:id => element)
# I assume this is a method you have already
fill_with_correct_data current_element
end
Calling the method would be like:
page.fill_element('name', 'main_address')
page.fill_element('address', 'secondary_address')
page.fill_element('phone', 'add_address')
Update - Created Scoped Locator:
This seems a bit messy, but is the best I can think of given the current implementation of the page object gem. I think there are some backlog feature requests for the gem that would make this cleaner.
What you could do is:
class AddressPage
include PageObject
class ScopedLocator
def initialize(name, page_object)
#name = name
#page = page_object
end
def populate_with(data)
scoped_data = Hash[data.map {|k, v| ["#{#name}_#{k}", v] }]
#page.populate_page_with scoped_data
end
def method_missing(m, *args)
#page.send("#{#name}_#{m}", *args)
end
end
['main_address', 'secondary_address', 'add_address'].each do |name|
define_method(name) { ScopedLocator.new(name, self) }
text_field("#{name}_name"){ div_element(:id => name).text_field_element(:id => 'name') }
text_field("#{name}_address"){ div_element(:id => name).text_field_element(:id => 'address') }
text_field("#{name}_phone"){ div_element(:id => name).text_field_element(:id => 'phone') }
end
end
This does 2 things:
It will create the normal accessors for each address. For example, it creates a :main_address_name, :main_address_address and :main_address_phone (and again for the other address types).
It creates a method, eg main_address that delegates actions to the normal accessors. This is to give the main_address.populate_with syntax.
With this page object, you could do the populate_with for a given address section:
page.main_address.populate_with data_for 'new_user'
page.secondary_address.populate_with data_for 'new_user'
page.add_address.populate_with data_for 'new_user'
As well as be able to update individual fields of an address. For example:
page.main_address.name = 'user'
I am new to Ruby and Sinatra. I am attempting to pass parameters from an HTML form and insert them into a PostgreSQL database (on Heroku) with Sequel.
The connection to the database works because I have succeeded with this code block
DB = Sequel.connect(connection_ credentials)
insert_ds = DB["INSERT INTO users (email_address, username, password) VALUES ('email#email.com', 'my_username', 'my_password')"]
insert_ds.insert
This works fine, but I cannot insert data from an HTML form. The data is not being passed.
So, for example, this does not work
#email_address = params[:email_address]
#username = params[:username]
#password = params[:password]
insert_ds = DB["INSERT INTO users (email_address, username, password) VALUES (#email_address, #username', #password)"]
insert_ds.insert
The error message is
Sequel::DatabaseError at /
PG::Error: ERROR: column "email_address" does not exist LINE 1: ...sers (email_address, username, password) VALUES (#email_addr... ^
which leads me to presume that the parameter was not passed
The full code is:
require 'sinatra'
require "rubygems"
require "sequel"
require 'sinatra/sequel'
require 'pg'
DB = Sequel.connect('postgres://my username:my password#ec2-54-243-250-125.compute-1.amazonaws.com:5432/d70h0792oqobc')
get '/' do
##users = users.all :order => :id.desc
##title = 'All Users'
erb :index
end
post '/' do
#email_address = params[:email_address]
#username = params[:username]
#password = params[:password]
insert_ds = DB["INSERT INTO users (email_address, username, password) VALUES (#email_address, #username, #password)"]
insert_ds.insert
redirect '/'
end
__END__
## layout
<!DOCTYPE html>
<html>
<head></head>
<body>
<%= yield %>
</body>
</html>
##index
<section id="add">
<form action="/" method="post">
<label class="label"> <span>Email Address: </span> </label> <input type="text" id="email_address" name="email_address" />
<label class="label"> <span>Username: </span> </label> <input type="text" id="username" name="username" />
<label class="label"> <span>Password: </span> </label> <input type="password" id="password" name="password" />
<input type="submit" value="Register me!">
</form>
</section>
Very grateful for all help!
Thank you.
You are trying to interpolate the global variables #email_address, #username, and #password but haven't used the interpolation operator #
Your SQL string apears as
INSERT INTO users (email_address, username, password) VALUES (#email_address, #username', #password)
when (apart from the stray single quote) you mean to have the values of those variables appear within the command. You should write instead
insert_ds = DB["INSERT INTO users (email_address, username, password)
VALUES (##email_address, ##username, ##password)"]
It is easy to diagnose this by adding
puts insert_ds
directly after the assignment.
Summary: Your code wasn't working because you were sending invalid SQL to the database, due to a misunderstanding of how Sequel works (and how Ruby's string interpolation works).
Details:
You almost never need to (nor should you) write raw SQL code when using the Sequel library. To use Sequel more appropriately, do this:
DB[:users] << {
email_address: params[:email_address],
username: params[:my_username],
password: params[:my_password]
}
or this:
DB[:users].insert( email_address:params[:email_address], … )
If you want to store these as instance variables for some reason (you're using them in a view response for the post?) then:
#email = params[:email_address]
#user = params[:username]
#pass = params[:password]
DB[:users] << { email_address:#email, username:#user, password:#pass }
If you really want to write raw SQL, you can use placeholders safely like so:
DB[
'INSERT INTO users (email_address,username,password) VALUES (?,?,?)',
params[:email_address],
params[:username],
params[:password],
]
The benefit of this is that it prevents users from performing a SQL Injection Attack on your site, for example by saying that their username is bob','haha'); drop table users; select('. (Think about what happens if you put that into a normal INSERT statement between two ' characters.)
Since your form parameters match your column names, you can even use a custom function for slicing your hash to make this even easier, e.g.
DB[:users] << params.slice(:email_address,:username,:password)
Read the Sequel Documentation (e.g. the Cheat Sheet) for more information on how to properly use it.
For the Future:
Your problem was unrelated to Sinatra or parameters, but simply how you were incorrectly using Sequel. For the future, to test if you are getting your parameters, you can do:
p params
and look at your console (I assume you are developing locally?) or else use whatever logging capabilities Heroku gives you if you are developing live on the server (*shudder*). Alternatively, you can even do:
post '/' do
params.inspect
end
and you will see, when you post the form, exactly what is in the params hash. Then you won't apply blame and investigation to the wrong area.
I am building a Sinatra app and wrote it linearly (no methods) to learn how Sinatra works. Now I am trying to refactor it, but the params from my form submission aren't being passed to the methods. Here are the routes:
get '/' do
erb :index
end
post '/' do
session = login(params[:username], params[:password])
get_courses(session, params[:username])
erb :index
end
And here is index.erb
<% if !#courses %>
<form action="/" method="post">
<input type="text" label="username" name="username">
<input type="password" label="password" name="password">
<input type="submit">
</form>
<% end %>
<% if #courses %>
<ul>
<% #courses.each do |course| %>
<li><%= course %></li>
<% end %>
</ul>
<% else %>
<p>No course data yet.</p>
<% end %>
I know the params are being passed to Sinatra because I was able to do a simple puts of them, but I get an "ArgumentError at / wrong number of arguments (0 for 2)" when I try to pass them to the methods.
Update
From the same file as the routes, here is the login method:
def login(username, password)
login = Savon::Client.new($LOGIN_WSDL)
login.http.auth.ssl.verify_mode = :none
session = login.request(:login) do
soap.body = { :id => username, :pw => password }
end
session.to_hash
end
Apologies if this is obvious, but have you defined #courses in any of your methods?
Something like this:
def get_courses(session, username)
# ...
#some logic to figure out courses based on session and username
# ...
#courses = ["Some course", "Another course"]
end
I just ran into this locally as well.
I found that, for reasons yet unclear to me, the keys in the #params Hash are always Strings rather than Symbols. The Sinatra documentation seems to reflect that the Hash is either a HashWithIndifferentAccess, or the keys are automatically symbolized as part of the #params method.
Not sure if that is the same issue you are experiencing, if it's an unintended behavior or Sinatra, or I'm just bad at reading docs.
The code that didn't work:
login_form = page.form_with(:method => 'post')
and code that works:
login_form = page.form_with(:method => 'POST')
I inspected the form object via puts page.forms.inspect and got
[#<WWW::Mechanize::Form
{name nil}
{method "POST"}
....]
html source:
<form class="login" method="post"> <fieldset>
<legend>Members Login</legend>
<div>
<label for="auth_username">Username</label> <input id="auth_username" name="auth_username">
</div>
<div>
<label for="auth_password">Password</label> <input id="auth_password" name="auth_password" type="password">
</div>
</fieldset>
<div class="buttons">
<input name="auth_login" type="submit" value="Login"><p class="note">Forgot your password?</p>
</div>
</form>
Is this a bug or intended behaviour?
Looking at the source, it could be that Mechanize is supposed to work like that. It forces the form method to uppercase when it fetches the form; you are expected to supply the method in uppercase when you want to match it. You might ping the mechanize person(s) and ask them if it's supposed to work like that.
Here in Mechanize.submit, it forces the form method to uppercase before comparing it:
def submit(form, button=nil, headers={})
...
case form.method.upcase
when 'POST'
...
when 'GET'
...
end
...
end
and here again in Form.initialize, the method is forced to uppercase:
def initialize(node, mech=nil, page=nil)
...
#method = (node['method'] || 'GET').upcase
But in page.rb is the code where mechanize is matching a form (or link, base, frame or iframe) against the parameters you gave, the parameter you passed in is not forced to uppercase, so it's a case sensitive match:
def #{type}s_with(criteria)
criteria = {:name => criteria} if String === criteria
f = #{type}s.find_all do |thing|
criteria.all? { |k,v| v === thing.send(k) }
end
yield f if block_given?
f
end
Well, it's a case sensitive match if you pass in a string. But if you pass in a regex, it's a regex match. So you ought to be able to do this:
login_form = page.form_with(:method => /post/i)
and have it work fine. But I would probably just pass in an uppercase String, send the Mechanize person(s) an email, and move on.