I'm trying to POST some data to my Rails 4 API.
The resource:
App.factory 'House', ['$resource', ($resource) ->
$resource '/api/v1/houses/:id', { id: '#id' }
]
The JSON representation of the resource:
newHouse = {
"owner_id": "30",
"name": "test",
"address_attributes": {
"street": "somewhere",
"city": "on",
"region": "earth"
}
}
On House.save(null, $scope.newHouse), the server logs give me this:
Processing by Api::V1::HouseController#create as JSON
Parameters: {"owner_id"=>"34", "name"=>"test", "address_attributes"=>{"street"=>"somewhere", "city"=>"on", "region"=>"earth"}, "house"=>{"name"=>"test", "owner_id"=>"34"}}
Which is underisable, to say the least.
owner_id and name appear directly bellow root, and below "house" - I would expect only below "house"
address_attributes appears only directly bellow root - I would expect it to be below house
Basically I want this:
Processing by Api::V1::HouseController#create as JSON
Parameters: {"house"=>{"name"=>"test", "owner_id"=>"34", "address_attributes"=>{"street"=>"somewhere", "city"=>"on", "region"=>"earth"}}}
Any help?
EDIT
Rails controller action:
def create
house = House.new house_params
if house.save
head 200
else
render json: {
"error" => "validation errors",
"detail" => house.errors.messages
}, status: 422
end
end
def house_params
fields = [:name, :owner_id, address_attributes: [:street, :city, :region, :country, :postal_code ]]
params.require(:house).permit(fields)
end
The model House has:
has_one :address
accepts_nested_attributes_for :address
I don't want to change the way the server handles the data. Many backends expect parameters to be held in the format I want, and even jQuery does it in its AJAX calls. AngularJS should be the one to change, or at least allow ways to configure.
I am not sure you are using your $resource correctly.
EDIT: I am not sure why your class method behaves like this, the requests made should be equal./EDIT
One example of an alternative way to do it would be this:
Lets say your newHouse model is this:
{
"owner_id": "30",
"name": "test",
"address_attributes": {
"street": "somewhere",
"city": "on",
"region": "earth"
}
}
In your html you would write something like:
<span class="btn btn-danger" ng-click="createNewHouse(newHouse)">Create new house</span>
Your Controller would bind createNewHouse() method to $scope:
$scope.createNewHouse= function(newHouse){
var newHouseInstance = new House();
newHouseInstance.house = newHouse;
newHouseInstance.$save();
}
Above code gave me a POST request with this payload (direct copy from Chrome developer console):
Remote Address:127.0.0.1:8080
Request URL:http://localhost:8080/api/v1/houses
Request Method:POST
Request Payload:
{"house":{"owner_id":"30", "name":"test", "address_attributes":{"street":"somewhere", "city":"on", "region":"earth"}}}
This will nicely translate to what you need back at the server.
Good luck!
Related
I am developing in student tracking website in RoR. In model I have following code to build json
self.as_json
json = Jbuilder.new do |j|
j.courses student_courses do |course|
j.(course, :id, :name)
j.students students, :name
end
end.target!
puts json
return json
end
My controller code is
render json: {
courses: course.as_json,
}
and produces
{"courses":[
"{\"id\": 1,\"name\": \"english\",\"students\": [{\"name\": \"ALison\"},{\"name\": \"Robert\"}]
},{...}... ]"
instead of
"courses" : [
{
"id": 1,
"name": "english",
"students": [
{"name": "ALison"},
{"name": "Robert"}]
}, {..},...
]
It is adding escape character(/) before every double quotes. How can I solve this issue
Hey you can use this to generate as alternative
course.to_json(:include => { :students => { :only => :name } })
What should the strong parameters for my chapters_controller be if I have a Book entity and a Chapter entity?
Note: I am using JSON API.
In my chapters_controller, should my strong parameters be:
:title, :order, :content, :published, :book, :picture
Or should it be:
:title, :order, :content, :published, :book_id, :picture
If I use :book instead of :book_id, then in my Ember application, when I go to create a new chapter, I am able to create it and associate this chapter to the parent book, however, my test fails:
def setup
#book = books(:one)
#new_chapter = {
title: "Cooked Wolf Dinner",
order: 4,
published: false,
content: "The bad wolf was very mad. He was determined to eat the little pig so he climbed down the chimney.",
book: #book
}
end
def format_jsonapi(params)
params = {
data: {
type: "books",
attributes: params
}
}
return params
end
...
test "chapter create - should create new chapter assigned to an existing book" do
assert_difference "Chapter.count", +1 do
post chapters_path, params: format_jsonapi(#new_chapter), headers: user_authenticated_header(#jim)
assert_response :created
json = JSON.parse(response.body)
attributes = json['data']['attributes']
assert_equal "Cooked Wolf Dinner", attributes['title']
assert_equal 4, attributes['order']
assert_equal false, attributes['published']
assert_equal #book.title, attributes['book']['title']
end
end
I get error in my console saying Association type mismatch.
Perhaps my line:
book: #book
is causing it?
Either way, gut feeling is telling me I should be using :book in my chapters_controller strong parameters.
It's just my test isn't passing, and I am not sure how to write the parameter hash for my test to pass.
After a few more hours of struggle and looking at the JSON API docs:
http://jsonapi.org/format/#crud-creating
It has come to my attention, in order to set a belongsTo relationship to an entity with JSON API, we need do this:
POST /photos HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "photos",
"attributes": {
"title": "Ember Hamster",
"src": "http://example.com/images/productivity.png"
},
"relationships": {
"photographer": {
"data": { "type": "people", "id": "9" }
}
}
}
}
This also led me to fixing another problem I had in the past which I couldn't fix. Books can be created with multiple genres.
The JSON API structure for assigning an array of Genre to a Book entity, we replace the data hash with a data array in the relationship part like this:
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
Additonally, in my controllers, anything strong parameters like so:
:title, :content, genre_ids: []
Becomes
:title, :content, :genres
To comply with JSON API.
So for my new test sample datas I now have:
def setup
...
#new_chapter = {
title: "Cooked Wolf Dinner",
order: 4,
published: false,
content: "The bad wolf was very mad. He was determined to eat the little pig so he climbed down the chimney.",
}
...
end
def format_jsonapi(params, book_id = nil)
params = {
data: {
type: "chapters",
attributes: params
}
}
if book_id != nil
params[:data][:relationships] = {
book: {
data: {
type: "books",
id: book_id
}
}
}
end
return params
end
Special note on the relationship settings - only add relationships to params if there is a relationship, otherwise, setting it to nil is telling JSON API to remove that relationship, instead of ignoring it.
Then I can call my test like so:
test "chapter create - should create new chapter assigned to an existing book" do
assert_difference "Chapter.count", +1 do
post chapters_path, params: format_jsonapi(#new_chapter, #book.id), headers: user_authenticated_header(#jim)
assert_response :created
json = JSON.parse(response.body)
attributes = json['data']['attributes']
assert_equal "Cooked Wolf Dinner", attributes['title']
assert_equal 4, attributes['order']
assert_equal false, attributes['published']
assert_equal #book.id, json['data']['relationships']['book']['data']['id'].to_i
end
I'm trying to write an update method that processes JSON. The JSON looks like this:
{
"organization": {
"id": 1,
"nodes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
Organization model:
has_many :nodes
accepts_nested_attributes_for :nodes, reject_if: :new_record?
Organization serializer:
attributes :id
has_many :nodes
Node serializer:
attributes :id, :title, :description
Update method in the organizations controller:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_attributes: node_params.except(:id))
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(nodes: [:id, :title, :description])
end
I also tried adding accepts_nested_attributes_for to the organization serializer, but that does not seem to be correct as it generated an error (undefined method 'accepts_nested_attributes_for'), so I've only added accepts_nested_attributes_for to the model and not to the serializer.
The code above generates the error below, referring to the update_attributes line in the update method. What am I doing wrong?
no implicit conversion of String into Integer
In debugger node_params returns:
Unpermitted parameters: id
{"nodes"=>[{"id"=>101, "title"=>"gsdgdsfgsdg.", "description"=>"dgdsfgd."}, {"id"=>1, "title"=>"ertret.", "description"=>"etewtete."}]}
Update: Got it to work using the following:
def update
organization = Organization.find(params[:id])
if organization.update_attributes(nodes_params)
render json: organization, status: :ok
else
render json: organization, status: :failed
end
end
private
def node_params
params.require(:organization).permit(:id, nodes_attributes: [:id, :title, :description])
end
To the serializer I added root: :nodes_attributes.
It now all works, but I'm concerned about including the id in node_params. Is that safe? Wouldn't it now be possible to edit the id of the organization and node (which shouldn't be allowed)? Would the following be a proper solution to not allowing it to update the id's:
if organization.update_attributes(nodes_params.except(:id, nodes_attributes: [:id]))
looks super close.
Your json child object 'nodes' need to be 'nodes_attributes'.
{
"organization": {
"id": 1,
"nodes_attributes": [
{
"id": 1,
"title": "Hello",
"description": "My description."
},
{
"id": 101,
"title": "fdhgh",
"description": "My description."
}
]
}
}
You can do this sort of thing. Put this in your controller.
before_action do
if params[:organization]
params[:organization][:nodes_attributes] ||= params[:organization].delete :nodes
end
end
It will set the correct attribute in params and still use all the accepts_nested_attributes features.
I would like to refactor an existing rails API using active model serializers.
Unfortunately the existing API uses a slightly different JSON schema than any of the the existing adapters and I have been unable to recreate it using AMS.
The schema I am trying to recreate is like this:
{
"status": 0,
"message": "OK",
"timestamp": 1438940571,
"data": {
"contacts": [
{
"contact": {
"id": "1",
"first_name": "Kung Foo",
"last_name": "Panda",
"created_at": "2015-07-23T14:09:20.850Z",
"modified_at": "2015-08-04T15:21:36.639Z"
}
},
{
"contact": {
"id": "2",
"first_name": "Johnny",
"last_name": "Bravo",
"created_at": "2015-07-23T14:09:20.850Z",
"modified_at": "2015-08-04T15:21:36.639Z"
}
}
]
}
}
I am wondering is there a way to create a custom adapter for active model serializers, or otherwise create this schema.
Could just use a couple of serializers.
class MessageSerializer < ActiveModel::Serializer
attributes :status, :message, :timestamp, :data
# We could've just used that, were it not for nested hashes
# has_many :contacts, key: :data
attributes :data
def data
ActiveModel::ArraySerializer.new(object.contacts, root: 'contacts')
end
end
class ContactSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :created_at, :modified_at
def root
'contact'
end
end
There seems to be no better way to do that
Then somewhere in your controller:
render json: Message.serializer.new(#message, root: false)
I'm trying to use the Active Model Serializer gem with my API, although I am struggling with something I thought would be pretty simple.
All my JSON responses are in a wrapped format, with every response having a top level message and status property, the data is within the content property. Every JSON response follows this format.
Example
{
'status': statuscode,
'message': message,
'content': { 'object':obj }
}
The contents of the "content" property is where I would like to place the output of the Serializer. My lists of articles, etc.
I cannot figure out how to do this though?
Any help would be greatly appreciated.
IF You dont mind your status and messages hashes being inside a hash you can use a meta key.
(from https://github.com/rails-api/active_model_serializers/tree/0-8-stable)
render :json => #posts, :serializer => CustomArraySerializer, :meta => {:total => 10}
=>
{
"meta": { "total": 10 },
"posts": [
{ "title": "Post 1", "body": "Hello!" },
{ "title": "Post 2", "body": "Goodbye!" }
]
}
Or if you need them to be top level keys you can SubClass ArraySerializer and overwrite as_json to allow it to merge in your keys.
def as_json(*args)
#options[:hash] = hash = {}
#options[:unique_values] = {}
hash.merge!(#options[:top_level_keys]) if #options.key?(:top_level_keys)
root = #options[:root]
if root.present?
hash.merge!(root => serializable_array)
include_meta(hash)
hash
else
serializable_array
end
end
then just
render :json #object, :serializer => YourCustomArraySerializer