I have basic Store app - I want to be able to add product to basket and then delete it using Sinatra. My adding works, but I can't make delete to work too. I had special function for that, but now I just want to see whether delete route works at all. My main app has many routes, and part I'm talking about is:
post '/basket' do #it works, adds to basket and redirects
AddToBasket.new(params).call
redirect '/'
end
delete "/basket/delete" do #it doesn't work at all and doesn't redirect
basket = BASKET.find{|p| p.id == params["id"]}
BASKET.delete(basket)
redirect "/"
end
In HTML I have:
<% basket.each do |b| %>
<form action="basket/delete" method="post">
<input type="hidden" name="_method" value="delete">
<input type="hidden" name="id" value=<%= b.id %>>
<button type="submit">Delete</button>
</form>
<% end %>
As you can see, after clicking on "Delete" button, I'm sending "id" in my params helper.
basket = BASKET.find{|p| p.id == params["id"]}
should find one specific item with this id and delete it from my big array BASKET. But it doesn't work, after clicking on "Delete" I'm otransferred to basket/delete page and I have an error, because post for basket/delete doesn't exist. It should redirect me to my index page. What's more, it doesn't delete my basket item, it still exists. I'll appreciate any help.
You need this component in your middleware pipeline use Rack::MethodOverride
Another way seems to be put set :method_override, true in your Sinatra::Base class
See this also
Related
I have a ruby on rails app. I have a view that lists hyperlinks as such, this view is also rendered via same controller/action . when user clicks hyperlinks displayed on this page they go to the same controller/action, with a different id parameter. I want to be able to tell in my controller/view how user gets to these pages, i.e. whether user clicked on one of these hyperlinks vs user came through a different source?
<div id='list'>
<a href='controller/action/1'> some link 1 </a>
<a href='controller/action/2'> some link 2 </a>
</div>
Best approach is to add an URL parameter to the links.
<div id='list'>
<a href='controller/action/1?via=from_view'> some link 1 </a>
<a href='controller/action/2?via=from_view'> some link 2 </a>
</div>
Edit
You can access this URL parameter in your controller with params. Simple example:
class MyController < ApplicationController
def show
#via = params[:via] || "external"
end
end
<% if #via == "from_view" %>
<p>Hello coming from the view!</p>
<% elsif #via == "external" %>
<p>Hello coming from external!</p>
<% end %>
<div id='list'>
<a href='controller/action/1?via=from_view'> some link 1 </a>
<a href='controller/action/2?via=from_view'> some link 2 </a>
</div>
You can easily pass URL parameters into Rails URL helper.
For example:
<%= link_to "Link Title", some_url_helper_path(:param1 => "value1", :param2 => "value2") %>
Then you will be able to pass the parameters from the view to the controller's action.
Where the user came from is part of the HTTP spec (the referrer header) and you can access that in your controller with request.referer - you can then check if that value matches the page you want to target.
If your controller has #origin = request.referer You can then use if #origin == 'http://example.com/page-1’ in your view to switch what is being rendered.
This way you don’t have to remember to decorate all of your links with the extra param; rely on the fact that the browser is automatically adding it to headers for you.
Also see:
How to get request referer path?
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer
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'm trying to build an e-commerce site using Sinatra, as practice. I'm getting stumped on how to implement the 'Add to Cart' Button. My thought process about it is:
User clicks 'add to cart'
The button 'add to cart' invokes a ruby method, for example clicking on the following button
<input class='btn btn-primary' type='button' value='Add To Cart'></input>
should call a ruby method like
shop.add_to_cart(product, quantity)
An example of what this method might looking like:
class Shop
attr_reader :cart
def initialize
#cart = []
end
def add_to_cart(product, quantity)
#cart << product, quantity
end
end
In Rails, I think we use the helper_method in the controller? Is there anything similar I can do in Sinatra?
Thanks!
Note:
This is if you want to do it in ruby. You could probably also do it in javascript as mentioned in the other answer, but I cannot help you with that because I don't know javascript well enough.
To run the ruby method on button click you first need to create a <form> with only the button, then have that run a route in your app file that will run the method then redirect back to the page you were on. Here is my code (have not tested):
home.erb:
<form method="post" action="/runMethod">
<input type="hidden" name="product" value="whatever">
<input type="hidden" name="quantity" value="whatever">
<input class='btn btn-primary' type='submit' value='Add To Cart'>
</form>
You would set the values of the two hidden inputs (where I wrote "whatever") to the quantity and product according to their names.
App File:
class Shop
attr_reader :cart
def initialize
#cart = []
end
def add_to_cart(product, quantity)
#cart << product, quantity
end
end
get '/' do
erb :home
end
post '/runMethod' do
shop.add_to_cart(params[:product], params[:quantity])
redirect '/'
end
This can also be accomplished with ajax so that you dont have to leave the page:
$("#hideCarousel").submit(function() {
//posts the contents of the form to /action using ajax
$.post("/action", $("#myform").serialize(), function(result){
// assuming result is a string of the updated data in html
// and assuming that your data goes in an element with the id data-table
$("#data-table").html(result)
});
return false; // prevents the form from submitting normally
});
Rails/Sinatra run on the server side. If you want stuff happening in Rails directly you probably need a form and post back data.
nowadays people use javascript and it's javascript that makes the callbacks in an asynchronous fashion for this kinds of things.
I have got a form (below) that is posted to an umbraco surface controller.
#using (Html.BeginUmbracoForm("AddToBasket", "Basket"))
{
<h1>#Model.productSelectionModel.Product.Title - #Model.productSelectionModel.Product.Price.ToString("C")</h1>
<ul>
#foreach (var productOption in Model.productSelectionModel.ProductOptions)
{
<li>#productOption.Option.Title</li>
#Html.DropDownList(productOption.Option.Id.ToString(), productOption.ValuesInOptions.ToSelectList(f => f.OptionValue.OptionValue1,
f => f.Id.ToString(),
"Select"));
}
</ul>
<input type="submit" value="Add To Basket">
}
When I look at the HTML rendered for this form it seems to have added a hidden field called ufprt. Does any one know what this is? Why is it being added, I'm not using it any where ( I don't think I am anyway)
Any ideas?
<input name='ufprt' type='hidden' value='6C01896EF3D5F430F9ED041DD2B0D31F89FA969A085C6F4FDEC3C9D4B906846E7AA80041CEA12573E9F58C1740893B770AAE3319FAA8FA35C89A54D301CFE31B85ADC0D3D9506D208DB068D1257C5F0D5F1B3B90FD59A5C2938EED0A2EB1168AD4573CD5D043D47A8F1AA789E988CC614686B89BE57D35DA8EAAA110044C393F' />
It is to route the form to the correct controller/action method (Umbraco has the ability to route forms via that input value rather than the typical MVC approach of using the URL). I believe this is particular to surface controllers (i.e., it wouldn't apply to a normal controller, API controller, or RenderMvcController).
It is not a CSRF token as another answer indicates. If it were, it would likely have a name of "__RequestVerificationToken" as indicated here: http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-%28csrf%29-attacks
EDIT: This information has been added here: https://github.com/kgiszewski/LearnUmbraco7/blob/a97e85e5ad17e2ba9fc463f02c76885911046b57/Chapter%2006%20-%20Surface%2C%20WebAPI%20and%20RenderMVC%20Controllers/02%20-%20Surface%20Controllers.md#special-routing
this is probably a stupid question but I cannot figure out how to do it.
So I'm new to Scala/Lift and I read the ajax form chapter in http://simply.liftweb.net/index-4.8.html#toc-Section-4.8 but the "RedirectTo" in the example does not seem to be very "ajaxian" to me. Often in case of submitting a form via ajax, you would just partially rerender the same page, right?
So that's what I'm trying to do and am completely failing right now.
How do I let Lift rerender just a part of the same page after I submit the form via ajax?
Any hints would be appreciated. Thanks.
Basically, what I have looks like this:
<div id="main" class="lift:surround?with=default;at=content">
<h2>Welcome to your project!</h2>
<div class="lift:Test">
<div>
<form class="lift:form.ajax">
<fieldset>
<label for="name">Name:</label>
<input id="name" name="name" type=text>
<p></p>
<input id="save" type="submit" value="Save">
</fieldset>
</form>
</div>
<div>
<span id="theName">Name</span>
</div>
</div>
</div>
class Test {
def render = {
var name = ""
def process(): JsCmd = {
Thread.sleep(500)
S.notice("Entered name is: %s".format(name))
Noop
}
"#theName " #> "This shall be updated with the name given in the form above" &
"#name" #> (SHtml.text(name, name = _) ++ SHtml.hidden(process))
}
}
How would I update "theName" when submitting the form?
Have a look at http://lift.la/shtmlidmemoize-simple-ajax-updating (Example Code). There is SHtml.memoize and SHtml.idMemoize which automatically caches the HTML code. Not sure why it is not used in this example in the Simply Lift book.
You have a 2 step form right? The above poster is correct.
Save your transformation in a RequestVar.
in your above example, the method you want to save is render, so 1st memoize the transform:
private def renderTest= SHtml.memoize { render }
Then, you can save this memoized transformation in a RequestVar (lasts for 1 request), or maybe a TransientRequestVar depending on your needs.
private object testTemplate extends RequestVar(renderTest)
When you want to replay the transform, from an ajax event - testTemplate.is.applyAgain.
I might have misunderstood the original question, b/c if you want to do a 2 step form, you don't really need the memoize. The memoize is if something changes on your current form, and you want to update it via an ajax event, i.e. on click or on change, b/c normally the form wouldn't update unless you did an ajax submit.