Get the JSON representation of a model and an association - ruby-on-rails

A QuestionTag belongs_to a Tag.
I want to get the JSON representation of a QuestionTag, and some information from the Tag it belongs to. What is the correct Rails-Activerecord query to get me this structure from a controller action?
{
question_id: 1,
name: "PHP",
description: "A programming language for monkeys."
}
Here are the tables:
QuestionTag
question_id
tag_id
Tag
name
description

In you controller, when you send back the response in JSON format, you can do something like following:
respond_to do |format|
format.json do
render :json => #tag.to_json(:include => { :question_tag => { :only => :question_id } })
end
end
Now, it will send response in the following format. It will send only question_id form QuestionTag table.
{
name: "name",
description: "description",
question_id: 0
}

Related

Rails Ember strong parameters clarification

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

Better way to return validation errors for nested models as json in Rails?

I have class order, that can content entries. And each entry can be complex type, and consist from another entries.
class Order < ActiveRecord::Base
accepts_nested_attributes_for :entries
end
class Entry < ActiveRecord::Base
accepts_nested_attributes_for :members, allow_destroy: true
end
In form i have rails generated fields in form, using fields_for
<input autocomplete="off" class="string required form-control" id="order_entries_attributes_1459329286687_members_attributes_1459329286739_title" name="order[entries_attributes][1459329286687][members_attributes][1459329286739][title]" placeholder="Наименование" type="text">
So, i submit a form of order, for example with 2 entries, and 5 members with some validation errors ( 2 members without title ) and it passes to controller
class OrdersController
def update
if #order.update(order_params)
render json: #order
else
render json: #order.errors, status: :unprocessable_entity
end
end
end
And it returns me this
{"entries.members.title":["cant be blank"]}
Problem is that i cant find which entry and which member in it, has a validation error, and that's why i cant for example highlight this field. Moreover it merges similar errors. And this is problem.
On submit i pass unique index(in name attributes), and rails correctly use it for creating nested models, it would be nice if error response contained this indexes.
Is there any other way to return nice indexed errors from server, and use rails as api for json without pain?
Updated to have the same format as Rails nested params
render json: {
order: {
entries: #order.entries.enum_for(:each_with_index).collect{|entry, index|
{
index => {
id: entry.id,
errors: entry.errors.to_hash,
members: entry.members.enum_for(:each_with_index).collect{|member, index|
{
index => {
id: member.id,
errors: member.errors.to_hash
}
} unless member.valid?
}.compact
}
} unless entry.valid?
}.compact
}
}
You should get a JSON response like:
{
order: {
entries: [
0: {
id: 1, # nil, if new record
errors: {},
members: [
0: {
id: 7, # nil, if new record
errors: {
title: ["cant be blank"]
}
},
1: {
id: 13, # nil, if new record
errors: {
title: ["cant be blank"]
}
}
]
}
]
}
}
P.S. Maybe others know a rails-integrated way of doing this. Otherwise, I would say this might be a good feature request for Rails in git.

how to create a custom json from raw data (database data)

I have create a show method in tour controller and want to render the data from the database into json format.
This the definition of the tour controller
class ToursController < ApplicationController
respond_to :json
def show
#tourcategory = Tourcategory.find(params[:id])
#tours= #tourcategory.tours
#tourcategories = Tourcategory.all
render :layout => false
end
end
This is the definition of the show.html.haml view
%h3 Tour for #{#tourcategory.title}
= #tours.to_json
The output of this code is following:
[{"content":"dscfds","created_at":"2015-12-12T09:48:32Z","elementid":"test1","id":8,"jobid":2,"next_button_title":"next","priority":23,"title":"test1","updated_at":"2015-12-12T09:48:32Z"}]
But i just want to render the data in this kind of json format, following:
var tour = {
id: "tour",
steps: [
{
title: "abc",
content: "Click this Button.",
target: "#abc",
placement: "bottom",
showNextButton: false,
skipIfNoElement : true
},
It's not clear what you're trying to achieve but it seems to me that you want to extract certain attributes from your #tours and group them as an array, if that's the case you can do something like this:
(list all the attributes you want inside t.attributes.slice())
tour = { id: "tour", steps: #tours.map { |t| t.attributes.slice("title", "content", "target") } }
and if you want to convert your keys from snake (underscore) to camel format:
tour = {
id: "tour",
steps: #tours.map {|t| t.attributes.slice("title", "content").map {|k,v| [k.camelize(:lower), v]}.to_h}
}

How to Include additional field in Rails 3 Application JSON Response

I have a Rails application which displays nested form in json format.
In the JSON Response i am also displaying an id field which represent another table.
How to display name corresponding to that id what i am getting so that i can display both name and id in my json format.
My controller
show method
def show
#maintemplate = Maintemplate.find(params[:id])
respond_with (#maintemplate) do |format|
format.json { render :json => #maintemplate }
end
end
Thanks in advance....
Try this:
render :json => #maintemplate.to_json(:include => { :user => { :only => :name } } )
This will replace the user_id key with a user key and a value with only the name attribute of user, like this:
{
"user_id": "12"
"user": { "name": "..." }
...
}
You can then access the username in the json response with ["user"]["name"]. You can also access the user id with ["user_id"].
For more see the documentation on as_json.
Update:
Using the info provided in the comments, I think this is what you actually want:
render :json => #maintemplate.to_json(:include => { :routine => { :include => :user, :user => { :only => :name } } } )
Add to as_json method with the additional method-attributes you desire to the class in which you are calling.
class MainTemplate
...
def name
User.find(self.user_id).name
end
def as_json(options = {})
options[:methods] = :name
super(options)
end
end

How can I include a model association in a JSON response in Rails?

I've looked at similar posts but can't seem to quite figure it out.
I have the following function which works just fine. The Listing model has a foreign key called price_id which maps to the Price model and its price_range column. Price_id is returned as part of the message object in the JSON response.
How can I return the corresponding price_range value from the association instead of the price_id value (as part of the message obj, and keep the other attributes)?
def update
#listing = Listing.find(params[:listing][:id])
#if params were passed in for updating
if #listing.update_attributes(params[:listing])
#should we return the whole thing or just what's needed?
json_response = {
"success" => #listing.save, #save to DB and assign true/false based on success...
"message" => #listing.attributes #USE attributes to show output the content of the #message obj, and not another object called "message"
}
respond_to do |format|
#json response
format.html { render:json => json_response }
format.xml { render :xml => #listing }
#normal response. Consider leaving this for now?
#format.html { render :action => "detail" } #refresh this page, with new data in it. Consider trying to use redirect instead?
#format.xml { head :ok }
end
end #end if
end
add a method in your Listing model with the price_range and call it in serializable_hash
class Listing
def price_range
price.price_range
end
end
Like explain on comment you can use delegate instead this method :
class Listing
delegate :prince_range, :to => price
end
In you controller you can now do :
json_response = {
"success" => #listing.save, #save to DB and assign true/false based on success...
"message" => #listing.serializable_hash(:methods => [:price_range])
}
Based on what I read in this article, you should be able to do this:
class Listing
def as_json
super(:include => :price)
end
end
Then in your controller:
json_response = {
"success" => #listing.save,
"message" => #listing.as_json
}
If I understand correctly, you want to add #listing.price.price_range value to the "message" ?
If so, try this:
"message" => #listing.attributes[:price_range] = #listing.price.price_range

Resources