How to add additional params to a button_to form? - ruby-on-rails

I want to have a Submit button. It updates one field on the submission; submission.state = :submitted
Now, I could make a custom route and a custom action and just post to that. But that seems really heavy-handed. Especially since I'll also have a reject button and possibly more. Needing a custom route & action for each of those seems downright silly to me.
It would be much nicer if I could do something like
button_to "Submit", submission_url(submission), :method => :put, :submission => { :state => :submitted }
Which would post to the submission's update method and update only the desired field.
But that doesn't work. How can I make it work? Or do you have a better idea of how to do this?

The pull request mentioned by #AugustinRiedinger has been merged and is now available as of Rails 4.1.0. Now just add the params option:
params: { state: :submitted }

It's not as concise, but without extending Rails, this will get me by:
= form_for submission, :html => { :class => "button_to" } do |f|
= f.hidden_field :state, :value => :submitted
= f.submit "Submit", :class => "link"

Add params:{} at the end, it will generate hidden_field
<%= button_to user.name, user, class:"btn btn-default", style:"", method: :patch, remote: true, params: { a_field: false, an_other_field:"a new value" } %>

I have something similar that works:
button_to "Submit", submission_url(submission, :submission => { :state => :submitted }), :method => :put

So, as from this rails pull request : https://github.com/rails/rails/pull/10471
Here is what you can do to have your custom button_to.
In application_helper.rb, add these lines:
module ApplicationHelper
// Unfortunately these 2 methods need to be redefined. I don't know how I could access the original ones.
def token_tag(token=nil)
if token != false && protect_against_forgery?
token ||= form_authenticity_token
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
''
end
end
def method_tag(method)
tag('input', type: 'hidden', name: '_method', value: method.to_s)
end
def button_to_with_params(name = nil, options = nil, html_options = nil, &block)
html_options, options = options, name if block_given?
options ||= {}
html_options ||= {}
html_options = html_options.stringify_keys
convert_boolean_attributes!(html_options, %w(disabled))
url = options.is_a?(String) ? options : url_for(options)
remote = html_options.delete('remote')
params = html_options.delete('params') { Hash.new }
method = html_options.delete('method').to_s
method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe
form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
form_options[:class] ||= html_options.delete('form_class') || 'button_to'
form_options.merge!(method: form_method, action: url)
form_options.merge!("data-remote" => "true") if remote
request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
html_options['type'] = 'submit'
button = if block_given?
content_tag('button', html_options, &block)
else
html_options['value'] = name || url
tag('input', html_options)
end
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
params.each do |name, value|
inner_tags.safe_concat tag(:input, type: "hidden", name: name, value: value.to_param)
end
content_tag('form', content_tag('div', inner_tags), form_options)
end
end
And to use it:
= button_to_with_params 'Awesome button', awesome_action_path, method: :put, :params => {:my_param => 'my_value'}
Enjoy! Have fun Railing!

If I read things correctly what you are effectively wanting to do something specific when a standard rails form is submitted in the standard way.
Notice that when a form is submitted using e.g.
f.submit "Save Changes"
then
params[:commit] = "Save Changes"
The GOOD thing about this is that it can allow you to do some appropriate branching in the controllers update action.
The BAD thing is that it's brittle. If one day you or someone else decides to change the button text, things break.. which is bad.
K

As of Rails 3.2.1 you can add additional params to the :html_options hash using the :form key.
http://apidock.com/rails/v3.2.1/ActionView/Helpers/UrlHelper/button_to
This did not exist prior to 3.2.1 so the more verbose solution of declaring a form with hidden attributes was required.

Related

Change params inside controller rails

