Calling a Ruby Method via a html button in sinatra - ruby

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.

Related

Sinatra - delete action

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

Ruby, Sinatra DELETE method

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

Unable to click on checkbox. Using page-object-gem

I am having trouble clicking on a checkbox using the page-object-gem. The html code looks like this:
<label class='cc pointer value-apple'>
<input class='hidden' type='checkbox' value='apple'></input>
<div style="background-image:url(/images/fruit-apple-3453452346.jpg)"><div>
</label>
<label class='cc pointer value-banana'>
<input class='hidden' type='checkbox' value='banana'></input>
<div style="background-image:url(/images/fruit-banana-2359235674.jpg)"><div>
</label>
Using watir-webdriver I have no issues clicking on the label or div since the checkbox is hidden. Those work fine. However this does not seem to work using the page-object-gem. I have tried the following:
label(:select_fruit_apple, :class => /apple/)
on(FruitsPage).select_fruit_apple
div(:select_fruit_apple, :style => /apple/)
on(FruitsPage).select_fruit_apple
Any suggestions on how to do this is much appreciated.
Since Watir-Webdriver requires you to click the label or div, you will have to do the same with the page object.
The label and div accessor methods do not create methods to click the element. For the code you tried, when you call select_fruit_apple, it is just returning the text of the label or div element.
To click the label (or div), you will need to:
Get the label/div element. This can be done by either defining a method that uses a NestedElement or creating the label/div accessor and using the _element method.
Call the click method for the element. This will call Watir-Webdriver's click method.
Here is what the class would look like if you were using NestedElement directly:
class MyPage
include PageObject
def select_fruit_apple()
label_element(:class => 'value-apple').click
end
end
Alternatively, if you need the label/div element for multiple things, you might want to create an accessor for them:
class MyPage
include PageObject
label(:apple, :class => 'value-apple')
def select_fruit_apple()
apple_element.click
end
end
You can then call this method to set the checkbox:
on(FruitsPage).select_fruit_apple
Of course, if you want to save having to define a method for each fruit, you could use a method that takes a parameter:
class MyPage
include PageObject
def select_fruit(fruit)
label_element(:class => 'value-' + fruit).click
end
end
Then the method call would be:
on(FruitsPage).select_fruit('apple')

Ruby on Rhodes using ajax call

I am trying to build a dynamic dropdown in ruby on rhodes.There are basically two dropdowns on my screen and i am using ajax to get the values of the second dropdown from the database depending on the value selected in the first dropdown..I am a newbie to ruby and do not know the syntax on how to use ajax in ruby on rhodes..
JavaScript Code I am using...
$.post("/app/Settings/dropdown",
{ value:a },
function(data){
alert(data);
});
-----Partial Controller Code
enter code here
def dropdown
#a = #params['value']
puts #a
if #a.eql?"Auto"
mystring="auto1|auto2|"
else
mystring="personal1|personal2|"
end
end
I can get any parameter sent via ajax call to controller..My Question is how to send back the data from controller to function in that ajax call so that i can use that information to create a dynamic dropdown..I want to send this mystring to function(data)??
In Rhodes, controller actions can only render other actions or return a string consisting of partials. So, in order to populate a dropdown using AJAX, you'll have to render the view associated with the action which will returned as response to the AJAX call.
Controller 'dropdown' action:-
def dropdown
#a = #params['value']
if #a.eql?"Auto"
#optionList[:auto1]="auto1"
#optionList[:auto2]="auto2"
else
#optionList[:personal1]="personal1"
#optionList[:personal2]="personal2"
end
render :action => "dropdown"
end
'dropdown.erb' view:-
<% optionList.each do |key, value| %>
<option value="<%= key %>"><%= value %></option>
<% end %>
AJAX call:-
$.post(
"/app/Settings/dropdown",
{ value:a },
function(data){
data = data.replace("<div>","");
data = data.replace("</div>","");
alert(data);
}
});
Make sure you replace the div tags in the AJAX response, since Rhodes automatically surrounds AJAX responses with div tags.

Using AJAX in Rails: How do I change a button as soon as it's clicked?

Hey! I'm teaching myself Ruby, and have been stuck on this for a couple days:
I'm currently using MooTools-1.3-compat and Rails 3.
I'd like to replace one button (called "Follow") with another (called "Unfollow") as soon as someone clicks on it. I'm using :remote => true and have a file ending in .js.erb that's being called...I just need help figuring out what goes in this .js file
The "Follow" button is in a div with id="follow_form", but there are many buttons on the page, and they all have an id = "follow_form"...i.e. $("follow_form").set(...) replaces the first element and that's not correct. I need help replacing the button that made the call.
I looked at this tutorial, but the line below doesn't work for me. Could it be because I'm using MooTools instead of Prototype?
$("follow_form").update("<%= escape_javascript(render('users/unfollow')) %>")
ps. This is what I have so far, and this works:
in app/views/shared:
<%= form_for current_user.subscriptions.build(:event => #event), :remote => true do |f| %>
<div><%= f.hidden_field :event %></div>
<div class="actions"><%= f.submit "Follow" %></div>
<% end %>
in app/views/events/create.js.erb
alert("follow!"); //Temporary...this is what I'm trying to replace
*in app/controllers/subscriptions_controller.rb*
def create
#subscription = current_user.subscriptions.build(params[:subscription])
#subscription.save
respond_to do |format|
format.html { redirect_to(..) }
format.js {render :layout}
end
Any help would be greatly, greatly appreciated!
The ID selector should be used if there is only one of those elements on the page, as the Id selector us for unique IDs. A Class selector is what your looking for. Classes are used for multiple elements that have the same styles or same javascript events.
The code to replace Follow with Unfollow would be as follows in Mootools. I am not totally understanding your question, but you will need to assign a ID to the button.
document.addEvent('domready',function(){
$$('follow_form').addEvent('click',function(e){
//Ajax Here
this.set('value','Unfollow');
});
});
Mootools has a different set of functions than Prototype, so most (if not all) code designed for prototype will not work in Mootools. If you want to see the functions in Mootools, I recommend the Docs (its a good read): http://mootools.net/docs/
This code would work as well for you, if you do not/can not add unique IDs to each form element:
$$('input').addEvent('click',function(){
if(this.value == 'Follow') {
//Ajax Here
this.set('value','Unfollow');
}
});

Resources