I have a rails 4 application that uses postgresql. I also have a backbone.js application that pushes JSON to the rails 4 app.
Here's my controller:
def create
#product = Product.new(ActiveSupport::JSON.decode product_params)
respond_to do |format|
if #product.save
format.json { render action: 'show', status: :created, location: #product }
else
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
def product_params
params.require(:product).permit(:title, :data)
end
I'm trying to parse the JSON and insert the product, but on insert, I'm getting the error:
TypeError (no implicit conversion of ActionController::Parameters into String):
Thanks for all help!
Your mileage may vary, but I fixed a smilar problem by a bandaid code like this:
hash = product_params
hash = JSON.parse(hash) if hash.is_a?(String)
#product = Product.new(hash)
The particular problem I had was that if I was just doing JSON.parse on the params that contained the object I wanted to create, I was getting this error while unit testing, but the code was working just fine when my web forms were submitting the data. Eventually, after losing 1 hour on logging all sorts of stupid things, I realized that my unit tests were somehow passing the request parameter in a "pure" form -- namely, the object was a Hash already, but when my webforms (or manual headless testing via cURL) did sumbit the data, the params were as you expect -- a string representation of a hash.
Using this small code snippet above is, of course, a bandaid, but it delivers.
Hope that helps.
Convert hash into JSON using to_json
The error is telling you that ActiveSupport::JSON.decode expects to be provided with a string, but is unable to coerce the argument you are providing it into a string. The argument provided to it here is "product_params" which returns a ActionController::Parameters (a loosely wrapped Hash).
If you are using "out of the box" style Backbone there is no need to decode what is being POSTed to that action. Just change the action to:
#product = Product.new(product_params)
The structure of your product_params method indicates that the action is expecting the data you are POSTing to look like this:
{
product: {
title: "Foo",
data: "bar"
}
}
and that your Product model has two attributes that will be populated by .new: title and data.
If you are explicitly encoding something into JSON on the client side you need to figure out what POST parameter it is being submitted as and the decode it on the server (again - there is almost certainly not a good reason to jump through hoops like that).
Related
Here's a simple controller update action:
def update
note = Note.find(params[:id])
if note.update(note_params)
render json: note.to_json
else
render json: {errors: note.errors}, status: :unprocessable_entity
end
end
This renders errors in the form
{"errors":{"title":["can't be blank"]}}
but I want it in the form of
{"errors":{"title":["Title can't be blank"]}}
Simply using {errors: note.errors.full_messages}
gives
{:errors=>["Title can't be blank"]} and misses the attribute keys.
The only way I can get it into the desired form seems to be a bit more involved:
full_messages_with_keys = note.errors.keys.inject({}) do |hash, key|
hash[key] = note.errors.full_messages_for(key)
hash
end
render json: {errors: full_messages_with_keys}, status: :unprocessable_entity
This works, but it seems odd that I have to do this since it seems to be a pretty common use case for doing validations on a SPA front-end. Is there a built-in method/more canonical way?
You can use ActiveModel::Errors#group_by_attribute to get a hash of errors per key:
person.errors.group_by_attribute
# => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}
From there is simply a matter of generating the full message from each ActiveModel::Error instance:
note.errors
.group_by_attribute
.transform_values { |errors| errors.map(&:full_message) }
Is there a built-in method/more canonical way?
Not really. A framework can't cover every possible need. It provides the tools needed to format the errors however you want.
However instead of repeating this all across your controllers this functionality can be pushed into the model layer, a serializer or even monkeypatched onto ActiveModel::Errors if you want to get crazy.
class ApplicationRecord < ActiveRecord::Base
def grouped_errors
errors.group_by_attribute
.transform_values { |errors| errors.map(&:full_message) }
end
end
I wonder if there is any explanation of the usage below under http://api.rubyonrails.org/ instead of http://guides.rubyonrails.org.
render json: #user
Although there is a page in Rails Guide mentioning this, it does not cover other available options like :include, for example:
render json: #user, include: { blog: { only: [:name, :permalink]} }
I can't believe such common API is hard to find in its official API document.
That's probably because it's using as_json method. Refer to:
https://apidock.com/rails/ActiveModel/Serializers/JSON/as_json
So, the render method isn't strictly related to as_json options. You should head to the link above.
Dug around a little and found the ActionController::Renderers documentation which, to add a new one states:
To create a renderer pass it a name and a block. The block takes two arguments, the first is the value paired with its key and the second is the remaining hash of options passed to render.
and then if you view it's source on GitHub you can see when they create the :json renderer, they do:
add :json do |json, options|
json = json.to_json(options) unless json.kind_of?(String)
...
end
So, Rails, is just calling to_json on "the value paired with it's key" (which is #user in render json: #user) passing in all the extras you passed to the render call, in this case the include.
So if you want to know what the include option is doing, you would need to check the #user.to_json method, which then calls ActiveSupport::JSON.encode which just kind of delegates to ActiveSupport::JSON::Encoding::JSONGemEncoder#encode which then just calls as_json on the object and turns it into a string.
I generated a new rails 5 --api --database=postgresql app the other day and only created one scaffold (Hero). I'm wondering how the strong parameters work in rails as I am seeing some odd behavior:
Controller looks like:
def create
hero = Hero.new(hero_params)
if hero.save
render json: hero, status: :created, location: hero
else
render json: hero.errors, status: :unprocessable_entity
end
end
My hero_params look like this:
def hero_params
params.require(:hero).permit(:name)
end
So I would assume that the client is required to submit a hash containing a "hero" key and it's allowed to have a "name" subkey that is allowed to be mass assigned when this controller action is called.
Meaning, the JSON should look like this:
{
"hero": {
"name": "test"
}
}
All is well but here is where I am seeing strange behavior. When the user submits the exact JSON as above, the parameters come in as:
Parameters: {"hero"=>{"name"=>"test"}}
Now if the user submits just:
{ "name": "test" }
It still creates a new resource and the parameters come in as:
Parameters: {"name"=>"test", "hero"=>{"name"=>"test"}}
Why are there two sets of parameters, one with the actual submitted data and one in the format of a hero object as if it was anticipating the mass assignment?
How come the require(:hero) doesn't raise an error when that key is not submitted? I assume the answer to this is because of what is automatically creating that second hash ("hero"=>{"name"=>"test"}} from question 1.
Any information on what I am missing here would be greatly appreciated, as this is barebones rails behavior out-of-the-box.
This behaviour comes from ActionController::ParamsWrapper:
Wraps the parameters hash into a nested hash. This will allow clients to submit requests without having to specify any root elements.
Rails applications have parameter wrapping activated by default for JSON requests. You can disable it globally by editing config/initializers/wrap_parameters.rb, or for individual controllers by including wrap_parameters false in the controller.
I need some help I have a controller with an action that queries two models.
Now I need to send both of them as json in order to be used on my angular views.
In the example bellow how should I send the "complex" and its "fields" in one json response?
Ex.
def complexes_and_fields
complex = Complex.find(params[:id])
search_params = {complex_id: complex._id}
fields = Field.where(search_params)
if !complex.nil?
render json: ???.to_json, status: :ok
else
render json: { error_description: 'no complex found' },status: :bad_request
end
An easy way to do this is to build a hash with your objects
complex = Complex.find(params[:id])
search_params = {complex_id: complex._id}
fields = Field.where(search_params)
render json: { complex: complex, fields: fields, search_params: search_params }, status: :ok
Another way would be to user a view such as some_view.json.erb where you render the objects as you are expecting it in your angular view. Also you can use can use ActiveModelSerializers, read on https://github.com/rails-api/active_model_serializers
Ideally what you will want to do is encapsulate this response into its object and make a single call in your controller that returns you the results
Without going into too much details something like this
results = MyComplexFieldsObj.response(params[:id])
render son: results, status: :ok
This is an extremely common requirement in Rails applications. This need is rarely restricted to a single model, or a single location. As a result, a variety of gems exist to provide this kind of functionality (in many cases, without altering the signature of your render lines substantially).
This post offers a good listing. Personally, I've had a good experience with active_model_serializers and an acceptable experience with grape-entity. It's reasonable to review their documentation and decide which is best for you.
Is there any way to remove sensitive fields from the result set produced by the default ActiveRecord 'all', 'where', 'find', etc?
In a small project that I'm using to learn ruby I've a reference to User in every object, but for security reasons I don't want to expose the user's id. When I'm using a simple HTML response it is easy to remove the user_id simply by not using it. But for some task I'd like to return a json using something like:
def index
#my_objects = MyObject.all
respond_to do |format|
...
format.json { render json: #my_objects, ...}
...
end
end
How do I prevent user_id to be listed? Is there any way to create a helper that removes sensitive fields?
You can use the as_json to restrict the attributes serialized in the JSON response.
format.json { render json: #my_objects.as_json(only: [:id, :name]), ...}
If you want to make it the default, then simply override the method in the model itself
class MyObject
def serializable_hash(options = nil)
super((options || {}).merge(only: [:id, :name]))
end
end
Despite this approach is quick and effective, it rapidly becomes unmaintainable as soon as your app will become large enough to have several models and possibly different serialization for the same type of object.
That's why the best approach is to delegate the serialization to a serializer object. It's quite easy, but it will require some extra work to create the class.
The serializer is simply an object that returns an instance of a model, and returns a JSON-ready hash. There are several available libraries, or you can build your own.