🦔
Railsでmodelから特定のテンプレートをレンダリングしてJSONを出力する
前提
- RailsでAPIを作っています
- データをJSONで返却するcontrollerのactionがあります
- この記事ではjbuilderを使っています
やりたいこと
- controller以外で、特定のcontrollerのactionと同じテンプレートを使い回してJSONを出力したい
- 今回はmodelでレンダリングします
- JSONを出力してファイルをアップロードする機能の開発でした
- 今回はmodelでレンダリングします
サンプルアプリケーションの説明
以下のようにAPIモードでrails newして、scaffoldを使って簡易的に作りました。
Ruby3.1, Rails7.0.1を使用しています。
rails new sample_app --api
# (省略)jbuilderとrspecをbundle installしています
bin/rails generate scaffold Products name:string price:integer
invoke active_record
create db/migrate/20220205143831_create_products.rb
create app/models/product.rb
invoke rspec
create spec/models/product_spec.rb
invoke resource_route
route resources :products
invoke scaffold_controller
create app/controllers/products_controller.rb
invoke resource_route
invoke rspec
create spec/requests/products_spec.rb
create spec/routing/products_routing_spec.rb
invoke jbuilder
create app/views/products
create app/views/products/index.json.jbuilder
create app/views/products/show.json.jbuilder
create app/views/products/_product.json.jbuilder
説明に必要なコードを貼ります。
class ProductsController < ApplicationController
def index
@products = Product.all
end
end
# index.json.jbuilder
json.array! @products, partial: "products/product", as: :product
# _product.json.jbuilder
json.extract! product, :id, :name, :price, :created_at, :updated_at
json.display_info "商品名: #{product.name}, 価格: #{product.price}"
方法
ActionController::Renderer
を使うことで、任意のテンプレートをcontrollerのactionを介さずにレンダリングできます。
サンプルアプリケーションのProductsController#index
と同じJSONをmodelでレンダリングします。
class JsonRenderer
def render_products
# controllerのactionを指定して、レンダリングで使われるデータを渡します
ProductsController.render :index, assigns: { products: Product.all }
end
end
上の書き方以外には、以下のような方法もあります。
# actionではなくtemplateを指定する方法もあります
ApplicationController.render template: '/products/index', assigns: { products: Product.all }
# #forを使うことでcontrollerを引数に渡してレンダリングすることもできます
ActionController::Renderer.for(ProductsController).render :index, assigns: { products: Product.all }
rails consoleで確認したときのログです。
ProductsController.render :index, assigns: { products: Product.all }
Rendering products/index.json.jbuilder
(0.1ms) SELECT sqlite_version(*)
Product Load (0.4ms) SELECT "products".* FROM "products"
Rendered collection of products/_product.json.jbuilder [3 times] (Duration: 0.8ms | Allocations: 448)
Rendered products/index.json.jbuilder (Duration: 7.4ms | Allocations: 2960)
=> "[{\"id\":1,\"name\":\"イヤホン\",\"price\":5000,\"created_at\":\"2022-02-05T14:48:09.618Z\",\"updated_at\":\"2022-02-05T14:48:09.618Z\",\"display_info\":\"商品名: イヤホン, 価格: 5000\"},{\"id\":2,\"name\":\"スピーカー\",\"price\":20000,\"created_at\":\"2022-02-05T14:48:18.642Z\",\"updated_at\":\"2022-02-05T14:48:18.642Z\",\"display_info\":\"商品名: スピーカー, 価格: 20000\"},{\"id\":3,\"name\":\"カメラ\",\"price\":50000,\"created_at\":\"2022-02-05T14:48:35.626Z\",\"updated_at\":\"2022-02-05T14:48:35.626Z\",\"display_info\":\"商品名: カメラ, 価格: 50000\"}]"
Discussion