how to: Rails respond_to json using exact same rabl template? - ruby-on-rails

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'

Related

How to merge default values into nested params in Rails 5?

I am desperately trying to merge a set of default values into my nested params. Unfortunately, using deep_merge no longer works in Rails 5 since it no longer inherits from Hash.
So this does not work:
class CompaniesController < ApplicationController
def create
#company = current_account.companies.build(company_params)
if #company.save
flash[:success] = "Company created."
redirect_to companies_path
else
render :new
end
end
private
def company_params
params.require(:company).permit(
:name,
:email,
:people_attributes => [
:first_name,
:last_name
]
).deep_merge(
:creator_id => current_user.id,
:people_attributes => [
:creator_id => current_user.id
]
)
end
end
It gives me this error:
undefined method `deep_merge' for ActionController::Parameters:0x007fa24c39cfb0
So how can it be done?
Since there seems to be no similar implementation of deep_merge in Rails 5 ActionController::Parameters by looking at this docs, then you can just simply do .to_h to convert it first into a ActiveSupport::HashWithIndifferentAccess (which is a subclass of a Hash):
to_h() Returns a safe ActiveSupport::HashWithIndifferentAccess representation of the parameters with all unpermitted keys removed.
def company_params
params.require(:company).permit(
:name,
:email,
:people_attributes => [
:first_name,
:last_name
]
).to_h.deep_merge(
:creator_id => current_user.id,
:people_attributes => [
:creator_id => current_user.id
]
)
end

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
}

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

Am I using the rails form properly?

I feel like I have no idea what I'm doing.. I have a vague Idea. I'm hoping I did this all right so far.
Any way you can see to refactor this would be greatly appreciated.
One thing I noticed it does wrong is it won't load the proper options that were previously submitted if there is an error and it posts to the same URL. The text inputs seem to load the previous value but the select and the radio buttons reset to the default every submit.
ResourcesController#new
def new
#resource = Resource.new
#title = "Submit Resource"
#categories = Category.all
end
ResourcesController#create (notice I have #categories = Category.all in both... according to DRY im not sure where else it should go, or it only works on the first form submit.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
category_ids = #categories.map { |c| c[1] }
if #resource.valid? and category_ids.include? params[:category_id]
#resource.cost = params[:cost]
#resource.category_id = params[:category_id]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
Resource.rb (model)
# == Schema Information
#
# Table name: resources
#
# id :integer not null, primary key
# upvotes :integer default(0)
# downvotes :integer default(0)
# url :string(255)
# title :string(255)
# cost :integer default(0)
# description :text
# flags :integer
# category_id :integer
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class Resource < ActiveRecord::Base
belongs_to :category
belongs_to :user
has_many :favorites
has_many :resource_tags
has_many :tags, :through => :resource_tags
attr_accessible :url, :title, :cost, :description, :category_id, :user_id
# Pseudo-Enum
COST = [:free, :paid, :both]
url_regex = /^(?:http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}(:[0-9]{1,5})?(\/.*)?$/ix
validates :url, :presence => true,
:format => { :with => url_regex,
:message => "must be valid"},
:uniqueness => { :case_sensitive => false,
:message => "has already been submitted"}
validates :title, :presence => true,
:length => { :within => 6..75 }
validates :cost, :presence => true
validates :description, :presence => true,
:length => { :within => 25..200 }
validates :category_id, :presence => true,
:format => { :with => /\d+/ }
validates :user_id, :presence => true,
:format => { :with => /\d+/ }
def cost
COST[read_attribute(:cost)]
end
def cost=(value)
write_attribute(:cost, COST.index(value.downcase.to_sym))
end
def category_id
read_attribute(:category_id).to_i
end
def category_id=(value)
write_attribute(:category_id, value.to_i)
end
end
My view file for the Resource#new form
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]})) %>
</div>
Last Q: i havent worked with the user_id field yet. This is going to be pulled from devise and will associate a User with a submitted resource. But how do I assign this without making some sort of input, like a hidden input. Would this go on behind the scenes in the controller?
To your last question:
devise adds a current_user method which is the logged in user. So if a user has multiple resources you could do something like:
#resource = current_user.resources.new(params[:resource])
First question:
When a form is rendered it is done so based on the #resource & #categories variables. When you post the form the create action is called which creates a new #resource. If the save fails for whatever reason the form is rerendered using the new #resource variable. The problem you have is that #resource.category is not set when you show the form again. So you'll have to do this before the is_valid? check.
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
But the real problem is with your form. It should nest the category_id in the resource params so that the category is set when you do Resource.new(params[:resource]).
Check the POST request body in your console or something and see if it's nested in the resource or not. I don't know the exact syntax for it but if you change this you can drop the #resource.category = Category.find line.
To piggyback on Sandip, you can dry up your actions by using a before_filter
class ResourcesController < ApplicationController
before_filter :load_categories, :only => [:show, :create]
def new
#resource = Resource.new
end
def create
#resource = Resource.new(params[:resource])
#resource.category = Category.find(params[:category_id])
if #resource.valid? # won't be valid if there is no category found.
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
private
def load_categories
#categories = Category.all
end
end
also if you plan on sticking #title inside of your application layout, I would change #title in your view to:
yield(:title) || 'My Site'
and on the appropriate pages use:
content_for(:title) do
Submit Resource
It will default to 'My Site' unless otherwise specified.
Looks like there is problem with create action
def create
#title = "Submit Resource"
#categories = Category.all
#resource = Resource.new(params[:resource])
if #categories.collect(&:id).include?(params[:category_id].to_i)
#resource.category_id = params[:category_id]
end
#resource.user = current_user
if #resource.valid?
#resource.cost = params[:cost]
#resource.save
redirect_to root_url
else
render :action => :new
end
end
view
<div class="field">
<%= f.label :category %>
<%= select_tag(:category_id, options_for_select(#categories.map {|c|[c.name, c.id]}, :selected => #resource.category_id)) %>
</div>

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