How can I change params inside controller?
When I click the accept it will pass the status is approved but if diff <= 0 change status to rejected
View
<%= link_to 'Accept', friend_path(s, :request => {:status => 'Accepted'}), method: :put %>
into this
if diff <= 0
req_params[:status] = "Rejected"
#request.update(req_params)
end
end
private
def req_params
params.require(:request).permit(:status)
end
end
To modify your status param, you need to do the following:
params[:request][:status] = 'Rejected'
or
req_params[:request][:status] = 'Rejected'
Because your request take params like this:
Parameters => { some_param => 'Something', required => { permited1 => 'bla',
permited2 => 'bla2',
... } }
I hope that helps you

Rails: using partials for inputs is correct?

I'm building a Rails app. I've done all the logic (db, controllers, models, etc). Now its time to make it nice.
In order to centralize the view of the app I was thinking in creating partials for the common stuff. For example one partial called common/_text_input.html.erb that will contain
<div class="field">
<%= f.label id %><br />
<%= f.text_field id %>
</div>
This will be called from inside a form using
<%= render partial: "common/text_input", locals: { f: f, id: :name } %>
Is this approach correct? Is there any other option in rails to do this?
If this is the correct way to do this, how can I acchieve this for a form tag, for example (where content is inserted inside it)?
Thanks
1 - There is another option to do this, Helpers and content_tag:
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
Usage:
= form_for #resource do |f|
= text_input(f, :first_name)
= text_input(f, :last_name, input: { style: 'color: red;' }, label: { class: :another_class })
2 - It is correct to do with partials, but it is not as flexible as the Helpers are (see the options hash and the possibility to use another method in specific cases). To handle the form_tag (i.e. no form_builder), you can implement a new method:
# usage
= form_tag root_path, method: :get do
= text_input(nil, :search, input: { value: params[:search] }, label: { content: "Search for something!" })
# helper methods
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
return text_input_tag(attribute, options) if form_builder.blank?
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
def text_input_tag(attribute, options = {})
value = options[:input].try(:delete, :value)
label_content = options[:label].try(:delete, :content)
content_tag :div, options[:div] do
label_tag(attribute, label_content, options[:label]) + content_tag(:br) + text_field_tag(attribute, value, options[:input])
end
end

Rails check_box_tag how to pass value when checked ajaxily

On my index page for my Task model, I want to show a checkbox for every row that corresponds to the boolean field "complete" in my Task database table.
Currently my code gets into the method "Complete", but it does not contain the value of the checkbox that the user just did (i.e. if they just checked the box, it does not pass true to my "Complete" method).
How can i pass the value that the user just performed - either checked or un checked?
/views/tasks/index.html.erb
<% #tasks.each_with_index do |task, i| %>
<tr>
<td><%= check_box_tag 'Complete', task.complete, task.complete, :data => {:remote => true, :url => url_for( :action => 'complete', :id => task.id, :complete => task.complete ), :method => :put}, :class => 'input-large' %></td>
</tr>
<% end %>
/controllers/tasks_controller#complete
# PUT /complete/1
def complete
#task = Task.find(params[:id])
p "inside complete"
p "complete = #{params[:complete]}"
#task.complete =
if #task.update_attributes(params[:task])
p "inside update"
render :text => "success"
else
p "inside error"
end
end
The suggestion from this issue in rails/jquery-ujs github repo worked for me: https://github.com/rails/jquery-ujs/issues/440#issuecomment-197029277
For you it would be:
<%= check_box_tag 'complete', '1', task.complete, {
onchange: "$(this).data('params', 'complete=' + this.checked)",
data: { url: url_for(action: 'complete', id: task.id,), remote: true, method: :patch },
} %>
If you are using jQuery, you can write a click event.
$('.input-large').click(function() {
var checked;
if ($(this).is(':checked')) {
checked = true;
} else {
checked = false;
}
$.ajax({
type: "POST",
url: "/tasks/complete",
data: { id: $(this).data('post-id'), checked: checked }
});
});
As of Rails 4, you should be able to ditch all the JS from the original answer. The code in your question should just work due to jQuery UJS magic.
It turns out that adding remote: true to an input causes jquery-ujs to make it ajax-y in all the nice ways. Thoughtbot's "A Tour of Rails jQuery UJS" briefly touches this (and many other good things available); the "Unobtrusive scripting support for jQuery" page in the jQuery UJS wiki does a thorough job on this as well.
check_box_tag 'complete', task.complete ? 'false' : 'true', task.complete, ...
:url => url_for( :action => 'complete', :id => task.id )
This way in your controller you can get params[:complete].
And you should implement complete.js.erb to rerender checkbox, so next click will send inverse value
Or you can implement js on click event
$('.input-large').on('click', function() {
$.ajax({
type: "PUT",
url: "/tasks/complete/" + $(this).data('post-id')
data: { complete: $(this).is(':checked') }
});
});
and don't forget to place data-post-id param to your checkbox

