導入
Rubyでgenko.meの配信APIに標準添付のnet/httpで接続し、記事一覧と記事詳細を取得して表示するまでの手順。
このページでは、Rubyからgenko.meのblogAPIに接続し、記事一覧と記事詳細を取得します。使うのは標準添付のnet/http・json・uriだけです。
用意するもの
- genko.meのアカウントとワークスペース。APIの作成とAPIキーの発行にはオーナー権限が必要です。
- Ruby 3.1以降。
Data.defineを使う型付きの受け取り方だけはRuby 3.2以降が必要です。追加のgemは要りません。 - リスト形式のAPIが1つと、公開済みの記事が1件。まだない場合はクイックスタートの手順2・3で作れます。
以降のyour-workspaceは自分のワークスペースIDに、blogは自分のエンドポイントに置き換えます。
インストール
ありません。 Gemfileに足すものはなく、以下のコードをプロジェクトに置くだけです。
APIキーとワークスペースIDを設定する
管理画面のURLがhttps://genko.me/w/your-workspaceなら、ワークスペースIDはyour-workspaceです。
APIキーは「設定・プラン」の「APIキー」タブ(/w/your-workspace/settings/api-keys)で作成します。「選択したAPIのみ」でblogだけを許可しておくと、キーが漏れたときの範囲を限定できます。作成直後に表示される値は後から再表示できません。
キーは環境変数から読みます。RailsならRails.application.credentialsでも構いません。
ENV.fetch("GENKO_API_KEY")ENV.fetchは未設定のときに例外になります。ENV["GENKO_API_KEY"]だとnilのまま401を受け取ることになるので、fetchを使ってください。キーをリポジトリに含めないでください。
クライアントを書く
配信APIへのGETを1か所にまとめます。
# frozen_string_literal: true
require "json"require "net/http"require "uri"
module Genko BASE_URL = "https://api.genko.me" WORKSPACE = "your-workspace"
class ApiError < StandardError attr_reader :status
def initialize(status, message) @status = status super(message) end end
# 配信APIをGETして、シンボルキーのHashで返す。 def self.get(path, **queries) uri = URI("#{BASE_URL}/v1/#{WORKSPACE}#{path}") queries = queries.compact uri.query = URI.encode_www_form(queries) unless queries.empty?
request = Net::HTTP::Get.new(uri) request["X-API-KEY"] = ENV.fetch("GENKO_API_KEY")
response = Net::HTTP.start( uri.hostname, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 10 ) { |http| http.request(request) }
body = JSON.parse(response.body, symbolize_names: true) unless response.is_a?(Net::HTTPSuccess) raise ApiError.new(response.code.to_i, body[:message] || "原因不明のエラーです。") end
body endendURI.encode_www_formを使うと、filtersの角括弧や日本語の値がURLエンコードされます。クエリ文字列を自分で連結しないでください。
queries.compactでnilの値を落としているので、指定しないクエリはnilを渡すだけで済みます。
記事一覧を出す
list = Genko.get("/blog", limit: 10, orders: "-publishedAt", fields: "title,publishedAt")
puts "全#{list[:totalCount]}件"list[:contents].each do |post| puts "#{post[:id]} #{post[:title]}"endレスポンスは:contents・:totalCount・:offset・:limitを持つHashです。該当する記事がない場合もエラーにはならず、:contentsが空配列になります。
ERBに渡すときは、テキストフィールドをエスケープしてください。配信APIはテキストをエスケープせずに返します。Railsのテンプレートは既定でエスケープしますが、素のERBではERB::Util.html_escapeを自分で通します。
型を付けて受け取る
Hashのまま扱うとフィールドIDの打ち間違いに気づけません。スキーマに合わせてDataクラスを1つ書いておくと、存在しない項目を読んだ時点でNoMethodErrorになります。
# frozen_string_literal: true
require "time"
# Data.define は Ruby 3.2 以降。3.1 では Struct.new(..., keyword_init: true) を使う。Post = Data.define(:id, :title, :body, :category, :tags, :published_at) do def self.from_entry(entry) new( id: entry[:id], title: entry[:title] || "", body: entry[:body] || "", # 任意項目や、あとから追加したフィールドは古い記事に無いことがある。 category: entry[:category], tags: entry[:tags] || [], published_at: entry[:publishedAt] && Time.iso8601(entry[:publishedAt]) ) endend配信APIのキーはキャメルケース(publishedAt)です。Ruby側でスネークケースにするなら、この変換の場所で揃えます。
fieldsで項目を絞ると、指定しなかったフィールドはレスポンスに含まれません。既定値の|| ""や|| []を省略しないでください。
posts = list[:contents].map { |entry| Post.from_entry(entry) }記事詳細を出す
一覧で得たidをパスの末尾に付けます。
require "erb" # ERB::Util.url_encode。Railsでは不要
def find_post(id) Post.from_entry(Genko.get("/blog/#{ERB::Util.url_encode(id)}"))rescue Genko::ApiError => e raise unless e.status == 404
nilend
post = find_post("your-entry-id")if post.nil? puts "記事が見つかりません。"else puts post.title puts post.published_at&.getlocal("+09:00")&.strftime("%Y年%-m月%-d日") puts post.body # リッチテキストはHTML文字列end一覧と違い、詳細のレスポンスは:contentsで包まれず、コンテンツのHashがそのまま返ります。非公開・削除済み・存在しないIDはどれも404です。
日時はISO 8601のUTC文字列で届きます。表示前に日本時間へ変換してください。
リッチテキスト(body)はHTML文字列なので、そのまま出力します。Railsのビューではrawやhtml_safeが必要です。管理画面で入力された内容がそのまま出るため、信頼できる書き手だけに編集権限を渡してください。 外部から投稿を受け付ける構成では、Sanitizeなどで整えてから出力します。
絞り込む
filtersはフィールドIDと演算子を組み合わせた文字列です。
tech = Genko.get("/blog", filters: "category[equals]tech", orders: "-publishedAt", limit: 10)
recent = Genko.get("/blog", filters: "publishedAt[greater_than]#{(Time.now - 30 * 24 * 60 * 60).utc.iso8601(3)}")演算子の一覧と、[and]/[or]での連結はクエリ仕様を参照してください。
Railsに置く場合
コントローラから直接呼ばず、キャッシュを挟みます。
class BlogPost CACHE_TTL = 5.minutes
def self.recent(limit: 10) Rails.cache.fetch("genko/blog/recent/#{limit}", expires_in: CACHE_TTL) do Genko.get("/blog", limit: limit, orders: "-publishedAt")[:contents] .map { |entry| Post.from_entry(entry) } end endendキャッシュのキーは、クエリが変わればキーも変わるように組み立てます。更新を早く反映したい場合は、TTLを短くするのではなくWebhookで破棄してください。
エラーの扱い
| ステータス | 主な原因 |
|---|---|
400 | クエリの形式・値・演算子が不正 |
401 | APIキーがない、無効、期限切れ |
403 | キーとワークスペースが一致しない |
404 | ワークスペース・API・コンテンツがない、またはキーの対象外 |
429 | 同じAPIキーで1分300リクエストを超えた |
Genko::ApiError#statusにHTTPステータスが入ります。詳しくはエラーと制限にまとめています。
次に読む
ページ送り、保存済み下書きのプレビュー、Webhookの署名検証は実践に続きます。
最終更新: 2026年9月17日