making POST request in rails with hyperlinks

北战南征 提交于 2019-12-07 02:05:39

问题


I need a bunch of links on a page each of which makes a POST to a different controller. But when I use normal links, I get a ActionController::InvalidAuthenticityToken error. I understand this is because of the missing authenticity_token value. But I don't want to use forms to do the POST because I want them to be links and not buttons. Fact is, I want complete control over the styling of the links and buttons just don't do it for me. What's the standard way of doing such things?


回答1:


You have lots of options.

  1. Disable AT check: protect_from_forgery :only => []
  2. Assign onclick handler to the link and submit hidden form with javascript.
  3. Grab AT while generating view and add it as request parameter.

BTW, how exactly do you make 'post' requests using only 'href' attribute? I thought form is a must for that.




回答2:


Technically speaking, you should be using buttons and forms for anything that isn't a GET; hyperlinks intentionally don't allow for methods other than GET (without hacks like the _method parameter). One very practical reason is that sometimes, "web accelerator" browser add-ons prefetch links in the page; if a GET link kicks off a mutative action, the user or resource state may be erroneously modified.

That said, you can style buttons to behave like links; I use something like the following to do it quite nicely. It assumes a proper CSS reset with margins and padding and all that good stuff being nilled.

input.restlink {
  border: 0;
  background: #fff;
  color: #236cb0;
  cursor: pointer;
}
input.restlink:hover {
  text-decoration: underline;
}

With a rule like that, you can use <%=button_to "Yay button", something_path, :method => :post %> and it'll look and behave like a link just fine.




回答3:


If you are using jQuery, you could also do something like this:

<!--  in your view --> 
<%= form_tag( user_free_dates_path(:user_id => @user.id), :remote => true ) do |f| %>
  <%= hidden_field_tag :date, current_day.to_s(:short_db)  %>
  <%= link_to "Allow", "#", :class => "submit"%>
<% end %>        

// in application.js
$("form a.submit").live("click", function(){
  $(this).closest("form").submit();
  return false;
}); 

The idea is that you build a "real" form in your view, without a submit button. Then, the link_to with the "submit" class will trigger a form submit. The end result looks like a link but really it's a form. I'm sure there's a similar way using Prototype.

In my example, user_free_dates_path(:user_id => @user.id) is just a path helper, and the hidden_field_tag is one of the parameters that I'm passing in my POST request.




回答4:


If you aren't opposed to using jQuery and some ajax'n, I have a blog post that covers this.

Here's some basic info if you'd like a high level overview.

Add this one your layout:

<%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>

This code adds the auth token to the response. This way the JS can pick it up and submit it to the server.

Next we intercept any ajax call in the application.js:

function isPost(requestType) {
  return requestType.toLowerCase() == 'post';
}

$(document).ajaxSend(function(event, xhr, settings) {
  if (isPost(settings.type)) {
    settings.data = (settings.data ? settings.data + "&" : "") + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
  }
  xhr.setRequestHeader("Accept", "text/javascript, application/javascript");     
});

Add this to your application controller:

before_filter :correct_safari_and_ie_accept_headers
after_filter :set_xhr_flash

protected
    def set_xhr_flash
      flash.discard if request.xhr?
    end

    def correct_safari_and_ie_accept_headers
      ajax_request_types = ['text/javascript', 'application/json', 'text/xml']
      request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
    end

And in your view:

<%= link_to "Delete", delete_product_path(product), :class => 'delete' %>

Back over to application.js:

$('a.delete').live('click', function(event) {
  if(event.button != 0) { return true; }

  $.post(link.attr('href').substring(0, link.attr('href').indexOf('/delete')), { _method: "delete" });

  return false;
});

This example does a delete but it's really the same process to handle posts or puts. The blog post has a full sample application that demonstrates this.




回答5:


Borrowing heavily from these answers/sources

  • https://stackoverflow.com/a/3486036/326979
  • https://stackoverflow.com/a/12642009/326979
  • http://juliankniephoff.wordpress.com/2011/03/23/using-form_for-in-helper-methods/

to get a button that just looks like a link but functions like a button. In my app/helpers/application_helper.rb:

def button_as_link(action, link_text, hiddens)
    capture do
        form_tag(action, :class => 'button_as_link') do
            hiddens.each { |k, v| concat hidden_field_tag(k.to_sym, v) }
            concat submit_tag(link_text)
        end
    end
end

CSS:

form.button_as_link {
  display: inline;
}

/* See https://stackoverflow.com/a/12642009/326979 */
form.button_as_link input[type="submit"], form.button_as_link input[type="submit"]:focus, form.button_as_link input[type="submit"]:active {
  /* Remove all decorations to look like normal text */
  background: none;
  border: none;
  display: inline;
  font: inherit;
  margin: 0;
  padding: 0;
  outline: none;
  outline-offset: 0;
  /* Additional styles to look like a link */
  color: blue;
  cursor: pointer;
  text-decoration: underline;
}
/* Remove extra space inside buttons in Firefox */
form.button_as_link input[type="submit"]::-moz-focus-inner {
  border: none;
  padding: 0;
}

and finally in my template, I call button_as_link function:

button_as_link('/some/path',{:key1=>:val1,:key2=>:val2}, 'text to display')


来源:https://stackoverflow.com/questions/3486018/making-post-request-in-rails-with-hyperlinks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!