NotionRubyMapping の 2025-09-03対応(6) : hkob の雑記録 (259)

はじめに

hkob の雑記録の第259回目は、notionErDiagram の DataSource 対応について解説します。ただ、それだけだと面白くないので、これまで対応していなかった dual_property に対応してみます。

以前の notionErDiagram

notionErDiagram は NotionRubyMapping に同梱しているサンプルプログラムです。Notion のデータベースと CodeBlock を渡すと ER図を描いてくれるプログラムです。解説はこちらにあります。

www.notion.so

これまでは database_id (database_url) と code_block_id (code_block_url) を渡す形になっていましたが、新しい NotionRubyMapping では data_source_id を渡すように変更します。作成当時はまだ RelationProperty に dual_property や single_property の区別がなかったため、多対多の描画には面倒で対応していませんでした。前回の描画はこんな形でした。

作成された ER 図 (v0.6.1版)

アップデートした NotionErDiagram

今回、3.0.0 で DataSource に対応しました。さらに dual_property の場合には多対多の接続になっていることがわかるので、その描画にすると同時に反対側からの描画をしないように修正しまた。

#! /usr/bin/env ruby

require "notion_ruby_mapping"
include NotionRubyMapping

def append_data_source(text, ds, ds_titles)
  base_title = ds_title ds
  normalize_ds_title(ds, ds_titles) if ds_titles[ds].nil?
  text << "  #{ds_titles[ds]} {"
  text << %(    Database title "#{base_title}") unless base_title == ds_titles[ds]
  ds.properties.reject { |p| p.is_a? RelationProperty }.each_with_index do |p, i|
    class_name = p.class.name.split("::").last.sub(/Property/, "")
    text << %(    #{class_name} p#{i} "#{p.name}")
  end
  text << "  }\n"
end

def normalize_ds_title(db, db_titles)
  base_title = ds_title db
  db_titles[db] = base_title.gsub(/[\w\d\-_]+/, "").empty? ? base_title : "d#{db_titles.count}"
end

def ds_title(db)
  db.data_source_title.full_text.gsub " ", "_"
end

if ARGV.length < 2
  print "Usage: notionErDiagram.rb top_data_source_id code_block_id"
  exit
end
data_source_id, code_block_id = ARGV
NotionRubyMapping.configure { |c| c.notion_token = ENV["NOTION_API_KEY"] }
block = Block.find code_block_id
unless block.is_a? CodeBlock
  print "#{code_block_id} is not CodeBlock's id"
  exit
end
dss = [DataSource.find(data_source_id)]
text = %w[erDiagram]

finished = {}
ds_titles = {}
until dss.empty?
  ds = dss.shift
  append_data_source(text, ds, ds_titles)
  ds.properties.select { |pp| pp.is_a? RelationProperty }.each_with_index do |pp, _i|
    new_ds = DataSource.find pp.relation_data_source_id
    normalize_ds_title(new_ds, ds_titles) if ds_titles[new_ds].nil?
    if pp.dual_property?
      unless finished[new_ds]
        text << %(  #{ds_titles[ds]} }o--o{ #{ds_titles[new_ds]} : "#{pp.name} / #{pp.synced_property_name}" )
      end
    else
      text << %(  #{ds_titles[ds]} |o--o{ #{ds_titles[new_ds]} : "#{pp.name}")
    end
    dss << new_ds unless finished[new_ds]
  end
  finished[ds] = true
  text << ""
end

text_objects = text.each_with_object([]) do |str, ans|
  strn = "#{str}\n"
  if (last = ans.last)
    if last.length + strn.length > 1999
      ans << strn
    else
      ans[-1] += strn
    end
  else
    ans << strn
  end
end
block.rich_text_array.rich_text_objects = text_objects
block.language = "mermaid"
block.save

synced_property_name で反対側の名前もわかるので、Relation 名も双方向の名称を入れるようにしてみました。出力結果は以下のようになります。

作成された ER 図 (v3.0.0版)

なお、single_property の場合には、このような形で 1 対多のリレーションになります。

single_property の例

おわりに

NotionRubyMapping も本体部分のテストデータの修正はほぼ終わり、周辺プログラムで動作確認をしているところです。もう少しでリリースができそうです。

hkob.notion.site