How can I make the html output from button_to have single quotes instead of double?

I've got this line:
<%= button_to 'Post', newpost_path(:type => 'short_note') %>
which outputs this:
<form method="post" action="/posts/newpost?type=short_note" class="button-to"><div><input type="submit" value="Add" /></div></form>;
But I need the output to have single quotes, not double. How can I do this?
<form method='post' action='/posts/newpost?type=short_note' class='button-to'><div><input type='submit' value='Add' /></div></form>;
Try this:
<%= (button_to 'Post', newpost_path(:type => 'short_note')).gsub('"', '\'') %>
create a file in rails_root/lib called url_helper_overrides.rb that simply overrides the button to method. Or you could add the button_to method below to your application_helper. This method is taken straight from the UrlHelper source in Rails (Github Link), I simply replaced the double quotes with single quotes.
module ActionView
module Helpers
module UrlHelper
def button_to(name, options = {}, html_options = {})
html_options = html_options.stringify_keys
convert_boolean_attributes!(html_options, %w( disabled ))
method_tag = ''
if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
end
form_method = method.to_s == 'get' ? 'get' : 'post'
remote = html_options.delete('remote')
request_token_tag = ''
if form_method == 'post' && protect_against_forgery?
request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
end
url = options.is_a?(String) ? options : self.url_for(options)
name ||= url
html_options = convert_options_to_data_attributes(options, html_options)
html_options.merge!("type" => "submit", "value" => name)
("<form method='#{form_method}' action='#{html_escape(url)}' #{"data-remote='true'" if remote} class='button_to'><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
end
end
end
end
I think the only way is to gsub(/"/, "'") but this is really ugly

Rails Button, remote_function. Possible without Ajax?

I want to create a very simple search partial. It has a text box, to query, and search db. Can I create a remote_function call without using AJAX or JS? Can I keep it entirely "Rails-ee"?
<%= text_field_tag "search_term",'', :size => 10 %>
<%= button "search", :onclick => remote_function( :url => {:action => :fill_in_lots },
:with => "search_term" ) %>
This isn't a problem, you need to use a technique called formal link. Instead of button you put a from with submit button. Below is a code of helper I use for this:
def formal_link_to(*args, &block)
options = html_options = name = nil
if block_given?
options = args.first
html_options = args.second
name = capture(&block)
else
name = args.first
options = args.second || {}
html_options = args.third
end
method = html_options.delete(:method) || "POST"
method = method.to_s.upcase
url = url_for(options)
html = "<form class=\"formal-link\" action=\"#{url}\" method=\"post\">"
html += "<input type=\"hidden\" value=\"#{form_authenticity_token}\" name=\"authenticity_token\" />"
html += "<input type=\"hidden\" value=\"#{method}\" name=\"_method\" />"
html += link_to(name, "#", html_options)
html += "</form>"
if block_given?
concat(html)
else
return html
end
end
You use this helper like a normal link_to, but you can pass extra options :method in second hash. Example:
<%= formal_link_to "Fill in lots", { :action => "fill_in_lots" }, { :method => :post } -%>
Remarks:
1. This of course will make the full page reload, but it is inevitable without using JavaScript.
2. I assumed action fill_in_lots is exposed to POST request. In case of GET you can use normal link_to helper.

Resources