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.
Related
I have order model which has one serialise column as order_details. When I tries to call index action it returns the hash in order_details key.
I want all the values of order_details in main object of order.
My order model as below:
# models/order.rb
class Order < ActiveRecord::Base
belongs_to :user
serialize :order_details
....
end
Controller
# controllers/orders_controller.rb
class OrdersController < ApplicationController
def index
orders = Order.select('id, user_id, total, order_details')
render json: orders, status: :ok
end
end
The JSON response I received is as below:
[{
"order":{
"id":1,
"user_id":1,
"total": 1000,
"order_details":{
"payment_done_by":"credit/debit",
"transaction_id":"QWERTY12345",
"discount":210,
"shipping_address": "This is my sample address"
}
},
{
"order":{
"id":2,
"user_id":2,
"total": 500,
"order_details":{
"payment_done_by":"net banking",
"transaction_id":"12345QWERTY",
"discount":100,
"shipping_address": "This is my sample address 2"
}
}
]
But here I need response in below format
[{
"order":{
"id":1,
"user_id":1,
"total": 1000,
"payment_done_by":"credit/debit",
"transaction_id":"QWERTY12345",
"discount":210,
"shipping_address": "This is my sample address"
},
{
"order":{
"id":2,
"user_id":2,
"total": 500,
"payment_done_by":"net banking",
"transaction_id":"12345QWERTY",
"discount":100,
"shipping_address": "This is my sample address 2"
}
]
I was trying to parsed each response using each but result can have hundreds of user object.
Please help here.
Thanks in advance.
Krishna
you should add as_json to your Order model to override the existing method with the same name for you to meet your expected output
def as_json(options = nil)
if options.blank? || options&.dig(:custom)
attrs = attributes.slice("id", "user_id", "total")
attrs.merge(order_details)
else
super
end
end
then, in your controller
def index
orders = Order.all
render json: orders, status: :ok
end
hope that helps
FYR: https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
In my Rails (api only) learning project, I have 2 models, Group and Artist, that have a many-to-many relationship with a joining model, Role, that has additional information about the relationship. I have been able to save m2m relationships before by saving the joining model by itself, but here I am trying to save the relationship as a nested relationship. I'm using the jsonapi-serializer gem, but not married to it nor am I tied to the JSON api spec. Getting this to work is more important than following best practice.
With this setup, I'm getting a 500 error when trying to save with the following errors:
Unpermitted parameters: :artists, :albums and ActiveModel::UnknownAttributeError (unknown attribute 'relationships' for Group.)
I'm suspecting that my problem lies in the strong param and/or the json payload.
Models
class Group < ApplicationRecord
has_many :roles
has_many :artists, through: :roles
accepts_nested_attributes_for :artists, :roles
end
class Artist < ApplicationRecord
has_many :groups, through: :roles
end
class Role < ApplicationRecord
belongs_to :artist
belongs_to :group
end
Controller#create
def create
group = Group.new(group_params)
if group.save
render json: GroupSerializer.new(group).serializable_hash
else
render json: { error: group.errors.messages }, status: 422
end
end
Controller#group_params
def group_params
params.require(:data)
.permit(attributes: [:name, :notes],
relationships: [:artists])
end
Serializers
class GroupSerializer
include JSONAPI::Serializer
attributes :name, :notes
has_many :artists
has_many :roles
end
class ArtistSerializer
include JSONAPI::Serializer
attributes :first_name, :last_name, :notes
end
class RoleSerializer
include JSONAPI::Serializer
attributes :artist_id, :group_id, :instruments
end
Example JSON payload
{
"data": {
"attributes": {
"name": "Pink Floyd",
"notes": "",
},
"relationships": {
"artists": [{ type: "artist", "id": 3445 }, { type: "artist", "id": 3447 }]
}
}
Additional Info
It might help to know that I was able to save another model with the following combination of json and strong params.
# Example JSON
"data": {
"attributes": {
"title": "Wish You Were Here",
"release_date": "1975-09-15",
"release_date_accuracy": 1
"notes": "",
"group_id": 3455
}
}
# in albums_controller.rb
def album_params
params.require(:data).require(:attributes)
.permit(:title, :group_id, :release_date, :release_date_accuracy, :notes)
end
From looking at https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html I think the data format that Rails is normally going to expect will look something like:
{
"group": {
"name": "Pink Floyd",
"notes": "",
"roles_attributes": [
{ "artist_id": 3445 },
{ "artist_id": 3447 }
]
}
}
with a permit statement that looks something like (note the . before permit has moved):
params.require(:group).
permit(:name, :notes, roles_attributes: [:artist_id])
I think you have a few options here:
Change the data format coming into the action.
Craft a permit statement that works with your current data (not sure how tricky that is), you can test your current version in the console with:
params = ActionController::Parameters.new({
"data": {
"attributes": {
"name": "Pink Floyd",
"notes": "",
},
"relationships": {
"artists": [{ type: "artist", "id": 3445 }, { type: "artist", "id": 3447 }]
}
}
})
group_params = params.require(:data).
permit(attributes: [:name, :notes],
relationships: [:artists])
group_params.to_h.inspect
and then restructure the data to a form the model will accept; or
Restructure the data before you try to permit it e.g. something like:
def group_params
params_hash = params.to_unsafe_h
new_params_hash = {
"group": params_hash["data"]["attributes"].merge({
"roles_attributes": params_hash["data"]["relationships"]["artists"].
map { |a| { "artist_id": a["id"] } }
})
}
new_params = ActionController::Parameters.new(new_params_hash)
new_params.require(:group).
permit(:name, :notes, roles_attributes: [:artist_id])
end
But ... I'm sort of hopeful that I'm totally wrong and someone else will come along with a better solution to this stuff.
I'm working on an API based Rails application, I don't know how to show all associated items in one request like my models
todo.rb
class Todo < ApplicationRecord
has_many :items, dependent: :destroy
validates_presence_of :title, :created_by
end
item.rb
class Item < ApplicationRecord
belongs_to :todo
validates_presence_of :name
end
in the controller
def show
json_response(#todo)
end
private
def set_todo
#todo = Todo.find(params[:id])
end
The endpoit for single todo
https://example.com/todos/1
and it showing like this
{
"id": 1,
"title": "Hello World!",
"created_by": "2",
"created_at": "2018-04-26T11:19:31.433Z",
"updated_at": "2018-04-26T11:19:31.433Z"
}
My question is how do I show all items which created by this todo in same end point request.
Try using ActiveModel::Serializers::JSON#as_json
For e.g.
def show
render json: #user.as_json(include: :posts), status: 200
end
That should return a response like
{
"id": 1,
"name": "Konata Izumi",
"age": 16,
"created_at": "2006/08/01",
"awesome": true,
"posts": [
{
"id": 1,
"author_id": 1,
"title": "Welcome to the weblog"
},
{
"id": 2,
"author_id": 1,
"title": "So I was thinking"
}
]
}
def show
render json: {"todos" => {"todo" => #todo, "items" => #todo.items }}, status:200
end
Alternatively you can use serializers for more filters and modifications of response datas
I want to use a serializer within another serializer so I can add a key-value pair at the top level but seems like when I do, the lower level serializer isn't working anymore-
My files:
ItemsController
class ItemsController
def index
open_items = Items.
select("distinct on (open_item_id) *").
preload(:company, :project)
total = open_items.count("id")
render json: {
total: total,
items: paginate(open_items, per_page: 2), serializer: ItemsSerializer
}, status: :ok
end
end
ItemsSerializer
class ItemsSerializer < ActiveModel::Serializer
attribute :total
has_many :items, serializer: ItemSerializer
end
ItemSerializer
class ItemSerializer < ActiveModel::Serializer
attributes :id,
:project,
:company,
def company
{
name: object.company.name,
id: object.company.id
}
end
def project
{
name: object.project.name,
id: object.project.id
}
end
end
I want to get another key/value pair to my serializer output in the below so that I can get something like this:
{
"total": 1,
"items": [
{
"id": 42920375,
"company": {
"id": 123,
"name": "CompanyName"
},
"project": {
"id": 456,
"name": "ProjectName"
}
}
]
}
But currently, I'm getting:
{
"total": 1,
"items": [
{
"id": 42920375,
"company_id": 5842,
"project_id": 191741,
}
]
}
I don't think you can use ItemsSerializer that way. It needs to correspond to a model.
Active-Model-serializer will automatically serialize each object in an association with its own serializer:
"In your controllers, when you use render :json for an array of objects, AMS will use ActiveModel::ArraySerializer (included in this project) as the base serializer, and the individual Serializer for the objects contained in that array."
So there's no need to reinvent the wheels. Just do this:
render json: paginate(open_items, per_page: 2), status: :ok
Then each item will be processed by ItemSerializer. I don't see a way to add total here though.
I am using AMS version 0.10 and looking to use the json-api specification for rendering my responses. However, I am having difficultly rendering the 'included' key for my relationship data. I have the following setup:
products_controller.rb
class Api::V1::ProductsController < ApplicationController
...
respond_to :json
def show
respond_with Product.find(params[:id])
end
...
product_serializer.rb
class ProductSerializer < ActiveModel::Serializer
attributes :id, :title, :price, :published
has_one :user
end
user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :auth_token, :created_at, :updated_at
end
products_controller_spec.rb
before(:each) do
#product = FactoryGirl.create :product
get :show, params: { id: #product.id }
end
...
it "has the user as a embeded object" do
product_response = json_response
puts "&&&&&&&&&&&&&&&"
puts product_response #output below
#expect(product_response[:user][:email]).to eql #product.user.email
end
...
json_response
{:data=>{:id=>"1", :type=>"products", :attributes=>{..working..}, :relationships=>{:user=>{:data=>{:id=>"1", :type=>"users"}}}}}
I would like to know how to get the 'included' section for the nested resource.
Example (from http://jsonapi.org/format/#introduction)
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"first-name": "Dan",
"last-name": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/9"
}
},
I have never used AMS before so any help will be greatly appreciated.
Many thanks
Just for anyone else the solution is here https://github.com/rails-api/active_model_serializers/blob/master/docs/jsonapi/schema.md.
Essentially i add the following to my controller action (GET products/1)
render json: product, include: params[:include]
This will allow the requesting system to determine whether they would like to include the nested models by adding the parameter include='user' for the api to process.
Thanks