rails form post action without changing path - ruby-on-rails

I've got a view that renders a contact form. This contact form is rendered through javascript. There is also a javascript filter that the user can set viewing options in. (Depending on the settings, different markers are shown on a google map. The user can then click on the markers and in the viewbox click on a view button that renders some info and the contact form below the map)
If I were to make a normal form and use the post method with a #contact and contact routes, I would have to rerender the entire page after the #contact#create method was called. Which would mean all of the current users filter options would be unset. I could obviously save the settings, but feel like this is a hassle.
What I would like is for the contact form to call a method upon submit without actually changing paths, but I have no idea if this is even possible. (i'm using simple form so an answer for that would be preferable)

Since your question is quite broad, I'll have to answer as such:
if this is even possible
Yes it's possible.
You'll have to use ajax to send an asynchronous request to your server.
Ajax (Asynchronous Javascript And Xml) sends requests out of scope of typical HTTP; you can send/receive "hidden" data without reloading (this is what you want):
Don't worry - ajax is really simple once you understand it.
There's a great Railscast about it here:
Implementation
For you, you will just have to get your form to submit over ajax (javascript). There are two ways to do this:
Standard JS (JQuery)
Rails UJS (unobtrusive Javascript)
Basically, javascript acts as a mini browser, opening a url on your server, handling the returned data & doing what you tell it on the path:
<% form_tag contact_path, remote: true %>
<%= text_field_tag "name %>
<%= email_field_tag "email" %>
<%= submit_tag %>
<% end %>
You'll then be able to back this up with the corresponding controller action on your server:
#app/controllers/contact_forms_controller.rb
class ContactFormsController < ApplicationController
def create
... #-> your own logic here
respond_to do |format|
format.js #-> when receiving a pure xml request, this will fire
format.html
end
end
end
#app/views/contact_forms/create.js.erb
$("body").append("<%=j #variable %>");
Without going into too much detail, this is the most efficient way to achieve what you want. I can elaborate if required.

Related

Rails - Partial View form to execute SQL stored procedure

I've got a partial view:
<%= form_with url: admin_command_path() do |f| %>
[form stuff]
<%= f.submit :onclick(?) button_to? link_to? %>
<% end %>
This view/form doesn't need to create/update the model referenced, but should instead ONLY execute a sql() that I currently have stashed in ApplicationHelper AND in the referenced ^Command module, just to see where I can call it from. Little bit of pasta, meet wall situation :/
def command_string(id)
execute = ActiveRecord::Base.connection.exec_query("
exec [dbo].[FunctionName] #{id}")
end
I've tried just about all manner of form action, url routing, etc no no avail.
The ideal outcome is just calling the dang sql function from the form's submit button, or in a in the view itself, but either the function executes on load (rather than on click) or doesn't execute at all.
Yall don't get too hung up on the missing params, code for rough context. Just looking for an onclick -> sql exec path forward. Thx
You're thinking about this completely wrong.
Helpers are mixins/junk drawers where you can place code that you intend to resuse in your controllers and views. They are not called directly from your routes and "I want method X in my helper to be called when a button is clicked" isn't a very good way of going about it.
If you want to something to happen server side when the user clicks a link/form/button etc its done by sending a HTTP request from the client to the server. This can be a syncronous (normal) or asyncronous (AJAX) request.
You make Rails respond to HTTP requests by creating a route which matches the request and a controller action which sends a response.
# routes.rb
post 'admin_command', as: :admin_command,
to: "admin#do_the_thing"
class AdminController < ApplicationController
def do_the_thing
# This code is vulnerable to a SQL injection attack if the
# id originates from user! Use a parameterized query!
result = ActiveRecord::Base.connection.exec_query("
exec [dbo].[FunctionName] #{id}")
render text: "Okelidokeli duderino"
end
end
<%= form_with url: admin_command_path, local: true do |f| %>
<%= f.submit %>
<% end %>
You follow the same basic structure in Rails applications even when there is no model or view involved. Don't think in terms of functions - think in terms of the API your Rails application provides to the client and how you're going to respond.
Its only later if you need to resuse the code which performs the SQL query across controllers or in your view that you would place it in a helper when refactoring - it has no merit in itself.
If you then want to make this asyncronous (so that the page doesn't reload) you can do so by using Rails UJS or Turbo depending on your rails version. Or you can do from scratch by attaching an event handler to the form and sending an ajax request with the Fetch API.
But you should probally figure out the basics of the syncronous request / response cycle first.

Is there a real problem with using i.e. get method instead of put in an action through clicking on a button

For example I have button for thumbs up or I want to resend a specific mail or I change a specific enum state through clicking on a button (like published/unpublished)
Now regarding the rails implementation, I can make it work using either put, get or patch, since I only have to send a specific id.
In my opinion it seems to be best practice using patch, post or put wheter one or several attributes will change on my objects. So this seems mainly to be a convention here.
On the server side I will have to add some policies to allow only specific users to do so, but beyond adding policies are there any possible issues with not using the conventional http-method here?
One very real problem with using GET is that is supposed to be an idempotent method. Lets say the new guy creates the form:
get '/things/create', to: "things#create"
<%= form_with(model: #post, url: '/posts/create', method: :get) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= f.submit %>
<% end %>
class PostsController < ApplicationController
def index
#posts = Post.all
end
def create
#post = Post.new(title: params[:post][:title])
#post.save
redirect_to action: :index
end
end
He then tries it out in the browser in and is perfectly happy with it. Mission accomplished.
His boss then attempts to test it. He creates a Post titled "This new guy might not be so bad anyways". And then he hits the back button to try creating another Post. Weirdly it just loops back to the index page. He tries it again - the only thing that happens is that the page starts to fill up with "This new guy might not be so bad anyways" and he is becomes less and less convinced that its actually true.
If you used POST, PATCH, PUT or DELETE the browser would have warned him that he is about to resend a form. Thats why GET should never change anything on the server (besides maybe your pageview stats).
It also opens up for any malicous actor to get users to create, delete or modify resources simply by fooling them into clicking a link. The malicous actor doesn't even have to got though the effort of creating a phishing site and circumventing the anti-CSRF protection that Rails provides.
There is absolutely no difference between how POST, PATCH, PUT or DELETE are treated by the client or server beyond the conventions of Rails.
But since Rails is a highly convention driven framework which adheres to a specific flavor of REST it really befits you to follow those conventions if you want to be productive and not be that new guy.
When it comes to actions beyond the classical CRUD verbs its really down to your best judgement and intent is really what matters. What does the action do? Is it updating something (PATCH)? Is it actually a separate resource? (POST /mails/1/mailouts). As you may see there is no easy answer. Just be clear and document what you're doing if you're unsure.

Why a ruby command runs even if a user don't activate the script yet?

I'm a Ruby user, trying to make a web service that receives user's active request. I made a button, of which class is a "btn-send-alert". Then after the html code, I put a script function.
<div class="page-title">
<button class="btn-send-alert" style="background-color: transparent;">Help Request</button>
<p>Hello</p><br>
</div>
........
<script>
$(".btn-send-alert").click(function(){
alert('hello!');
<% Smalier.class_alert(#lesson,current_user).deliver_now %>
});
</script>
The problem is, the ruby code just start on its own even before I click this button.
And if I click this button, no email is delivered any longer.
Maybe in some point, I think I'm seriously wrong but I can't find where it is. Is there way that I can make this function work correctly?
Looking forward to seeing the response!
Best
Thanks to Rich, I am now able to write a code that works fine! The below code is that code.
<%= content_tag :div, class: "page-title" do %>
<%= button_to "Help Request", support_path, method: :get, remote: true, class:"btn btn-danger", params: { lesson_id: #lesson.id, user_id: current_user.id} %>
<%= content_tag :i, "wow!" %>
////
def support
#lesson = Lesson.find_by(:id => params[:lesson_id])
current_user = User.find_by(:id => params[:user_id])
mailer.class_alert(#lesson,current_user).deliver_now
end
Above code runs well!
I'm a Ruby user
Welcome to Rails!!
Stateless
Firstly, you need to understand that Rails applications - by virtue of running through HTTP - are stateless, meaning that "state" such as User or Account have to be re-established with each new action.
In short, this means that invoking actions/commands on your system have to be done through ajax or another form of server-connectivity.
Many native developers (native apps are stateful) don't understand how Rails / web apps are able to retain "state", and thus make a bunch of mistakes with their code.
receives user's active request
Even if you understand how to set up authentication inside a Rails app, it's important to understand the virtues of it being stateless... EG the above line means you have to have a user signed in and authenticated before you can send the request.
This forms one part of your problem (I'll explain in a second)
ERB
Secondly, the other problem you have is with the ERB nature of Rails.
the ruby code just start on its own even before I click this button.
This happens because you're including pure Ruby code in your front-end scripts. This means that whenever these scripts are loaded (triggered), they will fire.
The bottom line here is you need to put this script on your server. Otherwise it will just run...
Fixes
1. ERB
<%= content_tag :div, class: "page-title" do %>
<%= button_tag "Help Request", class:"btn-send-alert" %>
<%= content_tag :p, "Hello %>
<% end %>
You'll thank me in 1+ months.
Convention over Configuration means you use as many of the Rails helpers as you can. You don't need to go stupid with it, but the more "conventional" your code is, the better it will be for future developers to improve it.
Another tip - only use HTML for formatting; CSS for styling. Don't use <br> unless you actually want to break a line.
Another tip - never use inline styling - Rails has an adequate asset pipeline into which you should put all your CSS
--
2. Ajax
Secondly, your use of Javascript is incorrect.
More specifically, you're calling a server-based function inside front-end views. To explain this a little more, I'll show you the famed MVC image I post on here a lot:
This is how Rails works (MVC - Model View Controller) - this means that whenever you deal with your application, you have to accommodate a layer of abstraction between the user & your app -- the browser.
By its nature, the browser is not stateful - it stores session cookies which you have to authenticate on the server. You cannot call "server" code in the front-end HTML/JS.
This is why your Ruby code is firing without any interaction, although I'm not sure how it's able to fire in the asset pipeline.
If you want to make it work properly, you'll need to create a controller action to invoke the mailer send function, which you'll be able to do using the following setup:
#config/routes.rb
get :support, to: "application#support", as: :support -> url.com/support
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
respond_to :js, only: :support
def support
Smalier.class_alert(#lesson.current_user).deliver_now
end
end
#app/views/controller/view.html.erb
<%= content_tag :div, class: "page-title" do %>
<%= button_to "Help Request", support_path, method: :get, class:"btn-send-alert" %>
<%= content_tag :p, "Hello" %>
<% end %>
#app/views/application/support.js.erb
alert ("Hello");
Each and every ruby code snippet embedded in ERB runs on server, in order to assemble a valid HTML or Javascript script for browsers to render.
Browsers don't understand ruby script at all, all they can understand is HTML and Javascript.
In your case (I'm supposing you're using rails since you tagged your question with ruby-on-rails), emails are delivered when rails engine is assembling HTML's.
If you want the emails being sent after the users click that button, the correct way is:
Define an action method in some controller, give it an URL (i.e. add a route in config/routes.rb), send email in that action.
When the button on the page is clicked, send an AJAX request to that URL.

Rendering a Rails partial within a single-page app after button-click

How can I render a rails partial within a single-page app after a button-click (assuming there are at least two controllers at play)?
It is unclear to me, regardless of how much material I read:
How many controllers I need
How many views I need
How many actions I need
To which action do I assign resources to?
This is precisely what I am trying to do:
I would like for a user to visit the index page of my application. At that page, they should be able to click a button to "get party event results." The results returned to them should come from the database. This button-click request should be AJAX.
From what I understand, that means
I need two controllers: 1) StaticPagesController and 2) PartyEventsController. It means that I should have a partial for party events app/views/layouts/_part_events_results.html.erb.
I should have an app/views/static_pages/index.html.erb view.
It means that I will need a js.erb file somewhere outside of the assets/javascript path.
Currently I have:
app/controllers/static_pages_controller.rb
def index
end
app/controllers/party_events_controller.erb
def food_event
end
app/views/static_pages/index.html.erb
<%= button_to "get events", 'layouts/party_events/', remote: true %>
I have 'remote: true' set because this should indicate that the link will be submitted via AJAX.
app/views/static_pages/_party_events.html.erb
<% food_events.each do |event| %>
<li><%= event.text %></li>
<li><%= event.location %></li>
---------------------
<% end %>
Other info, if helpful:
Rails 4.0.0
There will eventually be many types of events, e.g. "food", "music". Do I need view partials for each one?
Open to any design solution as long as it's a single-page application. I know how to achieve this in PHP/JS, but am struggling to understand how with MVC.
In your static page, you should have a DOM element available for the ajax to replace or modify.
In your button_to, you should specify the controller and action you would like to hit
In your party_event_controller#action, you can respond with a js and have it render a template, #action.js.erb which will perform the DOM manipulation to replace or update the page.
Similar question asked: Rails - updating div w/ Ajax and :remote => true
An tutorial showing ajax crud with unobstructive javascript: http://stjhimy.com/posts/07-creating-a-100-ajax-crud-using-rails-3-and-unobtrusive-javascript

Posting a form through popup in Rails

I have a model called Details, and two controller methods new and create. New method displays a form to create Details (in new.html.erb), which gets posted to the Create method that saves it. (when it succesffully saves, it it renders the new.html.erb file with the details.) This works as expected.
Now i want a separate page with a link to fill in these details. I want the click to do the intended work through a popup, example redbox. When you click on that link, a popup should show the form, whose submit should post the form, and if it is successfully done, then refresh the original page. If the post is unsaved, then the same form should show the errors. What do i need to do to make it work in Ror? I guess i need some stuff to go in new.js.rjs and maybe create.js.rjs, but i can't really figure out what to put in those files.
Redbox updates the page's dom to add a div at the end of it. So your form is a part of the main page.
So if you add a form tag in this redbox, all your page will be reloaded as there's only one.
So you add anywhere in your page (preferably at the end) :
<div id="redbox" style="display: none;">
<%= form_for #details do %>
# Whatever form fields you want here
<% end -%>
</div>
You do a link that'll open the redbox :
<%= link_to_redbox 'Open The Details Form', 'redbox' %>
This will display your redbox with your form.
And as it is the same page, not a new windows, when you'll validate your form, it'll reload the all of it.
I used a link_to_remote_redbox for this, which on clicking, fetches the form rendered by the new method call in a popup widnow. it submits to the create function and it is now working. actually i had previously made it work without ajax and i was having difficulty in making it work with ajax. the problem was solved once i made separate partials for ajax calls, and doing:
respond_to do |format|
format.js { render :template => 'detail/ajax_template'}
...
end
I provided different templates for both create and new method calls, used the :html => {:id => 'some-id'} in form and the :update => {:some-id} to replace the form with the message that it worked correctly.
So I didnt know that i needed separate templates for the different styles of forms, and that i needed to use the :update option to replace the form when i asked the above question.

Resources