render multiple queries as json in rails controller - ruby-on-rails

I have two rails controller actions:
def show
#project = Project.find(params[:id])
render json: #project,
:only => [:id, :compilation_id],
:methods => :track_name,
:include => {
:user => { :only => [:id, :email] }
}
end
def list_users
render json: User.select(:id, :email)
end
I would like to render them both in one response. What is the best way to go about doing this? I tried using the to_json method as described here but I read that that method is deprecated and I also noticed that it escapes the content which seems to be unnecessary. Any help is appreciated.

For the cases where you need json structures complicated enough for to_json to look readable, I recommend to use active_model_serializers gem.
You can then define two serializer classes like this:
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :compilation_id
has_many :users
end
class UserSerializer < ActiveModel::Serializer
attributes :id, :email
end
And then in your controller:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
render json: #project, serializer: ProjectSerializer, status: 200
end
end
As a bonus track, you can even cache the response!

The solution, of course, was pretty simple:
project = Project.select(:id, :compilation_id, :user_id, :genre_id, :ordering).find(params[:id])
render json: { :project => project,
:users => User.select(:id, :email),
:genres => Genre.select(:id, :name),
:track_name => project.track_name
}

Related

What's the difference between respond_to and respond_with in Rails?

When I'm sending data to my controller I'm getting the following error
with the parameters
{"title"=>"some",
"user_id"=>"2",
"task"=>{"title"=>"some"}}
Why is that so? And what's the difference between respond_to and respond_with in Rails?
class TasksController < ApplicationController
respond_to :json
def create
respond_with current_user.tasks.create(task_params)
end
private
def task_params
params.require(:task).permit(:id, :title, :due_date, :priority, :complete)
end
end
When I'm using respond_to it says Undefined method upcase for Task
It's saying it doesn't recognize the format of your response. Since respond_with current_user.tasks.create(task_params) will generate a html response.
In your routes.rb change
resources :tasks
to
resources :tasks, :defaults => {:format => "json"}
This question may help you
Try this one:
def create
respond_with(current_user.tasks.create(task_params), :location => tasks_url)
end

Rails 4 - Remove "created_at" and "updated_at" from render

When I want to remove these datas from one resource I do:
#teams = Team.all
render json: #teams, :except => [:created_at, :updated_at],
My doubt is when I have many includes like these:
#teams = Team.all
render json: #teams, :include => [:stadiums, :scores, :links, :rounds]
How do I remove from all of them?
Correction: You can do something like
render json: #teams.to_json(:except => [:created_at, :updated_at], :include => { :stadiums => { :except => [:created_at, :updated_at]}, ... })
There is no simple way of doing this without iterating over the relevant models, obtaining the attributes hash and selecting the desired attributes.
Such use cases are often solved elegantly using json templating DSLs like jbuilder or rabl.
To illustrate this using jbuilder:
Jbuilder.encode do |json|
json.array! #teams do |team|
json.name team.name
json.stadiums team.stadiums do |stadium|
json.name stadium.name
# Other relevant attributes from stadium
end
# Likewise for scores, links, rounds
end
end
Which would produce the output as:
[{
name: "someteamname",
stadiums: {
name: "stadiumname"
},
...
}, {...},...]
If you find this too verbose for your use case, as #liamneesonsarmsauce has pointed out in the comments another solution is to use ActiveModel Serializers
Using this approach you can specify a serializer class for each of your models, listing the allowed attributes which would become a part of json response. For example
class TeamSerializer < ActiveModel::Serializer
attributes :id, :name # Whitelisted attributes
has_many :stadiums
has_many :scores
has_many :links
has_many :rounds
end
You can define similar serializers for associated models as well.
Since associations are seamlessly handled in a way that is already familiar to rails developers, unless you require much customization of the generated json response, this is a more succinct approach.
How 'bout adding to models/application_record.rb
# Ignore created_at and updated_at by default in JSONs
# and when needed add them to :include
def serializable_hash(options={})
options[:except] ||= []
options[:except] << :created_at unless (options[:include] == :created_at) || (options[:include].kind_of?(Array) && (options[:include].include? :created_at))
options[:except] << :updated_at unless (options[:include] == :updated_at) || (options[:include].kind_of?(Array) && (options[:include].include? :updated_at))
options.delete(:include) if options[:include] == :created_at
options.delete(:include) if options[:include] == :updated_at
options[:include] -= [:created_at, :updated_at] if options[:include].kind_of?(Array)
super(options)
end
then use it like
render json: #user
# all except timestamps :created_at and :updated_at
render json: #user, include: :created_at
# all except :updated_at
render json: #user, include: [:created_at, :updated_at]
# all attribs
render json: #user, only: [:id, :created_at]
# as mentioned
render json: #user, include: :posts
# hurray, no :created_at and :updated_at in users and in posts inside users
render json: #user, include: { posts: { include: :created_at }}
# only posts have created_at timestamp
So in your case, your code remains the same
#teams = Team.all
render json: #teams, :include => [:stadiums, :scores, :links, :rounds]
and yeah, you get them all without :created_at and :updated_at. No need to tell rails to exclude that in every single model, hence keeping the code real DRY.

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

