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.
Related
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
}
I am trying to store client_id in join table: clients_orders after submitting the form below.
I set the tables in this way so I can look up all the orders a client has made.
I am using rails 4 with devise and simple form.
models
class Order < ActiveRecord::Base
belongs_to :user
#has_and_belongs_to_many :clients
belongs_to :clients #solution
end
class Client < ActiveRecord::Base
#has_and_belongs_to_many :orders
as_many :orders, dependent: :destroy #solution
end
orders form
<%= simple_form_for(#order) do |f| %>
<%= f.error_notification %>
<%= f.association :client, collection: Client.all, label_method: :name, value_method: :id, prompt: "Choose a Client" } %>
<%= etc... %>
<%= f.submit %>
<% end %>
with the current code above, the join table clients_orders does not update
create_table "clients_orders", id: false, force: true do |t|
t.integer "client_id"
t.integer "order_id"
end
order controller
class OrdersController < ApplicationController
# GET /orders/new
def new
#order = Order.new
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(order_params)
#order.user_id = current_user.id
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:code, :client_id, :user_id, :memo, :status, items_attributes: [:id, :name, :_destroy])
end
end
For future reference:
this is a simple one-to-many relationship. All you have to do to access a client's orders is to set up the has_many :orders in the User model and belongs_to :user in Order model. Then you can use collection methods like current_user.orders and it will get all of that specific user's orders for you. Just assign it to the user with #order = current_user.orders.build(:order_params)
You aren't whitelisting the correct parameters in your create action.
When dealing with has_and_belongs_to_many associations, you're dealing with multiple objects on each side, so the attributes you're whitelisting are plural, not singular.
You need to be whitelisting client_ids, not client_id.
Also, I'm pretty sure your form is wrong. You have it setup as though client is a has_one relationship. I think you want the plural version there as well.
<%= f.association :clients, #...
# ^----- add an 's'
If you really intended for the form to model a singular relationship, then you'll need to massage the data somewhere before saving your model. Here's one way to do it:
def create
#order = Order.new(order_params)
#order.client_ids << params[:order][:client_id]
#order.user_id = current_user.id
# save and respond...
end
If you go this route, then just remove :client_id from your parameters whitelist rather than pluralizing it.
I am have two actions of which renders eventually should output json using the same rabl template, but at the moment, they each have a template with their own name
dashboard.json.rabl and batch_create.json.rabl
they are the exact same, how can I specify in the batch_create template to use the dashboard's template?
Thank you!
EDIT #including controller's two actions and their rabl views
line_items_controller.rb
def dashboard
#line_items ||= LineItem.limit(1000).all.to_a
pids = [1,2,3,4,5]
#projects = Project.only(:id, :name).where(:_id.in => pids).to_a
#users = User.only(:first, :last, :role, :company_id).all.to_a
#companies= Company.where(:_id.in => #users.map(&:company_id)).to_a
#specs = Spec.where(:specable_id.in => pids).to_a
spec_ids= #specs.map { |e| e.id }
#contact_infos = ContactInfo.where(:infoable_id.in => spec_ids).to_a end
gon.rabl
respond_to do |format|
format.html
format.json
end
end
def batch_create
#line_items = LineItem.where(:_id.in => params[:line_item_ids]).to_a
# else same as #dashboard
end
app/views/line_items/dashboard.json.rabl SAME AS app/views/line_items/batch_create.json.rabl
object false
child #projects => :projects do
attributes :id, :name
end
child #companies => :companies do
attributes :id, :name
end
child #users => :users do
attributes :id, :full, :company_id
end
child #specs => :specs do
attributes :id, :style, :due_at_note
end
child #contact_infos => :contact_infos do
attributes :info, :infoable_id
end
child #line_items do
attributes :id, :title, :dashboard_length, :dashboard_created_at, :original_file_url, :project_id
end
Have you tried using this in the batch_create.json.rabl:
extends '<insert_object_name>/dashboard'
I have the following classes:
class VideoChannel < ActiveRecord::Base
#Associations
belongs_to :video_playlist, :dependent => :destroy
VideoChannel.video_playlist_name
delegate :name, :id, :list_type, :list_id, :manual, :to => :video_playlist, :prefix => true
#validations
validates_presence_of :name
#After Functions
def after_create
video_playlist = VideoPlaylist.new(:name => self.name,
:list_type => "VideoChannel",
:list_id => self.id)
video_playlist.save
end
And :
class VideoPlaylist < ActiveRecord::Base
belongs_to :list, :polymorphic => true
has_many :video_channels, :dependent => :destroy
delegate :name, :id, :description, :to => :video_channel, :prefix => true
end
I'm trying to use the Rails Delegate function to create a link in the VideoChannel page that allows me to to link to the Video Playlist and edit the contents there. So the association is there and You can currently edit the playlists by going through the playlists section but we want to combine them. I can't seem to figure this out. Im also very new to Rails, still working through the guides etc.
Edit: Here's the view code
<%= link_to '<span class="pen icon"></span>Edit',
content_url(:controller =>"video_playlists", :id => channel.video_playlist_id, :action => "edit"),
:class => "button right" %>
Here are teh relevant pieces of the controllers:
class VideoChannelsController < ApplicationController
# GET /videochannels
# GET /videochannels.xml
def index
#video_channels = VideoChannel.roots(:order => 'order_num')
#video_channels_parents = #video_channels.group_by {:parent_id}
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #video_channels }
end
end
# GET /videochannels/1
# GET /videochannels/1.xml
def show
#video_channel = VideoChannel.find(params[:id], :order => 'order_num')
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #video_channel }
end
end
end
class VideoPlaylistsController < ApplicationController
# GET /video_playlists
# GET /video_playlists.xml
def index
if !params[:with].nil?
#video_playlists = VideoPlaylist.find(:all, :conditions => {:list_type => 'VideoShow'})
else
#video_playlists = VideoPlaylist.find(:all, :conditions => {:list_type => 'Section'})
end
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #video_playlists }
end
end
# GET /video_playlists/1
# GET /video_playlists/1.xml
def show
#video_playlist = VideoPlaylist.find(params[:id], :include => [{:video_video_playlists => :video}, {:videos => :asset}, {:videos => :content_image}])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #video_playlist }
end
end
end
Where did the line
VideoChannel.video_playlist_name
Come from? What's it doing? You're also calling a method on the class not an instance (sort of - Ruby isn't quite like this, but it's enough to explain).
Anyway:
Delegate is really for avoiding lots of train wreck code like this:
fred.jim.bill.xxx
You've said that they belong to each other - the relationships look like they're the wrong way round. Why are you creating the parent from inside the child? How are you going to have many video channels belonging to a given playlist?
I think you need to look at build and the relationship names. To gat past me maybe misunderstanding your model, lets have switch to a product that has many stock items:
class Product < ActiveRecord::Base
has_many :stock_items
end
class StockItem < ActiveRecord::Base
belongs_to :product
end
This means that stock_item will have a column product_id.
So, say you're creating a product you'd do something like:
product.stock_items.build # :whatever the params are required
This automatically sets the id's for you and means you don't have to set id's. Then when you do product.save it will save all the related stock items too.
And in the view for this toy model, then if you were displaying one of the stock items, you'd use delegate to show the name of the product in the view without having to do lost of stock_item.product.name (for example).
I hope this helps.
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.