Rails 4 require and permit for update PUT request - ruby-on-rails

Currently I have a rails controller with an update method:
def update
respond_to do |format|
if #segment.update(segment_params)
format.html { redirect_to #segment, notice: 'Segment was successfully updated.' }
else
format.json { render json: #segment.errors, status: :unprocessable_entity }
end
end
end
The segment_params method:
def segment_params
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags)
end
I'm unsure as to how to make the actual request to be accepted by rails. I'm using Postman (Google Chrome) to make the request. I'm making a PUT request to localhost:3000/segments/2677.json with the segment info as JSON. Is this the correct way of doing it? Or would this be a URL param? Not quite sure how to format it as a URL param.

You should not make changes to server data via a GET request. For the reasoning behind this see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html section 9.1.1 - bascally GET and HEAD request should always be safe.
The method you are using right now is actually a good one. My only suggeston - if you really want to follow the HTTP spec would be to use PATCH instead of PUT for partial updates. The semantics for PATCH are essentially that rather than being idempotent like PUT it does delta updates like what you are doing. See : http://restful-api-design.readthedocs.org/en/latest/methods.html for more info on the topic.

You can check the server log after the PUT and you'll see something like this:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"zzzz", "segment"=>{"name"=>"My Segment Name", "description"=>"Something", "f_30_estimated_reach"=> "I dont know", "tags"=> "tag1 tag2 tag3" }
The key "segment" must be present with your data as value. I assume that "tags" isn't an array, or represent an associated model.
If not, let say 'tags is an array', you must call permit this way:
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags [])
If tags is an associated model and you update the 'name' field:
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, tags: {:name})
I hope it helps.

In Rails 4, the recommended HTTP action for an update action is the PATCH method. If you run rake routes in your Terminal, you'll see that the update action is linked to the PATCH action by default (assuming that you're using the resources method.
Rails is very opinionated about how you pass parameters in, and uses the concept of "strong params" to sanitize and validate your parameters as they're passed in from your form to your controller. This is a good primer on the topic (https://github.com/rails/strong_parameters), but I'm assuming you're trying to pass tags in as a collection (array in this case).
def segment_params
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags => [])
end
As Alejandro Babio said, if you're using an associated model then it you need to permit tags along with their model attributes that you've defined in your database migration file for the tags table. For example:
def segment_params
params.require(:segment).permit(:name, :description, :f_30_day_estimated_reach, :tags => {:name, :created_at, :updated_at})
end
It also might be a good idea to see what your params actually looks like by calling segment_params.inspect before #segment.update(segment_params). If :tags does not show up in the inspect method output, then it's not being passed into your strong params correctly.

Thanks for all the suggestions. This was an oversight on my part. I thought POSTMAN would specify the Content-Type HTTP Header as application/json because I was posting JSON.

Related

change params value before patch

