Rails 5 API - how to include multiple models in JSON response - rails-api

In a Rails 5 API app with active_model_serializers, I can send a response that contains include option for one model only and it works:
UsersController
json: #current_user, status: :ok, include: 'shop'
a User belongs_to a Shop, and a Shop has_one Address.
How is it possible to include a Address associated to the Shop to the response ?
Thank you.

Related

Problem with creating two objects with one controller action with ruby on rails

I am trying to send one post to a create in a controller and create two objects. For some reason the contoller only will create a company. I did a similair thing in sinatra and it worked. I know that the route is correct and so is the object the post sends.
Conrtoller:
def index
stocks = current_user.stocks
render json: stocks, include: ['company']
end
def create
company = Comapny.find_or_create_by(name: params["company"])
stock = current_user.stocks.create(stock_params)
render json: stock, status: :created
end
Params:
def stock_params
params.require(:stock).permit(:name, :price_purchased_at, :number, :info, :restaurant )
end
Serializer:
class StockSerializer < ActiveModel::Serializer
attributes :id, :name, :price_purchased_at, :info, :number, :company
belongs_to :user
belongs_to :company
end
I have tried changing the serializer and the params. I have also tried leaving out the company create line to see if it will create the stock but it still won't create a stock.
You ensure in your create that a company with the expected name exists. But you do not pass the found company to the stock creation method. Therefore, the stock creation fails with Company must exist.
Just change your create method to this:
def create
company = Company.find_or_create_by(name: params['company'])
stock = current_user.stocks.create!(stock_params.merge(company_id: company.id))
render json: stock, status: :created
end

Rails API render all responses in camelCase

