Using Rails Path and URL Helpers with fast_jsonapi - ruby-on-rails

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

Related

Rails API active model serializer root node not working

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

multiresources GET /search API implemented with JSONAPI

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?

How to remove API logic from view in Rails Way?

I would like to remove this logic:
Suitcase::Hotel.find(id: hotel.id).images.first.url
from view.
https://gist.github.com/2719479
I dont have model Hotel. I get this url via API using Suitcase gem.
Problem is because
hotel is from #hotels = Suitcase::Hotel.find(location: "%#{headed}%") and API recevie me images only if do Suitcase::Hotel.find(id: hotel.id)
If Suitcase::Hotel.find(id: hotel.id).images.first.url works then i would guess hotel.images.first.url will work too if hotel is an hotel instance.
Is adding:
#hotel = Suitcase::Hotel.find(id: hotel.id)
to #show action doesn't work?
EDIT:
In that case make an helper:
def hotel_image_url(hotel)
Suitcase::Hotel.find(id: hotel.id).images.first.url
end
But as I can see here you can simply write in controller:
#hotels_data = Suitcase::Hotel.find(ids: #hotels.map(&:id))
Or to be more elegant add to your model (or create decorator (it's better option)):
def photo
Suitcase::Hotel.find(id: self.id).images.first.url
end
I think this should work, not sure about the second option
class Search < ActiveRecord::Base
attr_accessible :headed, :children, :localization, :arriving_date, :leaving_date, :rooms, :adults
def hotels
#hotels ||= find_hotels
end
private
def find_hotels
return unless headed.present?
#hotels = Suitcase::Hotel.find(location: "%#{headed}%")
#hotels.each do |hotel|
def hotel.image_url
Suitcase::Hotel.find(id: hotel.id).images.first.url
end
end
end
end
# or this, but I'm not sure if this works
#hotels.each do |hotel|
image_url = Suitcase::Hotel.find(id: hotel.id).images.first.url
def hotel.image_url
image_url
end
end

Is there a way to make a regular ruby class use a erb/haml template?

Basically, I have a class that outputs some html:
class Foo
include ActionView::Helpers
def initialize(stuff)
#stuff = stuff
end
def bar
content_tag :p, #stuff
end
end
so I can do: Foo.new(123).bar
and get "<p>123</p>"
... But what I really want to do is something like this:
class Foo << ActionView::Base
def initialize(stuff)
#stuff = stuff
end
def bar
render :template => "#{Rails.root/views/foo/omg.html.erb}"
end
end
# views/omg.html.erb
<h1>Wow, stuff is <%= #stuff %></h1>
and then do Foo.new(456).bar and get "<h1>Wow, stuff is 456</h1>"
Just call erb directly? Something like:
def bar
template = ERB.new(File.read("#{Rails.root}/views/foo/omg.html.erb"))
template.result(binding)
end

Trouble rendering a view inside a generic class

I'm trying to encapsulate the logic for generating my sitemap in a separate class so I can use Delayed::Job to generate it out of band:
class ViewCacher
include ActionController::UrlWriter
def initialize
#av = ActionView::Base.new(Rails::Configuration.new.view_path)
#av.class_eval do
include ApplicationHelper
end
end
def cache_sitemap
songs = Song.all
sitemap = #av.render 'sitemap/sitemap', :songs => songs
Rails.cache.write('sitemap', sitemap)
end
end
But whenever I try ViewCacher.new.cache_sitemap I get this error:
ActionView::TemplateError:
ActionView::TemplateError (You have a nil object when you didn't expect it!
The error occurred while evaluating nil.url_for) on line #5 of app/views/sitemap/_sitemap.builder:
I assume this means that ActionController::UrlWriter is not included in the right place, but I really don't know
Does this do what you're trying to do? This is untested, just an idea.
in lib/view_cacher.rb
module ViewCacher
def self.included(base)
base.class_eval do
#you probably don't even need to include this
include ActionController::UrlWriter
attr_accessor :sitemap
def initialize
#av = ActionView::Base.new(Rails::Configuration.new.view_path)
#av.class_eval do
include ApplicationHelper
end
cache_sitemap
super
end
def cache_sitemap
songs = Song.all
sitemap = #av.render 'sitemap/sitemap', :songs => songs
Rails.cache.write('sitemap', sitemap)
end
end
end
end
then wherever you want to render (I think your probably in your SitemapController):
in app/controllers/sitemap_controller.rb
class SitemapController < ApplicationController
include ViewCacher
# action to render the cached view
def index
#sitemap is a string containing the rendered text from the partial located on
#the disk at Rails::Configuration.new.view_path
# you really wouldn't want to do this, I'm just demonstrating that the cached
# render and the uncached render will be the same format, but the data could be
# different depending on when the last update to the the Songs table happened
if params[:cached]
#songs = Song.all
# cached render
render :text => sitemap
else
# uncached render
render 'sitemap/sitemap', :songs => #songs
end
end
end

Resources