I'm puzzling on the following problem: in my web app users can select their language from a dropdown box. Upon selection, a piece of Javascript takes the current url, replaces the old locale substring in it with the new locale string and sets the window.location to the new url. That works great as long as the URL the result of a GET request.
My problem occurs when the user posts a form and it returns a validation error. The url is now a POST url, that (in my case) does not work with a GET request. Hence, if the user now decides to switch language, an invalid GET request will be sent to the server.
Any ideas how to solve this?
To be a little more specific: I'm running into this problem in Rails 3.0.9 on the registration form of Devise (1.4.2)
I often write my own routes to avoid the exact problem you are describing. I think it's a big flaw in Rails routing.
You may be able to change the Devise routes, and the rest of your resource routes, so that your GET and POST URLs look identical. Here's an example of what I mean.
scope '/posts' do
get '/:id/edit' => "posts#edit", :as => "edit_posts"
post '/:id/edit' => "posts#update", :as => "update_posts"
end
It's a bit more work doing things that way, though.
You could always disable the locale select for the pages where you have problems.
You could check with javascript if there is a .fieldWithError classed element on the page. If so, you can be sure that the last request was POST. In that case, you would have to change the 'action' attribute for the form (change the locale) and submit it again.
You can not access the HTTP method of the current response with javascript.
#WizardofOgz thanks for putting me on the right track. I'm adding this answer, so that I can paste in some code, which I can't in a comment, but I'll accept your answer.
I found this related post specifically for Devise. I've tested it on the password reset routes and this seems to work fine (although I guess this isn't complete yet):
devise_for :user, :path_names => { :sign_up => "register", :sign_in => 'login', :sign_out => 'logout' }, :skip => [:passwords] do
scope :controller => 'devise/passwords' do
post :create, :path => 'user/password/new', :as => :user_password
get :new, :path => 'user/password/new' , :as => :new_user_password
end
end
Related
there is a website called https://www.guidgenerator.com/online-guid-generator.aspx which generates a global unique identifier. I'm trying to use perl's Mechanize to post to the site to pull that guid. I realize that this is based on javascript but was wondering if I could issue the proper post to pull the number. I trace it from my browser and I've got the headers all in the request but the returned html doesn't contain the guid.
This is from a successful run:
<textarea name="txtResults" rows="2" cols="20" id="txtResults" style="font-family:Courier New,Courier,monospace;font-size:Larger;font-weight:bold;height:152px;width:421px;">qk5DF22bhkm4C2AwZ5OcZw==</textarea>
and this is from my script:
<textarea name="txtResults" rows="2" cols="20" id="txtResults" style="font-family:Courier New,Courier,monospace;font-size:Larger;font-weight:bold;height:152px;width:421px;"></textarea>
This is the form within the page:
In my script I dump out the form and the input fields required with the following:
my #forms = $mech->forms;
foreach my $form (#forms) {
my #inputfields = $form->param;
print Dumper \#inputfields;
}
resulting in
$VAR1 = [
'__EVENTTARGET',
'__EVENTARGUMENT',
'__LASTFOCUS',
'__VIEWSTATE',
'__VIEWSTATEGENERATOR',
'__EVENTVALIDATION',
'txtCount',
'chkUppercase',
'chkBrackets',
'chkHypens',
'chkBase64',
'chkRFC7515',
'chkURL',
'LocalTimestampValue',
'btnGenerate',
'txtResults'
];
and this is the post
my $mainpage = "https://www.guidgenerator.com/online-guid-generator.aspx";
$mech->post( "$mainpage",
fields => {
'txtCount' => "1",
'chkBase64' => "on",
'LocalTimestampValue' => "Date%28%29.getTime%28%29",
'btnGenerate' => "Generate+some+GUIDs%21",
'txtResults' => "",
'__EVENTTARGET' => 'on',
'__EVENTARGUMENT', => 'on',
'__LASTFOCUS', => 'on',
'__VIEWSTATEGENERATOR' => "247C709F",
'__VIEWSTATE' => 'on',
'__EVENTVALIDATION' => 'on',
'chkUppercase' => 'off',
'chkBrackets' => 'off',
'chkHypens' => 'off',
'chkRFC7515' => 'off',
'chkURL' => 'off',
},
);
When I do the trace on the website I get the headers but there is another tab called Payload. That contains most of the fields listed above. I try to input these fields into the POST but not sure if I should be doing this differently or it doesn't matter because its javascript?
I know this is a lot of information. I'm not even sure that perl's mechanize can pull this information. Any help would be appreciated. Please let me know any other data you want me to post here.
You can use Mech's built-in stuff to do this. There is no need to submit any extra fields or headers.
use strict;
use warnings;
use feature 'say';
use WWW::Mechanize;
my $mech = WWW::Mechanize->new;
$mech->get('https://www.guidgenerator.com/online-guid-generator.aspx');
$mech->field( txtCount => 10 );
$mech->click;
say $mech->value('txtResults');
This will output something like:
$ perl mech.pl
211b3cad1665483ca303360bdbda0c61
ecc3348d83cb4bb5bdcb11c6148c5ae1
0a3f2fe5748946a1888a4a5bde8ef2e6
acb26deb9fda4411aa64638cdd1ec5f1
2afe609c355b4a10b6a0ae8c74d3aef1
30fd89ab170147cfb24f131346a203e3
2301d258e1d045aa8f0682f2ea14464c
f064507ca3e14a4eb860b0a30ba096ed
9a42b15d5c79420c921dcc07c306459b
5bea2e345f75453caaf795681963866a
The crux here was that you cannot use $mech->submit as that wouldn't submit the value of the submit button. That's a bit annoying. So instead, you have to use $mech->click, which pretends the default form's default submit button was clicked, hence submitting that value as well. That's just how buttons work on forms, and in this case the backend checks the value to see which one was clicked.
You can then use $mech->value to get the field value out. You'd probably want to split it to process it further.
The JavaScript in this page is actually completely irrelevant to the functionality. All it does is save and restore the settings you've chosen in a cookie, so that when you come back the same checkboxes will be ticked. That's nice, but nowadays probably better done with local storage on the frontend side. However you don't have to deal with the JS at all to crawl this page. The main functionality is backend side.
You might also be interested in $mech->dump_forms, which is a nice debugging aid that prints out all the forms with fields and values. Another good debugging aid when working with Mech (or any LWP based class) is LWP::ConsoleLogger::Everywhere. That's what I used to compare my program's request with my browser's one to find the missing button form field.
Disclaimer: I am a maintainer of WWW::Mechanize and I wrote LWP::ConsoleLogger::Everywhere.
I am doing an initial test of my Rails API by performing a fetch to my backend and using console.log() to view the results in my browser console. My error appears when I make the request to my namespaced url to see a list of programs.
I have checked the controller show method, as well as my routes and model. I know that it is returning html instead of JSON and that is why the fetch is breaking in part.
App.js
componentDidMount(){
fetch('http://localhost:3000/api/v1/programs')
.then(res => res.json())
.then(json => console.log(json))
programs_controller.rb
class Api::V1::ProgramsController < ApplicationController
def index
#programs = Program.all
render json: #programs
end
def show
#program = Program.find(params[:id])
render json: #program
end
private
def program_params
params.require(:program).permit(:url, :name, :network, :image)
end
end
I expected to see a list of 18 programs I placed in my seeds.rb file but I get the error from ActiveRecord that says 'Couldn't find Program without an ID'. I did try passing :id into the .permit() in my program_params method but had no luck there either.
I think your api/v1 url will be miss in routes.rb or something wrong. Can you show me your routes.rb file?
additionally
i have checked your source code in github
after you write comment to this answer
and I find wrong word
you have coding resource in routes.rb and it's wrong word
you have to change resource to resources in config/routes.rb
2.5 Singular Resources
Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like /profile to always show the profile of the currently logged in user. In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action:
https://guides.rubyonrails.org/routing.html#singular-resources
I recommend you have to read rails guides
https://guides.rubyonrails.org/
The following code successfully deletes a record when a button is clicked and confirmed:
Router (Express):
router.post('/meetings/delete/:slug', catchErrors(meetingController.deleteMeeting));
Controller:
exports.deleteMeeting = async (req, res) => {
const slug = req.params.slug;
const meeting = await Meeting.remove({ slug });
req.flash('success', 'meeting successfully deleted!');
res.redirect(`/meetings`);
};
View Template (Pug/Jade):
form.delete-meeting-form(method='POST' action=`/meetings/delete/${meeting.slug}?_method=DELETE`)
input.button.button--delete(type='submit' value='DELETE MEETING' onclick='return confirm("Are you sure you want to delete this meeting? (Cannot be undone)")')
This code works great as-is. However, it seems strange to me that I seem to be required to use a POST route in order to complete my DELETE request. When I attempted to use router.delete to form the route, it did not work.
I understand that HTML doesn't support DELETE and PUT/PATCH requests very well, but why was it a problem to name my Express route delete? Did I do something wrong?
Delete will work fine with express. Regular HTML forms do not support delete. If you add a delete route to your application you can test the delete method with ajax (fetch, axios, jQuery, etc.) or curl:
curl -X "DELETE" http://foo/meetings/some-meeting
If you'd like to be able to use regular HTML forms with delete, check out the method-override middleware. The custom logic section shows how you'd create an override that's similar to how Rails handles method overrides.
This is a matter of convention. jQuery (and Javascript in general) supports DEL and PUT in addition to GET and POST, but nothing is stopping you from deleting using GET or POST, as you are doing it above. It is a good practice to synchronize your api calls with corresponding calls but it takes a bit more effort to setup.
Basically i'm receiving some data (variables a and h) from another site, and i would like to receive them and then make a POST with that data (the a and h variables that i already received) to my self app, i know that doesn't make a lot of sense but i need to do that.
I already coded the part where i receive the data from the other site.
MyRoute
post '/', to: "pages#info"
MyController
class PagesController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:info]
protect_from_forgery :except => :info
layout "second"
def info
if params[:initzarqkr].present?
#h1 = params[:h]
#a1 = params[:a]
#h1_i = #h1.to_i
#a1_i = #a1.to_i
end
end
end
My View (shown at / where the post is received)
<h1><%= #h1 %> | <%= #a1 %> </h1>
So i want to take that data i stored on the variables #h1 and #a1, re POST it to my app and then store it on some other variables in order to show them on my view at the route /, instead of the ones that i got on the post that i received from outside (even though i know that the values should be the same ).
How can i grant this?
Thanks very much for reading.
What you'r talking about sounds like a redirect. A redirect is every time a GET request within Rails. Converting a POST to GET request has some drawbacks. You have to consider all the things which are different between a GET and POST request. One of the thing is the request lenght restriction.
In my point of view you have the following options:
redirect to the wanted action and append the parameters to the redirect url
handle the request directly within info and redirect after handling without params
store the parameters within your session hash and redirect to the other action without params
use a net/http or another libraray to post within info your data to an url and use a redirect
create a view for info with a form which gets submitted automatically via javascript
If a redirect with code 307 (repost data to new url) would work within Rails, I don't know.
I am trying to notify the browser of the user of a change of the status of the model. I am trying to use the live-module of Rails for that. Here is what I have got so far:
require 'json'
class Admin::NotificationsController < ActionController::Base
include ActionController::Live
def index
puts "sending message"
videos = Video.all
response.headers['Content-Type'] = 'text/event-stream'
begin
if(params[:id].present?)
response.stream.write(sse({id: params[:id]},{event: "video_encoded"}))
end
rescue IOError
ensure
response.stream.close
end
end
private
def sse(object, options = {})
(options.map{|k,v| "#{k}: #{v}" } << "data: #{JSON.dump object}").join("\n") + "\n\n"
end
end
The idea behind the above controller is, that when its url gets called with a parameter, it would send this parameter (in this case the id) to the user. Here is how I am trying to call the controller:
notifications_path(id: video.id)
Unfortunately though, the following event-listener in the browser does not fire, even if I use curl to provoke an event:
var source = new EventSource('/notifications');
source.addEventListener("video_encoded", function(event) {
console.log("message")
console.log(event)
});
The goal of this is, that I want to add an dom-element to a certain page (later on) if there is a change. There may be a better way, but Ruby Live seemed like a suitable solution. Any tips or proposals of a different approach are appreciated.
Your use case does not seem like a valid use case for ActionController::Live. You are not sending a streaming output to the browser. You do a one time check on ID and send the JSON output.
Use a regular controller and get the request by AJAX instead of EventSource.