I'm trying to get my Rails API to render all JSON responses in camelCase. Currently I am using Netflix Fast JSON API for my serializer and rendering errors like so:
render json: { errors: command.errors }, status: :unauthorized
For the Netflix Fast JSON API serializers I've been adding set_key_transform :camel_lower to every serializer, which seems to do the trick (although if anyone knows how to make that a default it would be much appreciated).
For rendering errors however I'm not sure the best way to go about camel casing. If anyone has any experience with this please let me know how you go about it! Ideally there is a method of doing this that doesn't add too much syntax to every render call being made.
UPDATE
In serializing errors I added a helper method on the application controller:
def render_error(errors_params, status)
render json: {
errors: errors_params
}.deep_transform_keys { |key| key.to_s.camelize(:lower) }, status: status
end
For the Netflix Fast JSON API I took the suggestion of #spickermann and added an application serializer for the other serializers to inherit from:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class SomeSerializer < ApplicationSerializer
attributes :attribute, :other_attribute
end
You could create an ApplicationSerializer and all other serializers could inherit from it:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class FooBarSerializer < ApplicationSerializer
attributes :buzz, :fizz
# ...
end
You could monkey patch the serializer
Rails.application.config.to_prepare do
FastJsonapi::ObjectSerializer.class_eval do
set_key_transform :camel_lower
end
end
and for handling errors you can probably create an error serializer
render serializer: ErrorSerializer, json: {status: : unauthorized, errors: resource.errors
Have a look here and here

accepts_nested_attributes_for for edit api in rails

I have a paper model which has many questions. I am making api.
In paper.rb
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions, reject_if: :all_blank,allow_destroy: true
In question.rb
belongs_to :paper
In paper_controller.rb
def create
paper = current_user.papers.create(paper_params)
unless plan.blank?
render json: {status: 'successful', paper: paper }, status: 201
else
render json: {error: 'Some thing went wrong'}, status: 401
end
end
def update
paper = Paper.find(params[:id])
if paper.update_attributes(paper_params)
render json: {status: 'successful', paper: paper }, status: 201
else
render json: {error: 'Some thing went wrong'}, status: 401
end
end
private
def paper_params
params.require(:paper).permit(:subject,questions_attributes: [:id, :question])
end
My problem is whenever I edit questions in a specific paper questions are not edited but new question are formed. For example If Have 3 questions and I have send request for edit then there will be 6 questions for that paper.
UPDATE
For JSON there is a known issue. More info here.
If new questions are being formed it means your controller cannot find the existing child questions. The only way that could happen is if you are not sending the :id for each question, so the problem is not your controller or model, but what the form is sending from the client side.
If your form is using javascript, and submitting a json body, you should check also that the JSON structure has a child called "questions_attributes" and not "question_attributes".
FYI you do not need to actually specify the question :id in the permit code if your id's are integers (although you of course do have to send it for the update to work).
def paper_params
params.require(:paper).permit(:id, :subject, questions_attributes: [:question])
end
See the official docs example here. If your id's are not integers (eg. custom, such as GUIDS, then you do need it).

Rails web API throwing of error

I am trying to create a web API that allows creation of FriendShip by email or phone_number.
class Api::FriendshipsController < Api::BaseController
respond_to :json
def create
friend = User.where("email = ? OR phone_number = ?", params[:emailOrPhone], params[:emailOrPhone]).first # create a friend by email or phone_number
if friend.valid? # check if the friend exists, if it does we create our new friendship
friendship = Friendship.new
friendship.user = current_user
friendship.friend = friend
if friendship.valid? # check if friendship is valid
friendship.save # if it is, we save and return a success JSON response
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
end
end
Here is my FriendShip model
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
validates_uniqueness_of :user_id, scope: :friend_id, :message => '%{friend_id} is already a friend'
validate :check_friend_and_user # prevent user trying to add him/herself as friend.
def check_friend_and_user
errors.add(:friend, "can't be the same as user") if user == friend
end
end
Whenever the uniqueness constrain is violated, an error missing interpolation argument :friend_id in "%{friend_id} is already a friend" ({:model=>"Friendship", :attribute=>"User", :value=>2} given) with error code 500
How do I make it not throw an error but instead proceed to give back a 'fail json response' with status code 400
I want the caller of this API to know that they are trying to add someone that is already a friend. Getting back a status code 500 and a bunch of html does not seems to uniquely identify it. So I would like to throw a error in the form of JSON and status 200
What you seem to be trying to do is determine if the friend is already associated to the User via the friendship class. Which you can simplify with a has_many :friendships association on the User object.
Also, the way you're looking up by email OR phone is IMO unnecessarily ambiguous and will be problematic if you want to separately track one or the other for other purposes. Which you seem to be wanting to do since you have them broken out into separate database columns. I think you could just put two form inputs email or phone number and only pass one along to the controller. If you must have only one then you could determine what the form submits in Javascript or something.
In that case, you'd be better off sending the type of identifier with your initial data from your form, so you can look for one or the other. So your form would explicitly send the column lookup identifier, e.g. the params would be equivalent to the ruby hash
{friendship: {email: "someone#somewhere.com"}}
With that in the params then you could do what your trying with this code
# assuming you're passing via a params hash that would look like
# one or the other of the following
# {friendship: {email: "john#doe.com"}}
# {friendship: {phone_number: "123-123-1234"}}
def create
if current_user.friendships.find_or_create_by(friendship_params)
render json: {created: true}, status: 200
else # if it's not a valid friendship, we display a error JSON response
render json: {created: false}, status: 400
end
end
protected
def friendship_params
require(:friendship).permit(:email, :phone_number)
end

Ruby on Rails - Join two tables add results to JSON

New to Ruby on Rails 4.0 and I am having some trouble getting my JSON to add the data from my joined table. I am using AngularJS on the front end, and I can't seem to find a good example on here to help me out.
In short, I have a Invite with a User ID. I have a User with a User ID. Knowing the Invite ID, I want to get the name of the User that corresponds to that invite.
Invite 1: UserID: 10
Invite 2: UserID: 11
Invite 3: UserID: 12
UserID: 10 Name: Owen
UserID: 11 Name Greg
Controller - Invites
# GET /invites/1
# GET /invites/1.json
def show
#invite = Invite.includes(:user).find(params[:id]).to_json(include: :user)
end
Model - Invite
class Invite < ActiveRecord::Base
belongs_to :user
has_one :meal
has_one :event
has_one :registry
end
Model - User
class User < ActiveRecord::Base
has_one :invite
end
However I only get this whenever I check the /invites/1.
{"id":1,"created_at":"2013-12-06T20:14:39.001Z","updated_at":"2013-12-06T20:58:15.597Z","event_id":7,"meal_id":8,"registry_id":0,"rsvp":false,"user_id":9}
Any help here is much appreciated!
Your show should be:
def show
#invite = Invite.includes(:user).find(params[:id])
respond_to do |format|
format.json { render :json => #invite.to_json(include: :user) }
end
end
I recommend you to use JBuilder http://rubygems.org/gems/jbuilder . You can use #invite.to_json, but JBuilder gives you much more control on the output.

Resources