Rails "param is missing or the value is empty" error - ruby-on-rails

I have this link_to helper passing an :email_sequence instance and an extra :set_active params.
I then try to update the :email_sequence instance in the controller using strong params but I'm getting an error saying:
param is missing or the value is empty: email_sequence
link_to:
<%= link_to "Activate", admin_email_sequence_path(base_email.email_sequence, set_active: :true), method: :patch %>
Controller:
class Admin::EmailSequencesController < AdminController
before_action :set_email_sequence
def update
if #email_sequence.update(active: email_sequence_params[:set_active])
flash[:success] = "Sequence updated succesfully"
redirect_to_forwarder_or(params[:forwarding_uri], admin_account_emails_path)
end
end
private
def set_email_sequence
#email_sequence = current_account.email_sequences.find(params[:id])
end
def email_sequence_params
params.require(:email_sequence).permit(:set_active)
end
end
This is what gets sent in the params:
{"_method"=>"patch", "authenticity_token"=>"[FILTERED]", "set_active"=>"false", "id"=>"1"}
Can anybody tell me what am I doing wrong?

By params.require(:email_sequence).permit(:set_active) you expect parameters to be { email_sequence: {set_active: "ANY SCALAR VALUE HERE"} } but you pass only set_active you can fix it by permitting the only one parameter
params.permit(:set_active)

You don't need strong parameters here in the first place. Contrary to popular belief strong parameters does not magically sanitize the parameters. It just prevent mass assignment vulnerabilities by requiring that you whitelist the parameters when passing a hash of parameters. Since you are only using a single parameter there is no mass assignment vulnerability:
class Admin::EmailSequencesController < AdminController
before_action :set_email_sequence
def update
if #email_sequence.update(active: params[:set_active])
flash[:success] = "Sequence updated succesfully"
redirect_to_forwarder_or(params[:forwarding_uri], admin_account_emails_path)
else
# provide an error response!
end
end
private
def set_email_sequence
#email_sequence = current_account.email_sequences.find(params[:id])
end
end
If you later want to use multiple parameters nested in a hash the use of link_to is pretty questionable even if it can be done.
<%= link_to "Activate",
admin_email_sequence_path(
base_email.email_sequence,
"email_sequence[set_active]" => true,
"email_sequence[foo]" => "bar"
),
method: :patch
%>
Use button_to or form_with/form_for to create a form element and style the button to look the way you want instead as this places the parameters in request body instead of the query string.

Related

Rails 5: Merging parameters in view - where to permit them?

what is the new way in Rails 5 to merge actual page query strings to link with a new one?
Let's assume I have page http://localhost:3000?type=a and I want to add another query param to the link on page:
<%= link_to root_path(params.merge(tech: "b")) do %>
but I get: unable to convert unpermitted parameters to hash.
Where I should permit the params?
I tried to do it in before_action filter, but it seems to be too late.
Thanks
EDIT:
My controller:
class HomeController < ApplicationController
before_action :permit_params
...
private
def permit_params
params.permit(:tech, :type)
end
end
You just need to whitelist the params you want to merge with
<%= link_to 'Home', root_path(params.permit(:type, :tech).merge(tech: 'b')) %>
and get /?tech=b&type=a. If you really want all parameters, you can use permit!
<% params.permit! %>
<%= link_to 'Home', root_path(params.merge(tech: 'b')) %>
which will give you /?action=index&controller=home&tech=b&type=a, which while those keys don't seem to be messing anything up, is very likely not desired (and controller and action will be overridden and not passed into your controller action). NOTE: The controller/action are set that way because I'm on my HomeController index action, not because that's what root_path is pointing to
I just don't think I can recommend doing this, however (seems iffy, imo)...stick with whitelisting.

Access params hash from other action in same controller wihtout reset

class UsersController < ApplicationController
def newpass
#somevariable = {
"a-var" => params[:token_id],
"b-var" => params[:client_id],
"c-var" => params[:user_id]}
end
def setpass
#I need to access this hash values without params getting reset
end
end
I have an action newpass with a corresponding view which calls setpass. I access URL's params in newpass action. I want to use these param values in setpass but the values get reset.
Using a link:
You need to send those values when your request hits setpass action in UsersController. You can create a button, or a link_to to send along those params values. Here is how:
<%= link_to "Go to", setpass_path(token_id: params[:token_id]) %>
You can pass params in _path method, and you will receive all those values inside the respective method in controller.
Using a form:
If you are using a form, and you would like to send those values along, hidden_field_tag is the best option here. You can do something like this:
<%= hidden_field_tag :token_id, params[:token_id] # this line must exist inside your form element %>

