JSON-LDとRails

More than 1 year has passed since last update.


TokyuRuby会議11 (2016-07-29) 発表のスライドです

#tqrk11



@tkawa



JSON-LDとは

コンピュータが理解できるLinked Dataを表すためのJSONベースのフォーマット

コンピュータが理解できる → Googleクローラが理解できる → リッチな検索結果

Googleにデータの意味を伝えるためのフォーマット(の1つ)として使われることが多い


  • BreadcrumbList

  • Recipe

  • Product

  • Article (NewsArticle, BlogPosting)

  • Review

  • Event

  • SoftwareApplication

  • ...

AMPでも使う


豚肉と茄子の生姜焼き2.png

{

"@context": "http://schema.org/",
"@type": "Recipe",
"@id": "https://park.example.co.jp/recipe/card/708850",
"name": "豚肉と茄子の生姜焼き",
"image": "https://park.example.co.jp/_var/images/recipe-master/142518/708850.jpg",
"author": {
"@type": "Person",
"name": "キャシィ塚本",
"image": "https://park.example.co.jp/resources_sp/images/common/cathy.png",
"description": "四万十川料理専門学校の講師です。"
},
"publisher": {
"@type": "Organization",
"name": "EXAMPLE株式会社",
"url": "http://www.example.co.jp/",
"logo": {
"@type": "ImageObject",
"url": "https://park.example.co.jp/resources_sp/images/common/logo_examplepark.png"
}
},
"datePublished": "2017-06-08",
"dateModified": "2017-06-08",
"description": "「豚肉と茄子の生姜焼き」のレシピ・作り方です。",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "90",
"reviewCount": "16",
"bestRating": "100",
"worstRating": "80"
},
"recipeYield": "2(servings)",
"totalTime": "PT10M",
"recipeIngredient": [
"豚こま切れ肉90g",
"なす2個(160g)",
"片栗粉大さじ1",
"ごま油大さじ1",
"キャベツのせん切り2枚分(100g)"
],
"recipeInstructions": [
"(1)なすはタテ5mm幅の薄切りにする。豚肉、なすの表面に片栗粉をまぶす。",
"(2)フライパンにごま油を熱し、(1)の豚肉・なすを入れて火が通るまで焼く。",
"(3)そのままドォーン!!!!"
],
"recipeCuisine": "和風",
"nutrition": {
"@type": "NutritionInformation",
"calories": "248kcal",
"carbohydrateContent": "20.9g",
"cholesterolContent": "30mg",
"fiberContent": "2.6g",
"proteinContent": "12.9g",
"sodiumContent": "1,171mg"
}
}

これを <script type="application/ld+json"> の中に書くという仕様。



さて、Railsでどうやって実装しますか?



例えばモデルに書く

# app/models/recipe.rb

def to_jsonld
{
'@context': 'http://schema.org/',
'@type': 'Recipe',
'@id': recipe_url, # ← ?
'name': name,
'image': image_url, # ← ?
...
}.to_json
end

URLが出せない(出しづらい) :persevere:

なぜならJSONもビューだから



JSONをビューとして扱う

# config/initializers/mime_types.rb

Mime::Type.register 'application/ld+json', :jsonld

URLに .jsonld もしくは

リクエストヘッダ Accept: application/ld+json

jsonldフォーマットのビューテンプレートが使われる。

# 今回は必要ないが、フォーマットによって処理を変える場合

respond_to do |format|
format.html { ... }
format.jsonld { ... }
end



テンプレートは rails/jbuilder が定番

# app/views/recipes/show.jsonld.jbuilder

json.set! :@context, 'http://schema.org/'
json.set! :@type, 'Recipe'
json.set! :@id, recipe_url(@recipe)
json.name @recipe.name
json.image image_url(@recipe)
...

個人的にはDSLが慣れない。。 :disappointed_relieved:



そこで amatsuda/jb

# app/views/recipes/show.jsonld.jb

{
'@context': 'http://schema.org',
'@type': 'Recipe',
'@id': recipe_url(@recipe),
name: @recipe.name,
image: image_url(@recipe),
...
}

Hashそのまま書ける!


  • 普通にrubyで書く

  • 返されたオブジェクトを to_json した文字列がrenderされる

  • jbuilderより速い



さらに

# app/views/recipes/show.jsonld.jb

{
'@context': 'http://schema.org',
'@type': 'Recipe',
'@id': recipe_url(@recipe),
name: @recipe.name,
image: image_url(@recipe),
author: {
'@type': 'Person',
name: @recipe.author.name,
image: image_url(@recipe.author),
description: @recipe.author.description
},
...
}

jb は jbuilder と同様にパーシャルが使えるので、ネストした author を……



パーシャルでDRY

# app/views/recipes/show.jsonld.jb

{
'@context': 'http://schema.org',
'@type': 'Recipe',
'@id': recipe_url(@recipe),
name: @recipe.name,
image: image_url(@recipe),
author: render(@recipe.author),
...
}

# app/views/authors/_author.jsonld.jb
{
'@type': 'Person',
name: author.name,
image: image_url(author),
description: author.description
}

recipe 以外のところでも author が再利用可能に



さらにパーシャル化

# app/views/recipes/show.jsonld.jb

{
'@context': 'http://schema.org',
'@id': recipe_url(@recipe)
}.merge(render(@recipe))

# app/views/recipes/_recipe.jsonld.jb
{
'@type': 'Recipe',
name: @recipe.name,
image: image_url(@recipe),
author: render(@recipe.author),
...
}

こうすれば recipe 自体も再利用可能(例えば recipe の List など)

HTMLのパーシャルの使い方と同じ

もちろんこの手法はWeb APIでも使えます

JSON-LDもWeb APIとして使えます



HTMLに埋め込むには

# app/helpers/application_helper.rb

def jsonld_script_tag
jsonld = controller.render_to_string(formats: :jsonld)
content_tag :script, jsonld.html_safe, type: Mime[:jsonld].to_s
rescue ActionView::ActionViewError => e
logger.error e.message
nil
ensure
# render_to_string のバグ回避 https://github.com/rails/rails/issues/14173
lookup_context.rendered_format = nil
end

controller.render_to_string すると、オブジェクトを渡したりしなくても現在のリクエストの状態のままレンダリングした文字列が得られる



HTML以外のものが必要になっても、別のフォーマットのビューと考えてHTMLと同じように作る



どんなフォーマットが来てもうまくいく!