Custom field in ActiveModel Serializer with another serializer - ruby-on-rails

I have User model with has_many :sessions. But for one particular request I want to send only one session based on request device platform (iOS, Android).
The problem is that I have separate UserSerializer and UserSessionSerializer and I want to pass UserSessionSerializer as option in UserSerializer for :session field. Like this:
# UsersController
render json: #user, serializer: UserSerializer, session: #session # #user's session founded by platform
# UserSerializer
attributes :id, :email, :username, :session
def session
#instance_options[:session], serializer: UserSessionSerializer
end
It is impossible, because I can pass serializer only in has_one and has_many as I understand. But I don't want to render all user sessions in JSON with has_many, only founded one.
Thanks for any help!

Your code is good just use UserSessionSerializer.new instead of serializer: UserSessionSerializer
render json: { user: UserSerializer.new(#user, session: #session) }
attributes :id, :email, :username, :session
def session
UserSessionSerializer.new(self.instance_options[:session]) if self.instance_options[:session].present?
end

Related

Rails 6 nested attributes not deleting existing records when updating

I'm building an application where I have used nested attributes to store different option records under a question record. There is a form where the user can dynamically add and remove options. Everything works fine in my create action, but in the update action, if I remove an existing option and submit the form, it is not deleted from the database.
When updating the question record, is there any way to completely overwrite the existing nested parameters and replace it with the ones we pass in the update request?
I understand that adding _destroy to the attributes and passing it as a parameter would satisfy my requirement here. Since I'm deleting the option information from my frontend state on press of a "remove" button in the UI, I'm not sending it along with the params. Is there any other method in Rails to completely overwrite nested attributes and delete those nested records which are not passed in the update request, from the update action in the controller itself?
question.rb
class Question < ApplicationRecord
belongs_to :quiz
has_many :options
validates :body, presence: true
accepts_nested_attributes_for :options
end
option.rb
class Option < ApplicationRecord
belongs_to :question
validates :body, presence: true
validates :is_correct, inclusion: { in: [ true, false ], message: "must be true or false" }
end
questions_controller.rb
class QuestionsController < ApplicationController
...
def update
#question = Question.find_by(id: params[:id])
if #question.update(question_params)
render status: :ok, json: { notice: t("question.successfully_updated") }
else
render status: :unprocessable_entity, json: { error: #question.errors.full_messages.to_sentence }
end
end
...
private
def question_params
params.require(:question).permit(:body, :quiz_id, options_attributes: [:id, :body, :is_correct])
end
Relevant question
If I understand you correctly you're deleting the options one by one by clicking a button next to the option. Thats not actually something you need or want to use nested attributes for. Nested attributes is only relevant when you're creating/editing multiple records at once.
While you can destroy a single nested record by updating the parent:
patch '/questions/1', params: {
question: { options_attributes: [{ id: 1, _destroy: true }] }
}
Its very clunky and not really a good RESTful design.
Instead you can just setup a standard destroy action:
# config/routes.rb
resources :options, only: :destroy
<%= button_to 'Destroy option', option, method: :delete %>
class OptionsController < ApplicationController
# #todo authenticate the user and
# authorize that they should be allowed to destroy the option
# DELETE /options/1
def destroy
#option = Option.find(params[:id])
#option.destroy
respond_to do |format|
format.html { redirect_to #option.question, notice: 'Option destroyed' }
format.json { head :no_content }
end
end
end
This uses the correct HTTP verb (DELETE instead of PATCH) and clearly conveys what you're doing.
I can share my recent project work which is a bit similar to your where I am using shrine gem for upload images and I can update/destroy images which is associated with a Product model
product.rb
.
.
has_many :product_images, dependent: :destroy
accepts_nested_attributes_for :product_images, allow_destroy: true
product_image.rb
.
belongs_to :product
.
_form.html.erb for update
<%= f.hidden_field(:id, value: f.object.id) %>
<%= image_tag f.object.image_url unless f.object.image_url.nil? %>
<%= f.check_box :_destroy %>
and in products controller,I have whitelisted this
product_images_attributes: [:_destroy,:image, :id]
Hope this helps you to solve on your case

How to include sub-object in json response

Hi I am trying to include the roles of a user when rendering json doing User.all
I am using ruby on rails and Mongoid
I only get the role_id in my response...
role_id":"56cb596bc226cb5c04efd1cb
User model:
class User
include Mongoid::Document
include ActiveModel::SecurePassword
has_many :role
belongs_to :store
has_many :orders
Role model:
class Role
include Mongoid::Document
belongs_to :user
field :name, type: String
field :active, type: Mongoid::Boolean
the response I get:
{"_id":"...","api_key":"...","email":"jesus#drinkz.io","name":"... Garcia","password_digest":"...","promotion_ids":[],
"role_id":"56cb596bc226cb5c04efd1cb"}
How I get the response: GET /api/v1/users
def index
#user = User.first
respond_with #user
end
How can I embed roles in the response ?
You'll get the JSON that represents User alone if you don't include the Role as well. You can do something like below
def index
#user = User.first
respond_with(#user, :include => :role)
end
Old school way would be,
def index
#user = User.first
respond_to do |format|
format.json { render :json => #user.to_json(:include => :role) }
end
end
Add gem 'active_model_serializers' to your gemfile if you are not already using it . Then generate an user serializer using
rails generate serializer user
Then add following to app/serializers/user_serializer.rb file.
class UserSerializer < ActiveModel::Serializer
attributes :id, :email,:name, :password_digest, :promotion_ids, :api_key
has_many :roles
end

Rails accepts_nested_attributes_for associated models not created

I have two models (Company and User) that have a belongs_to/has_many relationship.
class Company < ActiveRecord::Base
attr_accessor :users_attributes
has_many :users
accepts_nested_attributes_for :users, allow_destroy: true
end
class User < ActiveRecord::Base
belongs_to :company
end
In my CompaniesController I want to create a new instance of Company along with a group of Users.
class Cms::CompaniesController < ApplicationController
def create
company = Company.new(company_params)
respond_to do |format|
if company.save
format.json { render json: company, status: :ok }
else
format.json { render json: company.errors.messages, status: :bad_request }
end
end
end
private
def company_params
params.require(:company).permit(
:id,
:name,
users_attributes: [
:id,
:_destroy,
:first_name,
:last_name,
:email
]
)
end
end
When I call company.save, I would expect a new instance of Company along with several new instances of User to be saved, depending on how many users I have in my params, however no users are persisted.
Here is a sample of what company_params looks like:
{"id"=>nil, "name"=>"ABC", "users_attributes"=>[{"first_name"=>"Foo", "last_name"=>"Bar", "email"=>"foo#bar.com"}]}
What am I missing here?
Remove attr_accessor:
class Company < ActiveRecord::Base
has_many :users
accepts_nested_attributes_for :users, allow_destroy: true
end
Everything else should work.
--
attr_accessor creates getter/setter methods in your class.
It's mostly used for virtual attributes (ones which aren't saved to the database). Your current setup is preventing you from being able to save the users_attributes param, thus your users are not saving.

Customize Devise user JSON response on creation of a user account

How do I customize the JSON output on creation of a devise User?
### User.rb ###
class User < ActiveRecord::Base
devise :database_authenticatable,
:registerable, ...
...
end
### Routes.rb ###
...
devise_for :users, :controllers => {:registrations => "registrations"}
...
I've got some extra fields in my User table that are secret, but they get returned in the JSON response when I do a User creation via JSON like this:
$ curl -H "Content-Type: application/json" -d '{"user" : {"username":"someone","email":"someone#somewhere.com","password":"awesomepass"}}' -X POST http://localhost:3000/users.json
which returns:
{"user":{"secret_field_1":"some value","secret_field_2":"some value","created_at":"2013-07-25T21:24:50-05:00","email":"someone#somewhere.com","first_name":null,"id":3226,"last_name":null,"updated_at":"2013-07-25T21:24:50-05:00","username":"someone"}}
I'd like to hide those secret fields, but don't know how to customize the JSON response.
I've tried a standard ActiveRecord serializer:
class UserSerializer < ActiveModel::Serializer
attributes :id, :created_at, :updated_at, :email, :first_name, :last_name, :username
end
to no avail, I'm guessing because of Devise.
I just ran into the same issue. I haven't pinpointed exactly why but it looks like respond_with in Devise's SessionsController (tested on Devise 3.0 and active_model_serializers 0.8.1) doesn't trigger the ActiveModel::Serializer.
So I overrode respond_with in my controller:
class SessionsController < Devise::SessionsController
def respond_with(resource, opts = {})
render json: resource # Triggers the appropriate serializer
end
end
It is, however, working in my RegistrationsController with respond_with. There I needed to do the following:
class RegistrationsController < Devise::RegistrationsController
respond_to :json
end
I recently ran into this and overriding respond_with didn't fix the issue. I ended up overriding to_json in user.rb like so:
def to_json(arg)
UserSerializer.new(self).to_json
end
Not sure what the extra arg is, but that seems to be required by one of the devise mixins.
I'm using the following:
Rails 4.2.0
Devise 3.4.1
Just a guess, but it sounds like rails is not finding your serializer and is using to_json(). Did you define active_model_serializer() in your model?
I just had the same problem, below is how I resolved it, very simple.
All these passed in active_model_serializers (0.9.5)
Override Devise registration method, and in your customize action:
def registration
//Some process, and you get a #user when registration is successful.
render :json => UserSerializer.new(#user)
end
If you want to pass some parameters to your customized Serializer(token for example), you can pass it in your action:
render :json => UserSerializer.new(#user).as_json({auth_token: your_token})
And in your serializer, just use:
class UserSerializer < ActiveModel::Serializer
attributes :id, :name, :avatar_url, :auth_token
def auth_token
serialization_options[:auth_token]
end
end
Depending on what you are doing with that JSON, you simply have to remove attributes you don't want from your serializer.
For example :
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :username
end
I presume that, in your case, you just want to do that.
But you also have the possibility to include an attribute on a specific condition :
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :author
def include_author?
current_user.admin?
end
end
And finally you can override the attributes method to return the hash you need :
class PersonSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
def attributes
hash = super
if current_user.admin?
hash["ssn"] = object.ssn
hash["secret"] = object.mothers_maiden_name
end
hash
end
end
See README of ActiveModel::Serializers for more informations.

Mongoid: Object.to_json - how to load children objects as well?

Mongoid doesn't include children documents into JSON when I do Object.to_json. How can I do it? I tried this:
#realty = Realty.includes(:comments).find(params[:id])
...
respond_to do |format|
format.json { render json: #realty }
end
But comments still doesn't get included in JSON.
You need to use :include in the to_json call
#realty = Realty.find(params[:id])
...
respond_to do |format|
format.json { render json: #realty.to_json(include: [:comments]) }
end
You can include any association in there.
You can also use any random method:
#foo.to_json(methods: [:some_arbitrary_method])
This works for a smaller/simple api but check out:
JBuilder, which is part of the Rails 4 default gem inclusion, obviously you can use this with any Rails version
ActiveModel Serializers
I am just working on something like this and I use:
gem "active_model_serializers"
https://github.com/rails-api/active_model_serializers
http://railscasts.com/episodes/409-active-model-serializers
in my case Project has_many :posts and the json result would be:
{"projects":[{"id":1,"title":"test project","description":"nice test project","slug":null,"posts":[{"id":1,"title":"new test post for test project","body":"Some content here and there","responses":[],"author":{"id":1,"email":"admin#mail.md"}}],"members":[]}]}
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :title, :description, :slug
has_many :posts
has_many :memberships, key: :members
end
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :body, :responses
end

Resources