Ruby on Rails: is using params.require() important? - ruby-on-rails

I'm trying to create a RESTful API for a very simple web app I made using Ruby on Rails. Specifically, I'm trying to implement the POST /users.json to create a new user.
The "parsing the response to JSON" bit is handled by the scaffolding. The issue comes when trying to use the strong parameters method scaffolded.
I make a POST request using the Postman Chrome extension to:
# POST /users
# POST /users.json
def create
user_params[:karma] = 1
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
So user_params is called, and it requires a user (note that this method was generated by the scaffolding):
def user_params
params.require(:user).permit(:name, :karma, :about)
end
I realised I can work this around by simply "not requiring" the user in params:
def user_params
params.permit(:name, :karma, :about)
end
But is this safe or appropriate? Is there a more correct way?
And why is the user even required in the first place, if that's exactly what I intend to create?

params.require(:user).permit(:name, :karma, :about)
Says that the params hash MUST contain a key called user and checks that associated value only contains the named keys. Aside from this security check, it returns pretty much what params[:user]. This requires that the params hash is of the form
{
:user => {
:name => "Bob",
:about => "Professional builder",
:karma => "10"
}
}
You'll get a parameters hash like this if the field names in the form / http request are user[name], user[about], which is exactly what you'll get if you use the rails form helpers
On the other hand it sounds like the parameters hash your sending is
{
:name => "Bob",
:about => "Professional builder",
:karma => "10"
}
because in your request the field names are name, about, karma.
The problem with doing params.permit(:name, :karma, :about) is that it stops you from ever passing other parameters to the action because the parameter checker won't allow them (and if you did allow them then User.new would complain).

Related

Rails default parems for id set to new?

