Active Model Serializers pagination links not showing - ruby-on-rails

I'm using the instructions here: https://github.com/rails-api/active_model_serializers/blob/master/docs/howto/add_pagination_links.md
I have an initializer file with:
ActiveModelSerializers.config.adapter = :json_api
(restarted the server to initialize)
In my model I have:
def packages
#user = User.find(params[:id])
#records = #user.get_records("api").paginate(page: params[:page], per_page: 3)
render json: #records
end
My response is as follows and is not showing the links block:
[
{
created_at: "2016-04-07T18:24:03.216Z",
title: "Record 1"
},
{
created_at: "2016-03-07T18:24:43.245Z",
title: "Record 2"
},
{
created_at: "2016-02-07T18:22:33.236Z",
title: "Record 3"
}
]
I can see in my server log that it's using will_paginate:
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with WillPaginate::Collection (0.13ms)
Any ideas why I'm not seeing the links?
Update:
I am paginating an array with will_paginate, therefore I added require 'will_paginate/array' in the controller.
When I try to follow the second method of creating the dictionary in the controller like so...
class Api::V1::UsersController < ActionController::Base
require 'will_paginate/array'
def packages
#user = User.find(params[:id])
#records = #user.get_records("api").paginate(page: params[:page], per_page: 3)
render json: #records,
meta: pagination_dict(#records)
end
def pagination_dict(object)
{
current_page: object.current_page,
next_page: object.next_page,
prev_page: object.prev_page,
total_pages: object.total_pages,
total_count: object.total_count
}
end
end
Then I get:
undefined method `prev_page' for #<WillPaginate::Collection:0x007fced3698d78> Did you mean? previous_page per_page
Which I find also strange.

The function pagination_dict(object) should be in your Base controller and not in the Users Controller

Related

Rails display only published episodes from Simplecast api

I'm running a rails application that calls Simplecasts API to display my podcast episodes. I followed a tutorial to setup the API services using Faraday. My question is how to only display published episodes on my index page? Normally, I would add a .where(:status => "live") in my controller, IE #podcasts = Episodes.where(:status => "published") but this doesn't seem to work.
Simplecast's API for the podcast returns a collection that contains all the available episodes, each has a status node.
Any help would be appreciated as I'm new to working with external APIs in Rails
Sample API response
"collection": [
{
"updated_at": "2020-03-25T17:57:00.000000-04:00",
"type": "full",
"token": "lgjOmFwr",
"title": "Test",
"status": "draft",
Episode.rb
module Simplecast
class Episodes < Base
attr_accessor :count,
:slug,
:title,
:status
MAX_LIMIT = 10
def self.episodes(query = {})
response = Request.where('/podcasts/3fec0e0e-faaa-461f-850d-14d0b3787980/episodes', query.merge({ number: MAX_LIMIT }))
episodes = response.fetch('collection', []).map { |episode| Episode.new(episode) }
[ episodes, response[:errors] ]
end
def self.find(id)
response = Request.get("episodes/#{id}")
Episode.new(response)
end
def initialize(args = {})
super(args)
self.collection = parse_collection(args)
end
def parse_collection(args = {})
args.fetch("collection", []).map { |episode| Episode.new(episode) }
end
end
end
Controller
class PodcastsController < ApplicationController
layout "default"
def index
#podcasts, #errors = Simplecast::Episodes.episodes(query)
#podcast, #errors = Simplecast::Podcast.podcast(query)
render 'index'
end
# GET /posts/1
# GET /posts/1.json
def show
#podcast = Simplecast::Episodes.find(params[:id])
respond_to do |format|
format.html
format.js
end
end
private
def query
params.permit(:query, {}).to_h
end
end
Looks like collection is just an array of hashes so rails ActivrRelations methods aka .where are not supported. However It is an array so you can just filter this array:
published_episodes = collection.filter { |episode| episode[:status] == “ published” }
Also look through their API - may be the do support optional filtering params so you would get only published episodes in the first place.
BTW: second thought is to save external API request data in your own DB and then fetch require episodes with standard .where flow.

Rails API Serializer

I want to combine 2 models and sort them out and expose the result on my serializer.
so in my user controller I have
#users_controller.rb
def transactions
#debit = current_user.debits.all
#credit = current_user.credits.all
#history = [#debit, #credit].flatten
#sorted_history = #history.sort_by { |item| item.created_at.strftime('%m/%d/%y') }
render json: UserTransactionSerializer.new(#sorted_history).call
end
and on my serializer I have:
module Api
module V1
class UserTransactionSerializer < BaseSerializer
def call
{
id: object.id,
amount: object.amount,
status: object.status,
created_at: object.created_at.iso8601
}
end
end
end
end
I'm getting this error bellow:
NoMethodError (undefined method `id' for #<Array:0x0055ce49b57b20>)
EDIT: Typo
Your #sorted_history is an array, but UserTransactionSerializer should be passed in an object.
So easy solution would be:
render json: #sorted_history.map{ |trans| UserTransactionSerializer.new(trans).call }
object is an Array, so you have to iterate and return hash for each object in that Array.
def call
object.map do |obj|
{
id: obj.id,
amount: obj.amount,
status: obj.status,
created_at: obj.created_at.iso8601
}
end
end

Rails pagination does not load content for second page for search results

I have a method to get the search result and show and it is working fine. But, whenever I try to go to second page, it says error regarding no content.
Here is my controller code:
class BuyersController < UsersController
def show
end
def search
end
def search_products
search_name = params[:search][:name]
search_category = params[:search][:category_id].to_i
search_location_id = params[:search][:location_id].to_i
search_highest_bid = params[:search][:highest_bid].to_f
#matching_products = ProductsUnderBid.search_products_under_bid(name: search_name, category_id: search_category,
location_id: search_location_id, highest_bid: search_highest_bid)
#matching_products = #matching_products.paginate(page: params[:page], per_page: 1)
end
end
Try to do it deleting the per_page: 1 so you will have to put the code like this:
#matching_products = #matching_products.paginate(page: params[:page])
I changed the methods like below and it worked! As you can see, I cahnged the variable types to global in search_products method so that I can access it from show method.I also had to add show.html.erb under the in app/view/controller_name/ folder. Hope it helps others.
def show
#matching_products = ProductsUnderBid.search_products_under_bid(name: $search_name, category_id: $search_category,
location_id: $search_location_id, highest_bid: $search_highest_bid)
#matching_products = #matching_products.paginate(:page => params[:page],:per_page => 5)
get_already_placed_bids(#matching_products)
end
def search_products
$search_name = params[:search][:name]
$search_category = params[:search][:category_id].to_i
$search_location_id = params[:search][:location_id].to_i
$search_highest_bid = params[:search][:highest_bid].to_f
#matching_products = ProductsUnderBid.search_products_under_bid(name: $search_name, category_id: $search_category,
location_id: $search_location_id, highest_bid: $search_highest_bid)
#matching_products = #matching_products.paginate(:page => params[:page],:per_page => 5)
get_already_placed_bids(#matching_products)
end

How to render json in a rake task

I want to move the below logic to somewhere else so I can use it both in my controller and in a rake task.
My controller action looks something like this:
def show
#user = User.find(params[:id])
#account = # load account
#sales = # load sales
..
render :json => {
"user": user,
"account": #account.map do |a|
JSON.parse(a.to_json(include: :addresses))
end,
"sales": #sales.map do |s|
JSON.parse(s.to_json(include: :products))
end
}
end
Basically the point is that I have to traverse the associations so the JSON has all of the data in it.
How can I move this logic somewhere else so I can then call it in my controller action and also in a rake task.
Extract the code to a presenter or use ActiveModel::Serializers, so that the controller and the Rake task call this new class.
class UserPresenter
def initialize(user, account, sales)
#user = user
#account = account
#sales = sales
end
def as_json(*)
{
"user": #user,
"account": #account.map do |a|
JSON.parse(a.to_json(include: :addresses))
end, # or #account.as_json(include: :addresses))
"sales": #sales.map do |s|
JSON.parse(s.to_json(include: :products))
end # or #sales.as_json(include: :products))
}
end
end
# In the controller
render json: UserPresenter.new(#user, #account, #sales)

How to add virtual attributes to API json response

Context: ruby "2.0.0" and rails "4.0.0"
I am wondering how to return more data than a simple JSON representation of my model. I have this method in my API controller:
def show
#entry = params[:id].to_i != 0 ? Entry.find(params[:id]) : Entry.where(slug: params[:id]).first
respond_with #entry
end
which returns a well formatted JSON article when called via http://pow.url.dev/en/tenant/api/v1/entries/23.
I also have two other method to get the next and previous article, for instance, next looks like this:
def next
#current_entry = params[:entry_id].to_i != 0 ? Entry.find(params[:entry_id]) : Entry.where(slug: params[:entry_id]).first
#entry = Entry.where( status_id: 0 ).where( "published_at > ?", #current_entry.published_at ).first
respond_with #entry
end
The problem is that the client application needs to make 3 API calls to 1) fetch an article, 2) fetch the next article and 3) fetch the previous article.
It would be much better if the client had to do only one call to get all the information at once.
I would like to add into the show method the Title and ID of the next and previous article along with the JSON result, what would be the best way to do that?
Quick and dirty:
def show
#entry = ...
render :json => #entry.attributes.merge({
:next_id => id_of_next_article,
:next_title => title_of_next_article...
})
end
More flexible solution: use some sort of JSON serialization customizers (or roll your own), one I've used before is ActiveModel Serializers (https://github.com/rails-api/active_model_serializers). I'm sure there are others.
Will the user always use the Next and the Prev articles? If so, put them together on the JSON, otherwise, use link location tags with REL to prev and next on the returning JSON, then the clint will be able to know where to get the next and prev articles. You can get more info: RESTful Web Service usage of custom link relations - 'rel'
Here is the solution I came up with:
Implementing two methods in the Entry model object for next and previous as follows:
def next
Entry.where( status_id: 0 ).where( "published_at > ?", self.published_at ).select('id, title, slug').first
end
def previous
Entry.where( status_id: 0 ).where( "published_at < ?", self.published_at ).select('id, title, slug').last
end
and replace respond_with by render :json in the show action of my controller:
def show
#entry = params[:id].to_i != 0 ? Entry.find(params[:id]) : Entry.where(slug: params[:id]).first
render :json => #entry.to_json(:include => :entry_fields,
:methods => [:next, :previous])
end
Output:
{
"id": 20,
"title": "Video Test",
"slug": "video-test",
"status_id": 0,
"promoted": true,
"published_at": "2014-01-20T11:51:00.000Z",
"created_at": "2014-01-20T11:51:37.406Z",
"updated_at": "2014-03-05T13:42:49.981Z",
"excerpt": "Video",
"user_id": "933dc175-0d73-45fb-9437-61ecc4f55705",
"next": {
"id": 21,
"title": "Test Multiple",
"slug": "test-multiple"
},
"previous": {
"id": 18,
"title": "Example Entry",
"slug": "example-entry"
},
...
... (associations)
...
}

Resources