Hiding Rails Model Attributes - ruby-on-rails

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.

Related

render multiple queries as json in rails controller

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
}

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.

Rails model as_json overwriting default values

I'm trying overwrite default model values for json but instead of overwriting it create duplicate hash
My model:
class HomeScreenButton < ActiveRecord::Base
belongs_to :product_category
validates :product_category_id, :x, :y, :presence => true
attr_accessible :product_category_id, :x, :y
def as_json(options={})
hash = super(options)
hash.merge({
:product_category_id => "fdfd"
})
end
end
My controller:
def index
#home_screen_buttons = HomeScreenButton.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #home_screen_buttons}
end
end
When I'm opening json it shows me duplicate for product_category_id: [{"created_at":"2013-03-17T11:14:32Z","id":1,"product_category_id":5,"updated_at":"2013-03-17T11:14:32Z","x":300,"y":200,"product_category_id":"dfdffff"}]
There is no need to merge hashes
def as_json(options={})
hash = super(options)
hash[:product_category_id] = "fdfd"
hash
end

How can i show my microposts and articles order by created_time in one page?

The routes.rb
root :to => 'articles#index'
Micropost model:
class Micropost < ActiveRecord::Base
attr_accessible :content, :user_id
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 100 }
end
Article model:
class Article < ActiveRecord::Base
attr_accessible :content, :title
validates :title, :presence => true,
:length => { :minimum => 2 }
belongs_to :user
end
And the index define_method of articles_controller.rb
def index
#articles = Article.paginate(:page => params[:page],
:per_page => 5,
:order => 'created_at DESC')
respond_to do |format|
format.html
format.json { render json: #articles }
end
end
While i don't how to write my articles_controller#index and the index view.
Given your code works you should be able to simply change updated_at to created_at
def index
#posts = Post.paginate(:page => params[:page],
:per_page => 5,
:order => 'created_at DESC')
respond_to do |format|
format.html
format.json { render json: #posts }
end
end
What's exactly the question?
If you want all Articles on a single page, simply don't use paginate, instead:
Article.all
If you want all Articles ordered by created_at timestamp in descending order:
Article.order("created_at DESC")
Or did you want a second page with different ordering? Then I would create a new route which takes a parameter to tell the action which ordering you want. And then inside controller:
if(params[:order_by_created_time])
# ...
else
# ...
end
respond_to do |format|
# ...
end
If your view is already working (and displaying the microposts):
def index
#articles = Article.order("created_at DESC")
respond_to do |format|
format.html
format.json { render json: #articles }
end
end

how to: Rails respond_to json using exact same rabl template?

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'

Resources