multiresources GET /search API implemented with JSONAPI - ruby-on-rails

How would you implement a /search API that could return different resources with JSONAPI ?
GET /search?q=london could for example return an author resource for 'Jack London' and a book resource for 'London: The Novel'
I started doing this by implementing a search resource that would return included book and author resources but to follow JSONAPI, I'd have to refactor the API to GET /search?filger[q]=london
I have the feeling that this (quite common) usecase has not really been adressed by JSONAPI.
My final goal is to implement this with a Rails backend using JSONAPI::Resources and an ember app with ember-data
What would be your advice ?

You could do it manually using the jsonapi-utils gem so that you could point the resource you would like to use for the serialization:
With the jsonapi_serialize method:
class SearchesController < BaseController
def index
results = MySearchEngine.search(params)
render json: jsonapi_serialize(results, options: { resource: UserResource })
end
end
Or the high-level jsonapi_render method:
class SearchesController < BaseController
def index
results = MySearchEngine.search(params)
jsonapi_render json: results, options: { resource: UserResource }
end
end
Hope it helps.

Here's what I did:
# config/routes.rb
resource :search, only: [:show]
# app/controllers/searches_controller.rb
def show
render json: Search.new(params).as_json
end
# app/classes/search.rb
class Search
def initialize(params = {})
#results = [
Author.where(name: params[:q]),
Book.where(title: params[:q]),
].flatten
end
def as_json
serialized_resources =
#results.map do |result|
resource = JSONAPI::Resource.resource_for(result.model_name.singular)
serializer = JSONAPI::ResourceSerializer.new(resource)
serializer.serialize_to_hash(resource.new(result, nil))[:data]
end
{ data: serialized_resources }
end
end
Surely, there's a better way?

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

Rails - How to paginate json associations, using as_json include/methods

Let's say I have a Post that has many Comments. I have something like this in my model
class Post < AR:Base
...
PUBLIC_ATTRIBUTES = [:id, :title]
JSON_METHODS = [:comments]
...
def as_json(options={})
opts = {
only: PUBLIC_ATTRIBUTES,
methods: JSON_METHODS
}.merge(options){|k,o,n|o|n}
super(opts)
end
...
end
So in my controller I can just:
def show
post = Post.find ...
render json: { post: post }
end
JSON magically appears. The issue is when I have a lot of comments I want to paginate on. Also, the API is already in use, so people are using json[post][comments], otherwise i'd just add comments in like {post:post, comments:comments}, but that's a no-go.

Disable pagination for relationships

Given 2 resources:
jsonapi_resources :companies
jsonapi_resources :users
User has_many Companies
default_paginator = :paged
/companies request is paginated and that's what I want. But I also want to disable it for relationship request /users/4/companies. How to do this?
The best solution I found will be to override JSONAPI::RequestParser#parse_pagination like this:
class CustomNonePaginator < JSONAPI::Paginator
def initialize
end
def apply(relation, _order_options)
relation
end
def calculate_page_count(record_count)
record_count
end
end
class JSONAPI::RequestParser
def parse_pagination(page)
if disable_pagination?
#paginator = CustomNonePaginator.new
else
original_parse_pagination(page)
end
end
def disable_pagination?
# your logic here
# request params are available through #params or #context variables
# so you get your action, path or any context data
end
def original_parse_pagination(page)
paginator_name = #resource_klass._paginator
#paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
rescue JSONAPI::Exceptions::Error => e
#errors.concat(e.errors)
end
end

RAILS API Endpoint

I am a .NET developer and need to work on a API built in Ruby. Following is the API Code. Can anybody help me in getting the endpoint to it.
class Api::SessionsController < ApplicationController
respond_to :json
skip_before_filter :verify_authenticity_token
before_filter :protect_with_api_key
def update
status = true
participant_ids = []
unless params[:participants].blank?
params[:participants].each do |participant_data|
participant = Participant.where(participant_id: participant_data['participant_id']).first
unless participant.present?
status = false
participant_ids << participant_data['participant_id']
else
activity_records = participant_data['cumulative_time']['activity_records']
participant_data['cumulative_time']['activity_records'] = [] if activity_records.nil?
participant.participant_sessions.new(participant_data['cumulative_time'])
participant.save!
end
end
end
if status
render :json => {status: "OK"}
else
render :json => {error: "No participant with id # {participant_ids.join(',')}"}, :status => 422
end
end
end
I have tried to work with following way /api/sessions/
Pass the required key
passing the
participant parameter with PUT Request like below
{"participants":[{"first_name":"Demo", "last_name":"User", "email":"demouser#demouser.com", "password":"RubyNewBie","postal_code":"160055", "coordinator_name":"RubyNewBie", "coordinator_email":"info#RubyNewBie", "coordinator_phone":""}]}
Please guide me.
Thanks and Regards
By default, update action routes to /api/sessions/:id, so you should make query to that url. Also make sure that you have your route for session set up in routes.rb
Edit:
namespace :api do
resources :participants do
resources :sessions
end
end
If it looks like that, then you're using nested routing. Check:
http://guides.rubyonrails.org/routing.html#nested-resources
You'll have to use the url /api/participants/:participant_id/sessions/:session_id under that setting.

Rails: How do I exclude records with entries in a join table?

I have a rails 4.1 app with a work_tasks table and a work_task_statuses table.
They are joined together on work_task_statuses.id = work_tasks.id.
In my controller, I want to pull up only the work_tasks that do not have any records in the work_task_statuses table.
Here is models/work_tasks.rb:
require 'rails'
require 'filterrific'
class WorkTask < ActiveRecord::Base
has_many :work_task_statuses
scope :without_work_task_statuses, includes(:work_task_statuses).where(:work_task_statuses => { :id => nil })
end
Here is work_tasks_controller.rb:
require 'builder'
require 'will_paginate'
include ActionView::Helpers::NumberHelper
class WorkTasksController < ApplicationController
before_action :authenticate_user!
def list
end
def index
#filterrific = Filterrific.new(WorkTask, params[:filterrific])
#filterrific.select_options = {
sorted_by: WorkTask.options_for_sorted_by,
with_work: WorkTask.options_for_select
}
#work_tasks WorkTask.filterrific_find(#filterrific).page(params[:page]).without_work_task_statuses(#work_tasks)
respond_to do |format|
format.html
format.js
end
end
def reset_filterrific
# Clear session persistence
session[:filterrific_work_tasks] = nil
# Redirect back to the index action for default filter settings.
redirect_to action: :index
end
end
I am getting this error:
undefined method `call' for #<WorkTask::ActiveRecord_Relation:0x007fa0eeab4870>
How do I make the controller exclude work tasks that have records in work_task_statuses?
With the help of #jkeuhlen (who helped me to realize that the scope syntax had changed in rails 4) and via some further research I solved my problem.
For the benefit of future searchers, here is the final working code:
Model:
scope :with_work_task_statuses, -> {where.not(:id => WorkTaskStatus.select(:work_task_id).uniq)
}
Controller:
#work_tasks = WorkTask.filterrific_find(#filterrific).page(params[:page]).with_work_task_statuses
Here is a blog post that explains it in more detail: http://www.lauradhamilton.com/how-to-filter-by-multiple-options-filterrific-rails
This question is related to this other SO question.
Using the information on the highest voted answer from that post and applying it to your question:
WorkTask.includes(:work_task_statuses).where( :work_task_statuses => { :work_task_id => nil } )

Resources