undefined method `reorder' for #<Array:0x007fee740c1b78>

I'm using activeadmin and for whatever reason it's not like my Tag model. I don't see anything out of the ordinary about it? google hasnt proved helpful
application_controller
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :get_tags
private
def get_tags
#tags = Tag.all
end
end
tags_controller
class TagsController < ApplicationController
def search
#tags = Tag.where("name like ?", "%#{params[:q]}%")
respond_to do |format|
format.json { render :json => #tags.to_json(:only => [:id, :name]) }
end
end
def show
#tag = Tag.find(params[:id])
#title = #tag.name
end
end
tag model
class Tag < ActiveRecord::Base
self.include_root_in_json = false
has_many :resource_tags
has_many :resources, :through => :resource_tags
attr_accessible :name
validates :name, :presence => true,
:length => { :within => 2..20 },
:uniqueness => { :case_sensitive => false }
end
full trace: http://pastie.org/3641717
I'm going to go out on a limb and guess that your Tag model is conflicting with ActiveAdmins
Arbre::HTML::Tag class. There may be other/better solutions, but one thing that's worked for me in the past is to use the as: option in ActiveAdmin.
ActiveAdmin.register Tag, as: 'AwesomeTag' do
Obviously, the change in copy might be ideal, but it's a good troubleshooting step. Another option is to rename your Tag model, or try namespacing it.

Hiding Rails Model Attributes

I have a controller for an API that looks like this:
def index
respond_to do |format|
format.json { render :json => #groups.to_json(:only => [:id, :name, :description, :created_at, :updated_at])}
end
end
def show
respond_to do |format|
format.json { render :json => #group.to_json(:only => [:id, :name, :description, :created_at, :updated_at]) }
end
end
# #todo add store to item
def create
if #group.save
render :json => #group.to_json(:only => [:id, :name, :description, :created_at, :updated_at])
else
render :status => 406
end
end
def update
if #group.update_attributes(params[:group])
render :json => #group.to_json(:only => [:id, :name, :description, :created_at, :updated_at])
else
render :status => 406
end
end
def destroy
#group.destroy
render :text => ""
end
As you can see, I'm repeating my self a lot. I'd love to make these (and only these) attributes available by way of the model, but couldn't find a fitting solution. Is there anything to protect attributes from mass writing? Or do I possibly mean mass reading?
As noted in comments below I want to have a model with attributes, name and i_am_private. When I render that model as json - render :json => #model - I want only name to show up.
Ruby 1.8.7
Rails 3
How about overriding as_json method in your Group model?
class Group < ActiveRecord:Base
...
def as_json(options={})
{
:id => id,
:name => name,
:description => description,
:created_at => created_at,
:updated_at => updated_at
}
end
end
To prevent mass assignment, add the following to your model:
attr_accessible :attr1, :attr2, :attr3
where attr1, attr2, attr3 and so on are the attributes you want to allow for mass assignment, the rest of the attributes for that model will not be allowed for mass assignment.

Resources