RoR: form_for :symbol vs #object do the odd way

I'm following the Getting Started with Rails tutorial.
When generate the view for create new article controller, i use:
Case 1: :article
<%= form_for :article do |f| %>
and get the error No route matches [POST] "/articles/new"
I thought that it should be [POST] "/articles
Case 2 I change to: #artical
<%= form_for #article do |f| %>
and it's OK.
Note that in case 1, the submit button's text is: Save article, and that is Create article in case 2
It does the opposite way with This SOF answer.
It's too ambiguous to me!!! So could somebody help to me to explain it?
This is my source code on github
Short answer
This is how Rails work, so basically, you should pass an object to form_for.
Long answer
After looking around the source code, here what is happening.
When you pass an object to form_for, the action will be computed with:
url_for(polymorphic_path(#object))
which will give /articles if the object is an article not persisted, or /articles/:id if the object is a persisted article.
When you pass a string or a symbol, the action results in url_for called
with an empty hash, resulting in the current path, in your case /articles/new. Note that if you show the form from, let's say /articles/custom, the path will be /articles/custom.
For the button, if you pass an object, the submit input value will be either
I18n.t('helpers.submit.create') or I18n.t('helpers.submit.update') depending on either your model is persisted or not. However, if you pass a string or a symbol, the value will be I18n.t('helpers.submit.submit').
Here are the relevant line of codes from Rails source.
# actionview/lib/action_view/helpers/form_helper.rb#L422
def form_for(record, options = {}, &block)
# .....
when String, Symbol
object_name = record
object = nil
else
object = record.is_a?(Array) ? record.last : record
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
object_name = options[:as] || model_name_from_record_or_class(object).param_key
apply_form_for_options!(record, object, options)
end
# .....
html_options = html_options_for_form(options[:url] || {}, html_options)
form_tag_with_body(html_options, output)
end
# actionview/lib/action_view/helpers/form_helper.rb#L451
def apply_form_for_options!(record, object, options) #:nodoc:
# ....
options[:url] ||= if options.key?(:format)
polymorphic_path(record, format: options.delete(:format))
else
polymorphic_path(record, {})
end
end
# actionview/lib/action_view/helpers/form_tag_helper.rb#L840
def html_options_for_form(url_for_options, options)
options.stringify_keys.tap do |html_options|
# ....
html_options["action"] = url_for(url_for_options)
# ....
end
end
# actionview/lib/action_view/helpers/form_helper.rb#L1873
def submit_default_value
object = convert_to_model(#object)
key = object ? (object.persisted? ? :update : :create) : :submit
# .....
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
defaults << "#{key.to_s.humanize} #{model}"
I18n.t(defaults.shift, model: model, default: defaults)
end
# locale/en.yml#L142
helpers:
select:
prompt: Please select
submit:
create: Create %{model}
submit: Save %{model}
update: Update %{model}
For the action, you can see that apply_form_for_options! is not called when you pass a string, so options[:url] remains nil if it was. When you pass an object, apply_form_for_options! set options[:url] to polymorphic_path(#object) if it was not set. This is then passed to html_options_for_form, where the action is set applying url_for to the value.
For the submit value, you can see how the key is taken depending on whether the form target is an object or not, and then translated.
form_for :article created a form, but default url to submit is /articles/new
action new of articles used to display the form to submit, not handle creating a new article
=> resources :articles will map it with GET method
you can specified an url for that form, or change the request method (not recommended)
<%= form_for :article, url: articles_path do |f| %>

Losing parameters in production?

Or, that is what seems to be happening. I'm getting errors in my production environment that i can't recreate in development. Basically, i'm trying to associate a Booker to a Booking. If the Booker doesn't exist I'd like to invite him/her.
# GET /bookings/new
def new
#booking = Booking.new
authorize #booking
#booking.venue = Venue.new
#booker = User.new
end
Relevant create code;
def create
#booking = Booking.new(booking_params)
authorize #booking
booker_found = set_booker
And the set_booker private method
def set_booker
logger.info "booker: #{booking_params.inspect}"
# set existing user as booker or prepare inviting a new user
booker_found = false
#booker = User.find_by_email(booking_params[:user][:email])
etc.
The last line is where I get errors in production, because booking_params[:user] does not exist. I tried resetting my database in the development ENV and the code works fine. Yet in production I will always get NoMethodError (undefined method '[]' for nil:NilClass)
This is my relevant form code;
<%= simple_form_for(#booking, :wrapper => :bootstrap3 ) do |f| %>
<%= f.simple_fields_for #booker do |user_form| %>
<%= user_form.input :email, label: "Booker e-mail" %>
<% end %>
This is what logger shows in development;
Parameters: {"utf8"=>"✓", "authenticity_token"=>"lala", "booking"=>{"name"=>"Mah Booking", "date"=>"02-10-2014", "venue_name"=>"Meeeeeeeeeeee", "venue_id"=>"", "user"=>{"email"=>"booker#boekkoek.nl"}, "artist_fee"=>"0.00"}, "commit"=>"Create Booking", "locale"=>"en"}
booker: {"date"=>"02-10-2014", "name"=>"Mah Booking", "artist_fee"=>"0.00", "venue_id"=>"", "venue_name"=>"Meeeeeeeeeeee", "user"=>{"email"=>"booker#boekkoek.nl"}}
And this is from my production.log
Parameters: {"utf8"=>"✓", "authenticity_token"=>"yada", "booking"=>{"name"=>"tttt", "date"=>"02-10-2014", "venue_name"=>"meee", "venue_id"=>"", "user"=>{"email"=>"info#blabl.nl"}, "artist_fee"=>"0.00"}, "commit"=>"Create Booking", "locale"=>"en"}
booker: {"name"=>"tttt", "date"=>"02-10-2014", "artist_fee"=>"0.00", "venue_id"=>""}
I have no idea why the order is different, also, it seems to but "cutting off" after the venue_id, which is obviously causing the error. Has anyone seen behavior like this before?
Edit:
Here's my booking_params private method
def booking_params
params.require(:booking).permit(*policy(#booking || Booking).permitted_attributes)
end
And the pundit policy;
def permitted_attributes
[:status,
:date,
:name,
:get_in_time_string,
:soundcheck_time_string,
:dinner_time_string,
:show_time_string,
:show_time_end_string,
:artist_fee,
:venue_id,
:venue_name,
:act_ids => [],
user: [ :id, :email ]
]
end
Like I said, same code works fine in development. The only way I can sort of reproduce the problem is by removing the user params from my permitted_attributes method, but then I actually get an "unpermitted parameters" error. What would be the right way to define the permitted attributes for "user"? Totally lost on this one.
I suspect your strong parameters are incorrect/are missing the nested user. If you post them I can fix that side of it, but an easy temporary fix is to simply use
params[:user][:email]
i.e access the parameters directly. Going back to the issue, have a look here for using nested parameters in the strong parameters:
Rails 4 - Strong Parameters - Nested Objects
Never found out what the problem really was. Not using Pundit for the strong params fixed it though.

Rails Button_to not passing model param

I have the following in my products index page:
<%= button_to "Add", user_order_orderitems_path(user_id: current_user.id, item_id: x.id, order_id: current_user.group.current_order.id), class: "btn btn-mini" %>
Which I can see from the logs is being picked up by my Orderitems#create action in my controller ok. This looks like:
def create
#orderitem = Orderitem.new(orderitem_params)
if #orderitem.save
redirect_to items_path
else
redirect_to items_path
end
end
private
def orderitem_params
params.require(:orderitem).permit(:user_id, :order_id, :item_id)
end
end
The params specified in the button_to call are being created and are showing up in the logs as:
Started POST "/users/1/orders/1/orderitems?item_id=2264" for 127.0.0.1 at 2013-07-03 22:45:24 +0100
Processing by OrderitemsController#create as HTML
Parameters: {"authenticity_token"=>"blah=", "item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}
Fnally - the problem - my strong_params method can't process these params as the three params I care about are not nested in a hash with 'Orderitem's as a key. I would expect, for my create action to work, I need something like:
Parameters: {"authenticity_token"=>"blah=", "orderitems"=>{"item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}}
but I can't for the life of me work out how, with button_to I am able to do this - I have tried a form_for, but this also failed to work. Banging my head on a brick wall for a couple of days on this one...So, how can post my three ids to my OrderItemsController create action from an index view for Products but bypassing any form_for or new actions? Is it possible?
Please let me know if I am approaching this scenario (adding an item to a basket) in completely the wrong way.
This way you can treat a standard hash as one supported by strong parameters
raw_parameters = {"authenticity_token"=>"blah=", "item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}
parameters = ActionController::Parameters.new(raw_parameters)
parameters.permit(:user_id, :order_id, :item_id)

Resources