Suppose I have a Rails app with two models Post and Comment. A post has_many comments and a comment belongs_to a post.
How can I override the respond_to function in the show action in order to get a JSON response containing both the Post properties and an array of Comment objects that it has?
Currently it is the vanilla Rails default:
# posts_controller.rb
def show
#post = current_user.posts.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
You can do that using Active Record serialization method.
to_json
Below code should work.
format.json { render json: #post.to_json(:include => :comments) }
Try using active_model_serializers for json serialization. It is easy to include associated objects and also separates things by having a different file for serialization.
Example:
class PostSerializer < ApplicationSerializer
attributes :id, :title, :body
has_many :comments
end
You can override to_json in model or you can use Jbuilder or rabl.
Rails has provide the best way to respond :
Define respond_to on the top of your controller . like :
class YourController < ApplicationController
respond_to :xml, :json
def show
#post = current_user.posts.find(params[:id])
respond_with (#post)
end
end
For more info take a look on : http://davidwparker.com/2010/03/09/api-in-rails-respond-to-and-respond-with/
Related
I'm using ActiveModel Serializers to serialize my models and I'm constantly in need to create a new serializer in order to satisfy the needs of an controller without including unnecessary information into another.
class ContactGroupSerializer < ActiveModel::Serializer
attributes :id, :name, :contacts, :contacts_count,
:company_id, :user_id
def contacts_count
object.contacts.count
end
end
Is there a way to define a single serializer, such as the one above, and them dinamically select which attributes to be included on my controller response?
class ContactsGroupsController < ApplicationController
def index
...
render json: #contact_groups // here I would like to return only id and name, for example
end
end
I know I can achieve that by creating another serializer, but I wouldn't like to.
Well, you can just define a method in your application_controller.rb to which you can pass all your objects to be rendered with array of methods to be returned as response..like for example,
def response_for(object, methods = [:id])
if object.blank?
head :no_content
elsif object.errors.any?
render json: { errors: object.errors.messages }, status: 422
else
render json: build_hash_for(object, methods), status: 200
end
end
private #or in your `application_helper.rb`
def build_hash_for(object, methods)
methods.inject({}) do |hash, method|
hash.merge!(method => object.send(method))
end
end
In your particular case above, you can just
class ContactsGroupsController < ApplicationController
def index
...
response_for #contact_groups, [:id, :name]
end
end
When I have a has_many/belongs_to relationship in Rails 5 API with active_model_serializers I can pass the option to include a nested model.
def show
render json: #post, include: ['comments']
end
It's also possible to get multiple layers of nesting.
def show
render json: #post, include: ['comments', 'comments.comment_likes']
end
I can't find documentation anywhere about adding conditions to the include statement. Is it possible to do something like this?
def show
render json: #post, include: ['comments'] { top_contributor: true }
end
In master (which will soon become RC4), a PR has been merged that allows for the following at serializer level:
belongs_to :user, if: :include_user?
def include_user?
current_user.admin?
end
I'm making a rails app where user can paste a soundcloud link in an input field. Then, this link is sent to the create action in my post_controller and used to get the JSON file for that song.
# app/controllers/post_controller.rb
def create
require 'open-uri'
raw_link = params[:post][:link]
raw_link.downcase.include? "soundcloud.com/"
tmp_media_json = JSON.load(open("http://soundcloud.com/oembed?format=json&url=#{raw_link}"))
if tmp_media_json['thumbnail_url']["placeholder"]
tmp_media_json['thumbnail_url'] = JSON.load(open("http://soundcloud.com/oembed?format=json&url=#{tmp_media_json['author_url']}"))['thumbnail_url']
end
media_thumbnail = tmp_media_json['thumbnail_url'].gsub('t500x500.jpg', 't80x80.jpg')
media_title = tmp_media_json['title']
media_iframe = tmp_media_json['html']
media_type = params[:post][:media_type]
#post = Post.new(link: media_iframe, title: media_title, thumbnail: media_thumbnail, media_type: media_type)
respond_to do |format|
if #post.save
format.js { render :file => "/pages/create_new_post.js.erb" }
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render action: 'show', status: :created, location: #post }
else
format.html { render action: 'new' }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
In the Post model, I'm trying to run validates :link, presence: true, but the problem is that it seems to be done after all the code in the create action. I want the validation to be done before all the code in the create action. (Since if there isn't a valid link, the code in the create action won't work).
How can I do this or is there a better practice?
class YourController < ApplicationController
before_filter :my_filter
before_filter :my_filter2, :except => [:index, :new, :create]
def my_filter
# your code gose here
end
def my_filter2
# your code gose here
end
........
end
You need to move the json fetching code out of the controller and into a model. Think Single Responsibility Principle(SRP): http://en.wikipedia.org/wiki/Single_responsibility_principle
Also, This question is slightly confusing because you are validating the "link" attribute which is the result of the JSON load from soundcloud, so this kind of makes your question impossible to achieve.
That being said, keep the controller lean
# controller...
def create
#post = Post.new_from_link(params[:post][:link], params[:post][:media_type])
#post.save
...render...
end
# post model...
class Post < ActiveRecord::Base
attr_accessor :raw_link, :raw_media_type
def new_from_link(raw_link, raw_media_type)
#raw_link = raw_link
#raw_media_type = raw_media_type
end
def assign_soundcloud_attributes
fetcher = SoundCloudFetcher.new(raw_link, raw_media_type).fetch
self.link = fetcher.link
self.media_type = fetcher.media_type
self.media_thumbnail = fetcher.media_thumbnail
self.media_iframe = fetcher.media_iframe
end
end
class SoundCloudFetcher
attr_accessor :link, :media_type, :media_thumbnail, :media_title, :media_iframe
def self.initialize(raw_link, raw_media_type)
#raw_link = raw_link
#raw_media_type = raw_media_type
end
def fetch
...go get and set the data...
return self
end
end
So the code above is not complete. Its missing the actual call to #assign_soundcloud_attributes. This design lets you move around where you want to do the call. You can place the call in #new_from_link, or you can place it in a before_validation or before_create callback, depending on your needs.
The question to clear this up is are you intending to validate the raw_link thats passed in, or do you want to validate the link that comes back from the soundcloud api call.
If you are validating the raw link, move the call to #assign_soundcloud_attributes to a before_create callback.
If you are validating the actual link attribute that is retrieved from the SoundCloud api call, then put it in the #new_from_link or #before_validation callback.
You can use a before_filter with rails_parms gem.
See https://github.com/nicolasblanco/rails_param
It works like this:
param! :q, String, required: true
param! :categories, Array
param! :sort, String, default: "title"
param! :order, String, in: %w(asc desc), transform: :downcase, default: "asc"
param! :price, String, format: /[<\=>]\s*\$\d+/
I have this custom action which I want to save http trips to retrieve different collections with.
def dashboard
#projects = Project.all
#tasks = Task.all
respond_do do |format|
format.json {render {projects: #project, tasks: #tasks}, serializer: DashboardSerializer }
end
end
class DashboardSerializer < ActiveModel::Serializer
attributes :proejcts, :tasks
end
this gives me an error like this
undefined method `read_attribute_for_serialization' for #<Hash:0x007fb5d58108c0>
Is there any way that I can make arbitrary collection attributes in the active model serializer template as I can do in Rabl?
Thank you!
AMS has a distinction between single item serialization and item collection serialization.
I was getting the same error, my solution looked like this:
render json: #posts, each_serializer: FancyPostSerializer
I am using Thinking Sphinx to run searches and I get the appropriate ActiveRecord Models fine. The problem is, I want to create an appropriate link path and text on each model, then send the info to the browser in the form of JSON, via AJAX. I am using the following to build those link attributes:
In the controller:
class FindController < ApplicationController
def tag_results
#results = ThinkingSphinx.search(params[:terms])
#results.each do |result|
result.build_ajax_response
end
respond_to do |format|
format.html
format.json { render :json => #results }
end
end
end
In the model:
class TaggedItem < ActiveRecord::Base
attr_accessible :name
attr_accessor :search_link, :search_text
def build_ajax_response
self.search_link = Rails.application.routes.url_helpers.tagged_item_path(self.id)
self.search_text = self.name
end
end
The resulting json object doesn't have either of the search_* attributes listed, much less have a value for them. I've tried using #search_link as well as just search_link in the build_ajax_response method.
Am I doing this wrong? Could there be something else interfering?
Rails' default to_json doesn't know about those extra non active record attributes you've added. The easiest possible thing is probably to specify them as extra methods to include:
format.json { render :json => #results.to_json(:methods => [:search_link, :search_text]) }