I already have Rails API controller which return JSON response. It is used by front-end Javascript (as well as mobile app) to render values.
Now, I wish to prerender those values using ReactJS:
#app/controllers/api/v1/products_controller.rb
module API
module V1
class ProductsController < ApplicationController
def index
@products = Product.all #this could be acomplex multi-line statements.
#rendered in api/v1/products/index.json.jbuilder
end
end
end
end
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
#How to do this efficiently?
@products_json = #Call to internal /api/v1/products/index for prerender purpose.
@user_json = #Call to internal /api/v1/user/show for prerender purpose.
end
end
#app/views/products/index.html.erb
<%= react_component('ProductsList', @products_json, {prerender: true}) %>
<%= react_component('UserProfile', @user_json, {prerender: true}) %>
How do I call internal /api/v1/products
and /api/v1/user
URL efficiently (e.g. without making HTTP GET request to my own server)?
I agree with your desire to reuse your API code for your views. That will make the application much more maintainable.
What if you changed the scope a little bit? Instead of calling a controller method, move the logic into a new Ruby class.
This class's job is to turn an object into a JSON string, so it's called a "serializer". In my app, we have app/serializers/{model_name}/
for storing different serializer classes.
Here's an example serializer:
# app/serializers/product/api_serializer.rb
class Product::APISerializer
attr_reader :product, :current_user
def initialize(product, current_user)
@product = product
@current_user = current_user
end
# Return a hash representation for your object
def as_json(options={}) # Rails uses this API
{
name: product.name,
description: product.description,
price: localized_price,
categories: product.categories.map { |c| serialize_category(c) },
# ... all your JSON values
}
end
private
# For example, you can put logic in private methods of this class.
def localized_price
current_currency = current_user.currency
product.price.convert_to(current_currency)
end
def serialize_category(category)
{ name: category.name }
end
end
Then, use this serializer to build your API response:
class API::V1::ProductsController < ApplicationController
def index
products = Product.all
products_json = products.map do |product|
serializer = Product::APISerializer.new(product, current_user)
serializer.as_json
end
render json: products_json
end
end
Then, you can use the serializer again in the UI controller:
class ProductsController < ApplicationController
def index
products = Product.all
@products_json = products.map do |product|
serializer = Product::APISerializer.new(product, current_user)
serializer.as_json
end
# render view ...
end
end
Because you used the same serializer in both cases, the JSON representation of the products will be the same!
There are a few advantages to this approach:
Some people use ActiveModel Serializers for this, but I don't. I tried AMS a year ago and I didn't like it because it overrides as_json
for all objects in your app, which caused breaking changes in my case!
Try this:
def index
@products = Product.all
@products_json render_to_string('/api/v1/products/index', formats: [:json])
# etc...
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With