how to define association in ActiveModel::Serializer - ruby-on-rails

I have following Serializer
class BookSerializer < ActiveModel::Serializer
attributes :id, :name, :publisher, :author, :cover_url
has_many :chapters
end
I have two method in bookscontroller.rb file as follows:
def index
books = Book.select(:id, :name, :publisher, :publisher, :cover, :author)
if books.present?
render json: books
end
end
def show
book = Book.find_by_id params[:id]
render json: book
end
Actually everything is working fine but the problem is I want chapters records in show page not on index page, but right now query for fetching chapters are running in both action but I only want to include has_many :chapters in show action.
So Is there any way that can I use association for particular method in rails controller?

You can use different serializers for different actions. For example, remove has_many :chapters from BookSerializer and create a separate BookWithChaptersSerializer. Use it as follows:
class BookWithChaptersSerializer < ActiveModel::Serializer
attributes :id, :name, :publisher, :author, :cover_url
has_many :chapters
end
def show
book = Book.find_by_id params[:id]
render json: book, serializer: BookWithChaptersSerializer
end

Related

Why does the JSON patch data from relationships not get saved via Ruby on Rails?

I have an Ruby on Rails api where the data is handled in JSON. When I want to update an entity all the attributes are getting updated persistently but changed relationships arent' getting handled correctly, the entity stays the same as before.
JSON data before and after the PATCH:
{"data":{"id":"26","type":"candidate","attributes":
{"place":"Ort","zip_code":"PLZ","address":"Adresse",
"date_of_birth":"2019-01-01T00:00:00.000Z","title":"Frau",
"first_name":"Vorname","last_name":"Nachname",
"email_address":"email#example.ch",
"confirm_terms_and_conditions":true},"relationships":
{"occupational_fields":{"data":[]}}}}
PATCH input:
Started PATCH "/candidates/26" for 127.0.0.1 at 2019-01-22
19:40:53 +0100
Processing by CandidatesController#update as JSON
Parameters: {"data"=>{"id"=>"26", "attributes"=>{"place"=>"Ort",
"zip_code"=>"PLZ", "address"=>"Adresse", "title"=>"Frau",
"first_name"=>"Vorname", "last_name"=>"Nachname",
"email_address"=>"email#example.ch",
"confirm_terms_and_conditions"=>true, "date_of_birth"=>"2019-01-
01T00:00:00.000Z"}, "relationships"=>{"occupational_fields"=>
{"data"=>[{"type"=>"occupational-fields", "id"=>“4“}]}},
"type"=>"candidates"}, "id"=>"26", "candidate"=>{}}
This are my models, Candidates and OccupationalFields are related via a has_many belongs_to_many relationship through one CandidatesOccupationalField:
class Candidate < ApplicationRecord
has_many :candidates_occupational_fields, dependent: :destroy
has_many :occupational_fields, through:
:candidates_occupational_fields, dependent: :nullify
end
class CandidatesOccupationalField < ApplicationRecord
belongs_to :candidate
belongs_to :occupational_field
end
class OccupationalField < ApplicationRecord
has_many :candidates_occupational_fields, dependent: :destroy
has_many :candidates, through: :candidates_occupational_fields,
dependent: :nullify
end
This is the used controller:
class CandidatesController < ApplicationController
before_action :set_candidate, only: %i[show update destroy]
# GET /candidates
def index
#candidates = Candidate.all
render json: CandidateSerializer.new(#candidates).serialized_json
end
# GET /candidates/1
def show
#candidate = Candidate.find(params[:id])
render json: CandidateSerializer.new(#candidate).serialized_json
end
# POST /candidates
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
render json: CandidateSerializer.new(#candidate), status: :created
else
render json: #candidate.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /candidates/1
def update
#candidate = Candidate.find(params[:id])
if #candidate.update(candidate_params)
render json: CandidateSerializer.new(#candidate)
else
render status: :unprocessable_entity
end
end
# DELETE /candidates/1
def destroy
#candidate.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_candidate
#candidate = Candidate.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def candidate_params
params.require(:data)[:attributes]
.permit(:place, :zip_code, :address,
:date_of_birth, :title, :first_name,
:last_name, :email_address,
:confirm_terms_and_conditions,
occupational_field_ids: [])
end
end
The JSON formatting is handled by fastjsonapi, this are the used serializers:
class CandidateSerializer
include FastJsonapi::ObjectSerializer
attributes :place, :zip_code, :address, :date_of_birth,
:title, :first_name, :last_name, :email_address,
:confirm_terms_and_conditions
has_many :occupational_fields
end
class OccupationalFieldSerializer
include FastJsonapi::ObjectSerializer
attributes :field
has_many :candidates
end
Thank you for your help.
The problem was, that the used serializer fast_jsonapi can't be used as deserializer and the Rail's strong parameters can't handle the json input. It works with the gem restful-jsonapi and modified params as shown in the example of the readme of restful-jsonapi.

Active Model Serializer customize json response

So I'm having a problem with ams. I'm using Rails 5.2 and I read lots of tutorials and even when I did exactly what they showed I still have something that they don't and I didn't find answer on google.
I have model Course, Video, Quiz and Segment.
Course has many segment, segment can be video or quiz (I'm using sti).
This is how I wrote it:
app/models/course.rb
class Course < ApplicationRecord
validates :title ,presence: true
validates :author ,presence: true
has_many :videos
has_many :quizs
has_many :segments
end
app/models/segment.rb
class Segment < ApplicationRecord
belongs_to :course
end
app/models/quiz.rb
class Quiz < Segment
validates :course_id ,presence: true
validates :name ,presence: true
belongs_to :course
end
app/models/video.rb
class Video < Segment
validates :course_id ,presence: true
validates :name ,presence: true
belongs_to :course
end
app/controllers/courses_controller.rb
class CoursesController < InheritedResources::Base
def show
#course = Course.find(params[:id])
render json: #course.attributes
end
def index
#courses = Course.all
render json: #courses
end
end
app/serializers/course_serializer.rb
class CourseSerializer < ActiveModel::Serializer
attributes :title, :author
has_many :segments
end
and this is what is showed me
I have couple of problems:
I don't know where this data name come from and I don't know how to change it or hide it.
even when I request to see one course I get the created date and other stuff I didn't want, although I configured it so I only see title and author.
I want to know if I can custom the json response so I won't see the title of the relations or change it's name.
You haven't created a SegmentSerializer. By default, AMS will serialize all fields.
class SegmentSerializer < ActiveModel::Serializer
attributes :name
end
Remove the attributes method call. It returns a Hash and then your serializer is not used.
```
class CoursesController < InheritedResources::Base
def show
#course = Course.find(params[:id])
render json: #course
end
def index
#courses = Course.all
render json: #courses
end
end
```
Use the key option
has_many :segments, key: :your_key

How to set an order for association array

I' ve got a controller
class Api::V1::QuestionsController < Api::V1::BaseController
authorize_resource
before_action :set_question, only:[:show]
api :GET, '/questions/id', 'This renders question by id'
def show
render json:#question
end
and a serializer
class QuestionSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :created_at, :updated_at, :short_title
has_many :answers
has_many :comments
has_many :attachments
def short_title
object.title.truncate(10)
end
end
Here is a part of test
context 'answers' do
it 'included in question object' do
expect(response.body).to have_json_size(2).at_path("answers")
end
%w(id body created_at updated_at).each do |attr|
it "contains #{attr}" do
#NO UNDERSTANDING AT ALL!!!!!
expect(response.body).to be_json_eql(answers[0].send(attr.to_sym).to_json).at_path("answers/1/#{attr}")
end
end
end
The thing is that this test passes, so obviously (for unknown reasons for me) the scope differs. I am new to rails, could anybody tell me how could I set a default scope to normalize my responds, also I have to admit that In my app I have a similar not api controller and there are no problems with that scope.
Yep this is the way. I should only set order in serializer
class QuestionSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :created_at, :updated_at, :short_title
has_many :answers
has_many :comments
has_many :attachments
def comments
object.comments.order(updated_at: :asc)
end
def answers
object.answers.order(updated_at: :asc)
end
def attachments
object.attachments.order(updated_at: :asc)
end
def short_title
object.title.truncate(10)
end
end

Search items that belong to list when list belongs to user

I built a todo list API project and I need to add the option to search both items and lists. So far I am able to search lists only. The data is also serialized (AMS).
I have the query for lists happening in the controller as opposed to the model. I did this so that I could get the current_user 's list.
This is what I have:
Models
class User < ActiveRecord::Base
has_many :lists
has_many :items, through: :lists
end
class List < ActiveRecord::Base
belongs_to :user
has_many :items, dependent: :destroy
end
class Item < ActiveRecord::Base
belongs_to :list
end
Controllers
class Api::ListsController < ApiController
before_action :authenticated?
def index
if params[:name].present?
lists = current_user.lists.where('name LIKE ?', "%#{params[:name]}%")
else
lists = current_user.lists
end
render json: lists
end
end
Serializers
class UserSerializer < ActiveModel::Serializer
attributes :id, :username
has_many :lists
end
class ListSerializer < ActiveModel::Serializer
attributes :id, :name, :permissions, :user_id
has_many :items
end
class ItemSerializer < ActiveModel::Serializer
attributes :list_id, :id, :name, :complete
end
Since I had already built a front-end, the list and items are displayed in welcome#index. On that same page, I added a simple form to perform the search like so:
<%= form_tag api_lists_path, :method => 'get', :id => "api_lists_search" do %>
<p>
<%= text_field_tag :name, params[:name] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
Currently, performing the search executes the correct request, and takes me to GET /api/lists?name=shopping which is then displayed in json. How could I pull the lists's items as well in the search? Having two search boxes seems clunky and even then, I'm not sure what the request path should look like.
You can try something like this:
class Api::ListsController < ApiController
before_action :authenticated?
def index
if params[:name].present?
lists = current_user.lists.where('name LIKE ?', "%#{params[:name]}%")
items = current_user.items.where('name LIKE ?', "%#{params[:name]}%")
else
lists = current_user.lists
end
results = items.any? ? lists + items : lists
render json: results
end
end
Firstly, to move the query for lists for users to the model, you can create a class method in the user model like;
class User < ActiveRecord::Base
has_many :lists
has_many :items, through: :lists
def self.find_list(name)
self.lists.where('name LIKE ?', "%#{name}%")
end
end
and call this from the controller with;
class Api::ListsController < ApiController
before_action :authenticated?
def index
lists = current_user.lists
lists = current_user.find_list(params[:name]) if params[:name].present?
render json: lists
end
end
You can then add a drop-down in the view to select whether to search for lists or items, create a method in user model for items and check in the controller for the value of the drop-down and call the respective method
I was able to search through my items (as well as my lists) by changing my index action to:
class Api::ListsController < ApiController
before_action :authenticated?
def index
if params[:name].present?
lists = current_user.lists.joins(:items).where(
'lower(lists.name) LIKE ? or lower(items.name) LIKE ?',
"%#{params[:name].downcase}%",
"%#{params[:name].downcase}%"
)
else
lists = current_user.lists
end
render json: lists
end
end

Serialize JSON association for children of a model

I'm using active-model-serializers for my API.
I have a model (Task) that has many subtasks(always Task model), called children.
I do this recursive has_many association thanks to ancestry gem (https://github.com/stefankroes/ancestry)
It works all enough well, but I have this problem:
Task has an association with User, but while active-model-serializers, export user for the main object, it doesn't show user details for also all children.
This is my serializer:
class TaskSerializer < ActiveModel::Serializer
attributes :id, :name, :details, :user_id
belongs_to :user
has_many :children
end
This is my controller:
class Api::V1::TasksController < Api::V1::BaseController
respond_to :json
def index
#tasks = current_user.company.tasks
respond_with #tasks, location: nil
end
end
And this is my model:
class Task < ActiveRecord::Base
has_ancestry
belongs_to :user
end
I've also tried to do this in my model:
class Api::V1::TasksController < Api::V1::BaseController
respond_to :json
def index
#tasks = current_user.company.tasks
render json: #tasks,
each_serializer: TaskSerializer,
status: :ok
end
end
But doesn't work...I've the user details for the parent object, but not for the children(where he only show me user_id, without all User object)
Any suggestions ?
Have you tried adding a serializer for the Children model or querying them as a explicit attribute like so?
class TaskSerializer < ActiveModel::Serializer
attributes :id, :name, :details, :user_id, :children
def children
object.children
end
end

Resources