Rails API active model serializer root node not working - ruby-on-rails

I have a rails 4.2.5 API app. For some reason, the JSON root node is not included in the response and I don't understand why.
curl http://localhost:3000/api/v1/category/science
returns
{"title":"science","sub_categories":34}%
instead of
{"category": {"title":"science","sub_categories":34}%}
The code:
controller
class Api::V1::CategoryController < ApplicationController
def show
category = params[:category] || "sports"
#category = Category.where(cat_title: category.capitalize).first
respond_to do |format|
format.json { render json: #category, serializer: CategorySerializer, root: "category" }
end
end
end
serializer
class CategorySerializer < ActiveModel::Serializer
attributes :title, :sub_categories
def title
URI::encode(object.cat_title.force_encoding("ISO-8859-1").encode("utf-8", replace: nil).downcase.tr(" ", "_"))
end
def sub_categories
object.cat_subcats
end
end

have a look into your initializers, this should be commented out in wrap_paramters.rb:
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true
# end

Rails 5.0.0.1
Using ActiveMovelSerializers (0.10.2) you just need to add an initializer:
app/config/initializers/json_api.rb
require 'active_model_serializers/register_jsonapi_renderer'
ActiveModelSerializers.config.adapter = :json_api

Related

Using Rails Path and URL Helpers with fast_jsonapi

I would like to use the rails URL helper instead of hard coding the path to access the article.
I checked into the documentation but nothing is specified.
The article_path helper method exists (I checked by running rake routes)
class V3::ArticlesController < Api::V3::BaseController
def index
articles = Article.all
render json: ::V3::ArticleItemSerializer.new(articles).serialized_json
end
end
class V3::ArticleItemSerializer
include FastJsonapi::ObjectSerializer
attributes :title
link :working_url do |object|
"http://article.com/#{object.title}"
end
# link :what_i_want_url do |object|
# article_path(object)
# end
end
What you want to do is pass in the context to your serializer from your controller:
module ContextAware
def initialize(resource, options = {})
super
#context = options[:context]
end
end
class V3::ArticleItemSerializer
include FastJsonapi::ObjectSerializer
include ContextAware
attributes :title
link :working_url do |object|
#context.article_path(object)
end
end
class V3::ArticlesController < Api::V3::BaseController
def index
articles = Article.all
render json: ::V3::ArticleItemSerializer.new(articles, context: self).serialized_json
end
end
You should also switch to the jsonapi-serializer gem which is currently maintained as fast_jsonapi was abandoned by Netflix.
I found a solution thanks to max's example.
I also changed the gem to jsonapi-serializer
class V3::ArticlesController < Api::V3::BaseController
def index
articles = Article.all
render json: ::V3::ArticleItemSerializer.new(articles, params: { context: self }).serialized_json
end
end
class V3::ArticleItemSerializer
include JSONAPI::Serializer
attributes :title
link :working_url do |object|
"http://article.com/#{object.title}"
end
link :also_working_url do |object, params|
params[:context].article_path(object)
end
end

ActiveModel Serializer - Passing params to serializers

AMS version: 0.9.7
I am trying to pass a parameter to an ActiveModel serializer without any luck.
My (condensed) controller:
class V1::WatchlistsController < ApplicationController
def index
currency = params[:currency]
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer
end
My serializer:
class V1::WatchlistOnlySerializer < ActiveModel::Serializer
attributes :id, :name, :created_at, :market_value
attributes :id
def filter(keys)
keys = {} if object.active == false
keys
end
private
def market_value
# this is where I'm trying to pass the parameter
currency = "usd"
Balance.watchlist_market_value(self.id, currency)
end
I am trying to pass a parameter currency from the controller to the serializer to be used in the market_value method (which in the example is hard-coded as "usd").
I've tried #options and #instance_options but I cant seem to get it work. Not sure if its just a syntax issue.
AMS version: 0.10.6
Any options passed to render that are not reserved for the adapter are available in the serializer as instance_options.
In your controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer, currency: params[:currency]
end
Then you can access it in the serializer like so:
def market_value
# this is where I'm trying to pass the parameter
Balance.watchlist_market_value(self.id, instance_options[:currency])
end
Doc: Passing Arbitrary Options To A Serializer
AMS version: 0.9.7
Unfortunately for this version of AMS, there is no clear way of sending parameters to the serializer. But you can hack this using any of the keywords like :scope (as Jagdeep said) or :context out of the following accessors:
attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
Though I would prefer :context over :scope for the purpose of this question like so:
In your controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists,
each_serializer: WatchlistOnlySerializer,
context: { currency: params[:currency] }
end
Then you can access it in the serializer like so:
def market_value
# this is where I'm trying to pass the parameter
Balance.watchlist_market_value(self.id, context[:currency])
end
Try using scope in controller:
def index
#watchlists = Watchlist.belongs_to_user(current_user)
render json: #watchlists, each_serializer: WatchlistOnlySerializer, scope: { currency: params[:currency] }
end
And in your serializer:
def market_value
Balance.watchlist_market_value(self.id, scope[:currency])
end
You can send your params to your serializer like this
render json: #watchlists, each_serializer: WatchlistOnlySerializer, current_params: currency
and in your serializer you can use this to get the value
serialization_options[:current_params]

respond_with having issue with Rails 5

I am upgrading my Rails app from 4.1.1 to 5.1.4.
I am using roar-rails gem to parsing and rendering REST documents. I am facing some issues as responders gem has been extracted to separate gem.
respond_with has been moved to 'responders' gem.
My rails 4 code lookgs like this:
PostsController:
class PostsController < ApplicationController
respond_to :json
def index
posts = current_user.posts
respond_with posts, :represent_with => PostsRepresenter, current_user: current_user
end
end
My representers for Post
module PostsRepresenter
# Rails 4 code
# include Roar::Representer::JSON::HAL
# Rails 5 code (after adding responders) --------
include Roar::JSON
include Roar::Hypermedia
# Rails 5 code --------
collection(
:post,
class: Post,
extend: PostRepresenter,
embedded: true)
link :make do |args|
p "............. #{args.inspect}"
# In rails 4, args are coming correctly what is passing from Controller
# But in rails 5, args is coming `nil`
posts_path if args[:current_user].can_create?(Post)
end
end
Post representer
module PostRepresenter
# Rails 4 code
# include Roar::Representer::JSON::HAL
# Rails 5 code (after adding responders) --------
include Roar::JSON
include Roar::Hypermedia
# Rails 5 code --------
property :title
property :description
property :author
link :self do |args|
post_path(id) if args[:current_user].can_read?(self)
end
link :remove do |args|
post_path(id) if args[:current_user].can_delete?(self)
end
link :edit do |args|
post_path(id) if args[:current_user].can_update?(self)
end
end
I am facing issue with args which are passing through Controller,
after rails 5, its coming nil.
I have debug the issue and found that in responders gem, options are coming in respond_with method, but I think it could not send it to roar-rails.
/path-to-gem/vendor/responders-master/lib/action_controller/respond_with.rb
Here is snippet:
def respond_with(*resources, &block)
if self.class.mimes_for_respond_to.empty?
raise "In order to use respond_with, first you need to declare the " \
"formats your controller responds to in the class level."
end
mimes = collect_mimes_from_class_level
collector = ActionController::MimeResponds::Collector.new(mimes, request.variant)
block.call(collector) if block_given?
if format = collector.negotiate_format(request)
_process_format(format)
options = resources.size == 1 ? {} : resources.extract_options!
options = options.clone
options[:default_response] = collector.response
p "====================== options :: #{options.inspect}"
# Options are correct till here but coming `nil` on representers
(options.delete(:responder) || self.class.responder).call(self, resources, options)
else
raise ActionController::UnknownFormat
end
end
Please let me know what needs to be done here that make args
available in representers
respond_with was deprecated from rails 4.2.1.
https://apidock.com/rails/ActionController/MimeResponds/respond_with

How to render json with extra data with active_model_serializer on rails?

Using Rails 4.1.6 and active_model_serializers 0.10.3
app/serializers/product_serializer.rb
class ProductSerializer < ActiveModel::Serializer
attributes :id, :title, :price, :published
has_one :user
end
app/controllers/api/v1/products_controller.rb
class Api::V1::ProductsController < ApplicationController
respond_to :json
def index
products = Product.search(params).page(params[:page]).per(params[:per_page])
render json: products, meta: pagination(products, params[:per_page])
end
end
When I check the response body, it shows the products data only:
[{:id=>1, :title=>"Side Auto Viewer", :price=>"1.6999510872877", :published=>false, :user=>{:id=>2, :email=>"indira.sawayn#watsica.us", :created_at=>"2016-12-29T03:44:40.450Z", :updated_at=>"2016-12-29T03
:44:40.450Z", :auth_token=>"ht7CsFWM1hvSGKM_zPmU"}}, {:id=>2, :title=>"Direct Gel Mount", :price=>"56.7935950121941", :published=>false, :user=>{:id=>3, :email=>"jaye.rolfson#leuschke.info", :created_at=>
"2016-12-29T03:44:40.467Z", :updated_at=>"2016-12-29T03:44:40.467Z", :auth_token=>"MTK_5rkFv8E6Fy7gyAtM"}}, {:id=>3, :title=>"Electric Tag Kit", :price=>"46.4689779902597", :published=>false, :user=>{:id=
>4, :email=>"tatiana#moen.co.uk", :created_at=>"2016-12-29T03:44:40.479Z", :updated_at=>"2016-12-29T03:44:40.479Z", :auth_token=>"fTd8z7PCLHxZ7aewLPDY"}}, {:id=>4, :title=>"Remote Tuner", :price=>"48.2478
906626996", :published=>false, :user=>{:id=>5, :email=>"pauline.gaylord#hettinger.info", :created_at=>"2016-12-29T03:44:40.486Z", :updated_at=>"2016-12-29T03:44:40.486Z", :auth_token=>"XC7ZhcyfPrpEyDw-M15
1"}}]
The extra data meta was not been picked up. Is this active_model_serializers version not support that? Or is there a way can get extra data?
Edit
The pagination method:
def pagination(paginated_array, per_page)
{ pagination: { per_page: per_page.to_i,
total_pages: paginated_array.total_pages,
total_objects: paginated_array.total_count } }
end
I had the same issue, and it's been fixed by specifying the adapter to use in the call to render method:
app/controllers/api/v1/products_controller.rb
class Api::V1::ProductsController < ApplicationController
respond_to :json
def index
products = Product.search(params).page(params[:page]).per(params[:per_page])
render json: products, meta: pagination(products, params[:per_page]), adapter: :json
end
end

Rails 4 - Customizing (json) format of response objects in Rails

I have a Rails Controller who responds with JSON objects. Let's take this theoretical example :
respond_to :json
def index
respond_with Comment.all
end
This would respond with something like
[{"id":1,"comment_text":"Random text ", "user_id":1 ,"created_at":"2013-07-26T15:08:01.271Z","updated_at":"2013-07-26T15:08:01.271Z"}]
What i'm looking for is a "best practice" method to interfere with the formating of the json object and return something like this :
[{"id":1,"comment_text":"Random text ", "username": "John Doe", "user_id":1 ,"created_at":"3 hours ago"}]
As you can see, i'm adding a column that doesn't exist in the database model "username" , i'm taking out "updated_at" , and i'm formatting "created_at" to contain human readable text rather than a date.
Any thoughts anyone ?
Overwriting as_json or working with JSON ERB views can be cumbersome, that's why I prefer using ActiveModel Serializers (or RABL):
class CommentSerializer < ActiveModel::Serializer
include ActionView::Helpers::DateHelper
attributes :id, :created_at
def created_at
time_ago_in_words(object.created_at)
end
end
Look here for more information:
https://github.com/rails-api/active_model_serializers
https://github.com/nesquena/rabl
2 ways:
first: define a view, where you build and return an hash that you'll convert to json.
controller:
YourController < ApplicationController
respond_to :json
def index
#comments = Comment.all
end
end
view: index.json.erb
res = {
:comments => #comments.map do |x|
item_attrs = x.attributes
item_attrs["username"] = calculate_username
end
}
res.to_json.html_safe
second: use gem active_model_serializers
I'd redefine the as_json method of your model.
In your Comment model,
def username
"John Doe"
end
def time_ago
"3 hours ago"
end
def as_json(options={})
super(:methods => [:username, :time_ago], except: [:created_at, :updated_at])
end
You don't have to change your controller
Take a look at the documentation for as_json

Resources