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:
- Because your serializer is a plain Ruby class, it's easy to write & test
- It's easy to share the JSON logic between controllers
- It's very extensible: when you need JSON for a different purpose, simply add a new serializer and use it.
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!