开发者

making POST request in rails with hyperlinks

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 t开发者_JAVA百科hem 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?


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.


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.


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.


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.


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')
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