I am getting a 404 error on my RoR app and i found that it was because one of the method in the controller, which should only triggers when the record is not new, triggered when the record is new.
I do by that checking if the id of that record nil in my controller.
before_action :create_record, if: proc { not params[:id].nil? }
I was confused by it was triggered so i went head and checked my front-end network, which show following:
Request
Parameters:
{"format"=>"json", "id"=>"new"} <----Set to new by default
My completely controller looks like this:
class Api::MyController < ApplicationController
before_action :create_recotrd, if: proc { not params[:id].nil? }
def show
end
def index
#my_model = MyModel.all
end
def create
#my_model = MyModel.new(my_model_params)
respond_to do |format|
if #my_model.save
format.json { render json: #my_model, status: :created}
else
format.json { render json: #my_model.errors, status: :unprocessable_entity}
end
end
end
def update
#my_model = MyModel.update
end
private
def create_record
#my_model = MyModel.find(params[:id])
end
def my_model_params
params.require(:my_model).permit(
:city,
:state,
:county,
:zip,
:telephone,
:email,
)
end
end
I cant seem to find out why the id in the parameters is set to "new" instead of "nil".
I tried in the console by doing MyModel.new, the default id was nil, but then when i do the GET request, the id was set to "new"
This is a really weird approach to set a new record. I think the problem lies in your routes. You are probably trying to access yoursite.com/your_model/new and your routes are configured to look for
get "your_model/:id" => "your_controller#show"
You are probably missing
get "your_model/new" => "your_controller#new"
So when you try to visit your_model/new the routes map the "new" as the :id param in your url.
I don't see a new action in your controller as well. You should read up on basic resource set up for rails here: http://guides.rubyonrails.org/routing.html.

Rails redirect if validation fails

In a Rails 3.2 app, I have a validation for an attachment type.
Attachment model:
class Attachment < ActiveRecord::Base
validates_presence_of :name
validates_attachment_presence :attach, :message => "No file selected"
validate :check_type
def check_type
if self.costproject_id != nil
if self.attach_content_type != 'application/pdf'
self.errors.add(:pdf, " ONLY")
return false
end
end
end
But, the return false sends me to this URL:
http://localhost:3000/attachments
I want it to go back to the previous input screen:
http://localhost:3000/attachments/new?costproject_id=2
How do I accomplish that?
Thanks!!
UPDATE1
Perhaps the redirect has to take place in the controller?
format.html { render action: "new" }
Attachment controller:
# POST /attachments
# POST /attachments.json
def create
#attachment = Attachment.new(params[:attachment])
respond_to do |format|
if #attachment.save
format.html { redirect_to session.delete(:return_to), notice: 'Attachment was successfully created.' }
format.json { render json: #attachment, status: :created, location: #attachment }
else
format.html { render action: "new" }
format.json { render json: #attachment.errors, status: :unprocessable_entity }
end
end
end
I changed this line:
format.html { render action: "new" }
To:
format.html { redirect_to request.referer }
And now it goes back to where I want. But, I've lost the errors - they don't display.
To help you understand what's going on here. When you go to /attachments/new you are rendering a form. When you press submit, you are sending a POST request to /attachments, which invokes the create action.
You're create action appears to be solid and idomatic. However when you render action: "new" in the case of an error, it's not a full redirect, it's rendering the form in the context of the current action.
Normally this is fine, because idomatic rails would have you building a single, very similar, model object in both new and create, and the form for helper would render that object. However your new action is creating all kinds of objects based on a large assortment of query parameters, which I'm guessing is why you are seeing behavior you don't like.
I expect your final solution will involve bringing all those parameters into Attachment in some way, if they don't need to be saved to the database, you can make attr_accessors on Attachment
# Model
class Attachment < ActiveRecord::Base
attr_accessor :worequest_id, :workorder_id # etc
end
# View
<%= form_for #attachment do |f| %>
<%= f.hidden :worequest_id %>
<% end %>
Approaching it this way, your post request params will look like
{
attachment:
{
worequest_id: 1,
# etc
}
}
And you would also need to rework your query params to nest the inidividual ids inside of an attachment
/attachments/new?[attachment][worequest_id]=1
This way you could build attachment from params in both actions:
Attachment.new(params[:attachment])
And now your current create action should more or less work as expected, because now it's idomatic rails.
You still aren't going to get the new action with the same query params, but since you are taking those params and filling them in hidden fields on the form, they won't be lost when you try and fail to create. In any case, unless you do something to persist the values between requests, the POST to /attachments is going to wipe out the ery params.
Try this.
Replace
return false
With
redirect_to request.referrer || root_url
Note: root_url here is a catchall. Also this is Rails 4, I do not know if it also applies to Rails 3. Worth a try, though.
Debug ideas
First confirm a simple redirect_to root_url (or whatever name you use for your root) works in your controller
redirect_to root_url
Then, once redirect_to confirmed working, focus on getting the REST interface "request." information. There's a Rails 3 discussion here which may help you.
How to get request referer path?

Json post hasMany to Rails 4 fails with Unpermitted parameters

I'm trying to post a json message to a Rails 4.1.1 server, but is failing due to unpermitted parameters. I'm using Mongoid as well and submitting via POST and content type of application/json.
Here's my domain:
class Sale
include Mongoid::Document
include Mongoid::Timestamps
field :internalId, type: String
embeds_many :saleItems
accepts_nested_attributes_for :saleItems
end
Here's the controller code:
def sale_params
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems)
end
# POST /sales
# POST /sales.json
def create
#sale = Sale.new(sale_params)
#####################
puts "parent: "
puts #sale.inspect
puts "collections: "
#sale.saleItems.each do |si|
puts "collection here"
puts si.inspect
end
respond_to do |format|
if #sale.save
format.html { redirect_to #sale, notice: 'Sale was successfully created.' }
format.json { render action: 'show', status: :created, location: #sale }
else
format.html { render action: 'new' }
format.json { render json: #sale.errors, status: :unprocessable_entity }
end
end
end
I've successfully saved the collection saleItems fine outside of rails and just using a ruby script with the collection successfully saving via Mongoid.
Here's the JSON content:
{
"sale" : {
"internalId":"77E26804-03CC-4CA9-9184-181C2D8CB02A"
"saleItems" : [
{
"inventoryName" : "inv 1"
},
{
"inventoryName" : "inv 2"
}
]
}
}
Wow I figured it out. It needs to have the {} around the collection of items.
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType,
{:saleItems => [:inventoryName, :internalIdForSeller]})
Here's the post I found to help fix the issue.
Rails 4 - Strong Parameters - Nested Objects
I think the issue is the strong parameters being permitted.
You have
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems)
But salesItems is another class. You need something like
params.require(:sale).permit(:internalId, :parentInternalId, :externalId, :internalIdForStore, :internalIdForCustomer, :sendReceiptType, :saleItems_attributes => [:inventoryName, :anotherAttribute, :stillAnotherAttribute])
Kindly edit your answer the tell that what params you are getting in.
The things is params is data structure its a request object. And permit is a method which allow to permit the specific parameter .
So put the debugger and easily you will recognize what the problem is.

Rails 4: How do I handle a submitted form where nothing was selected?

Sorry if the title is a little confusing. I have a form for an Item with the field name. There's a text field where the user can input a name and submit it. But if the user doesn't type in anything and hits submit, Rails gives me a param not found: item error, and I'm not sure who to get around this.
items_controller.rb
def new
#item = Item.new()
respond_to do |format|
format.html
format.json { render json: #item }
end
end
def create
#item = Item.new(item_params)
respond_to do |format|
if #item.save
format.html { redirect_to items_path }
format.json { render json: #item, status: :created, location: #item }
else
format.html { render action: 'new', :notice => "Input a name." }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
private
def item_params
params.require(:item).permit(:name)
end
app/views/items/new.html.haml
= form_for #item do |f|
= f.label :name
= f.text_field :name
= f.submit "Submit"
The params.require(:item) part is what is causing the error. What the convention for handling the error when params[:item] isn't present?
It's late for an answer but i'll still write it for someone else. As stated in rails guides you need to use fetch instead of require in strong parameters, by using fetch you can provide a default value if nothing is passed as input. Something like:
params.fetch(:resource, {})
Update:
Scaffolded rails4 app:
https://github.com/szines/item_17751377
It works if a user keep name field empty when create a new item...
Looks, it works without problem...
Development.log shows that parameters would be the following if user keep a field empty:
"item"=>{"name"=>""}
There is always something in the hash...
As Mike Li already mentioned in a comment, something wrong... because shouldn't be empty this params[:item]...
You can check if something nil, with .nil? , in this case params[:item].nil? will be true if it is nil. Or you can use .present? as sytycs already wrote.
Previous answer:
If you have situation when :item is empty, you should just use params[:item] without require.
def item_params
params[:item].permit(:name)
end
More information about require in strong_parameters.rb source code:
# Ensures that a parameter is present. If it's present, returns
# the parameter at the given +key+, otherwise raises an
# <tt>ActionController::ParameterMissing</tt> error.
#
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
# # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: nil).require(:person)
# # => ActionController::ParameterMissing: param not found: person
#
# ActionController::Parameters.new(person: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
def require(key)
self[key].presence || raise(ParameterMissing.new(key))
end
I personally have not switched to strong parameters so I'm not sure how one should handle something like:
params.require(:item).permit(:name)
but you can always check for item presence with something like:
if params[:item].present?
…
end

Validation fails if redirect after save is anywhere other than the saved object

I am trying to make it so that when a user signs up (i.e: a new user is created) it will redirect them to the tutorial. However, when a user signs up it will give an error message saying that the username and email must be unique (even if they are) and render the 'new' page again.
This works fine if I redirect to #user instead.
This is my controller:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
login(#user)
format.html { redirect_to "static/tutorial", success: 'Congratulations on starting your journey!' }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
And these are the relevant lines in User.rb:
validates_confirmation_of :plain_password
validates_presence_of :name, :username, :email
validates_presence_of :plain_password, :on => :create
validates_uniqueness_of :email, :username
When I see that I am quite scared
validates_presence_of :plain_password, :on => :create
Are you saving unencrypted password in your database ?
Regarding your issue you may want to consider using respond_to / respond_with
class UsersController < ApplicationController
respond_to :html, :json
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
# set sessions
end
respond_with #user, location: 'static/tutorial'
end
end
Read this blog post http://bendyworks.com/geekville/tutorials/2012/6/respond-with-an-explanation
I figured it out - partly.
I needed to add a route to the tutorial in my routes file:
match "tutorial" => "static#tutorial"
And then redirect to that instead of a string:
format.html { redirect_to tutorial_path, success: 'Congratulations on starting your journey!' }
There is probably a full explanation to the theory behind this but I'll leave that for someone else to answer. This is how I solved it.

Resources