I have a texfield in a form where a foreign key's value is displayed. now I want to update the value and save it to the DB. Here is the code:
for the form I use:
f.text_field :port, :value =>#entry.port.number, class:"form-
control", placeholder:"Port"
in the controller I am using a param method:
def entry_params
params.require(:entry).permit(:description,:rule_id, :protocol_id,
:url, :port)
end
the update method looks like this:
def update
#entry.url.name = params[:entry][:url]
#entry.port.number = params[:entry][:port]
if #entry.update(entry_params)
flash[:success] = "Entry was successfully updated!"
redirect_to entry_path(#entry)
else
render 'edit'
end
end
but if I want try to save it, it shows this error:
Url(#70247237379440) expected, got "www.drive.google.com" which is an
instance of String(#70247218839280)
Now my question is, (I'm relativ new to rails) how can I fix this? I know that it expect an object as a parameter but if I change the param like this:
params[:url] = #entry.url
it doesn't work.
There are two approaches I can think of here, depending on exactly what you're looking to achieve.
If you're looking to assign an entry a new url based on a string params, you can use something like the following:
#entry.url = Url.find_by_name(params[:entry][:url])
Depending on your models' setup, if there's a url_id column on your entries, you might be better off using a select field on this, passing the URL's name and id to the options. If you can add this info to your question, I can update / rule this out as needed.
If you're just looking to update the URL via the entry's form, you'd be best looking at using accepts_nested_attributes_for.
Doing this, you can directly update the associated objects through the parent's form. If this sounds like the right approach for you, let me know and I can provide more detail :)
Edit: as per your comment, is sounds like this is the option you're after. So, you'd need something like the following:
entry.rb
accepts_nested_attributes_for :url
In the form:
...
f.nested_fields_for :url do |url_fields|
url_fields.text_field :name
end
...
And you'll need to update the params in your controller to accept these nested fields. I can't remember the exact for they take, but it's something like:
def new / edit # whichever you're in
...
#entry.build_url unless #entry.url.present?
end
def entry_params
params.require(:entry).permit(:description,:rule_id, :protocol_id,
:port, url_attributes: [:name])
end
(It might be you need an empty array or a hash for url_attributes.)
That'll then directly update the associated url. FYI if you've not got an associated URL, you'll need to build one using #entry.build_url in the controller.
Hope this helps - if you've any questions / details to help clarify, let me know and I'll update as needed.

One hash with params populate two objects

I'have an json api where user can update a post and related details to that post. To simply the process the user just fill one hash and we are no exposing our architecture. The stuff I did here work but I would like to know if their is a better way. For me I don't know why but it smells...
def update
params['blog_post'] = params.delete 'post'
#blog_post.update_attributes blog_post_params
update_blog_post_groups if params['blog_post']['group_ids']
params['blog_post_details'] = params.delete 'blog_post'
#blog_post.blog_post_details.update_attributes blog_post_details_params
render :blog_post, status: 200
end
blog_post_params and blog_post_details_params are strong_parameter methods.

Ruby on Rails - PUT Method Creates Extra Params Entry

So I observed some weird behaviour while implementing an endpoint for a RESTful API I am creating for a mobile client. I am using a PUT method to update an attribute on the User model. I send the user's id as a URL parameter and the value to update inside a JSON object. Everything seems to work just fine but when I check the parameters via the rails logs I noticed something strange. For some reason there is an extra parameter being sent to the backend that I can't seem to explain. Here are the logs I am seeing when I call this endpoint from the mobile client:
Parameters: {"enable_security"=>true, "id"=>"7d7fec98-afba-4ca9-a102-d5d71e13f6ce", "user"=>{}}
As can be seen above an additional "user"=>{} is appended to the list of parameter entries. I see this when I print out the params object as well. I can't seem to explain where this is coming from. I also checked the mobile client just to be safe and there is no where in code where I send a parameter with a key user. This is very puzzling to me and makes me think I am missing something fairly simple. Why is there an empty object with the user key being sent to the backend RESTful API?
Update to Provide More Information
Here is the code that gets called when the user hits the endpoint that updates the user User model:
#PUT /:id/user/update_security_settings
def update_security_settings
#user = User.find_by_id(params[:id])
#user.advanced_security_enabled = params[:enable_security]
respond_to do |format|
if #user.save
response = {:status => "200", :message => "User's security settings updated."}
format.json { render json: response, status: :ok }
else
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
Update in Response to User's Comments
Here are the routes that pertain to the user_controller, the view controller that defines all endpoints that deal with creating and updating the User model.
post '/user/upload_profile', to: 'user#upload_profile'
get '/:id/user', to: 'user#find_user'
put '/:id/user/update_security_settings', to: 'user#update_security_settings'
resources :user, :defaults => { :format => 'json' }
Does this comment really mirror your actual route?
#PUT /:id/user/update_security_settings
I'd expect it to be /user/:id/update_security_settings instead.
Can you show us your config/routes.rb - My wild guess is that your routes are somehow configured to expect an actual nested user param, which you don't send (of course) and therefor appears empty in the logs.
Update:
Some of your routes are unusual. You actually don't need the find_user route as it should be covered under resources :user as show action (provided you defined a show method in your controller, which is the default way to retrieve a single resource item; so no need for find_user)
For custom routes like your update_security_settings action I'd suggest to stick to the default pattern, like resource/:id/actionand nesting it in the default resourceful route. Putting the id before the resource is very unusual, confusing and may actually be related to your issue (thoguh I#m not sure about that). Try cleaning up your routes.rb liek this:
# notice that resources expects the plural form :users
resources :users do
member do
patch :update_security_settings
post :upload_profile
# any other custom routes
end
end
This will result in routes like GET /users (index), GET /users/1 (show) and PATCH /users/1/update_security_settings.
More on routing an be found here: Rails Guides | Routing From The Outside In
Please check if the changes above remove your empty user param.
Check your configuration in
config/initializers/wrap_parameters.rb
wrap_parameters format: [] this list should not contain json then it will wrap the parameters to the root of you controller for all the json request. Refer api docs

Sanitizing input

this may be a really n00b question, but if your list of params contains a bunch of stuff that isn't an attribute accessible, ie
params = {"controller"=>"api1/users", "action"=>"create"}
what is the best way to "sanitize" your params so that they only contain the accessible attributes. The current way that I thought of currently is to do :
User._accessible_attributes[:default].entries
that gives me a list of accessible attributes and then only pass those params:
["", "email", "password", "fb_token", "fb_id", "fb_name", "first_name", "last_name", "gender"
Another possible way is to have this:
def clean_params #ANTIPATTERN
params.delete(:controller)
params.delete(:action)
end
but this also feels like an antipattern...
I know that you're supposed to do something like params[:user] to get only the accessible params, but because this is an API, it would be nice to be able to pass things just in the url.
Thanks!
The Rails parameter wrapper will do this for you automatically. That is, it will accept parameters at the top level and group them under, for example, :user for your convenience, filtering out any that are not accessible to the User model. Internally it uses accessible_attributes, similar to what you've done. People who use your API will not need to group attributes -- rails will do it before it hands the params to your controller action.
By default it's turned on for JSON requests, but you can expand that by editing initializers/wrap_parameters.rb. Or you can adjust the behavior on a per-controller basis using the wrap_parameters method in your controller.
The rails scheme of parameter sanitizing is likely to change in 4.0, trending away from the model and toward the controller. You may want to watch development of the strong_parameters gem which could be a preview of things to come.
You could do it this way... This will only sense in the parameters you want to in the controller. credit: dhh's gist
class UserController < ApplicationController
respond_to :html
def create
respond_with User.create(user_params)
end
private
def user_params
params[:user].slice(:email, :first_name, :last_name)
end
end

How do I expose data in a JSON format through a web service using Rails?

Is there an easy way to return data to web service clients in JSON using Rails?
Rails resource gives a RESTful interface for your model. Let's see.
Model
class Contact < ActiveRecord::Base
...
end
Routes
map.resources :contacts
Controller
class ContactsController < ApplicationController
...
def show
#contact = Contact.find(params[:id]
respond_to do |format|
format.html
format.xml {render :xml => #contact}
format.js {render :json => #contact.json}
end
end
...
end
So this gives you an API interfaces without the need to define special methods to get the type of respond required
Eg.
/contacts/1 # Responds with regular html page
/contacts/1.xml # Responds with xml output of Contact.find(1) and its attributes
/contacts/1.js # Responds with json output of Contact.find(1) and its attributes
http://wiki.rubyonrails.org/rails/pages/HowtoGenerateJSON
Rails monkeypatches most things you'd care about to have a #to_json method.
Off the top of my head, you can do it for hashes, arrays, and ActiveRecord objects, which should cover about 95% of the use cases you might want. If you have your own custom objects, it's trivial to write your own to_json method for them, which can just jam data into a hash and then return the jsonized hash.
There is a plugin that does just this,
http://blog.labnotes.org/2007/12/11/json_request-handling-json-request-in-rails-20/
And from what I understand this functionality is already in Rails. But go see that blog post, there are code examples and explanations.
ActiveRecord also provides methods to interact with JSON. To create JSON out of an AR object, just call object.to_json. TO create an AR object out of JSON you should be able to create a new AR object and then call object.from_json.. as far as I understood, but this did not work for me.

Resources