NotionRubyMapping の 2025-09-03対応(2) : hkob の雑記録 (247)

はじめに

hkob の雑記録の第247回目は、引き続き NotionRubyMapping を 2025-09-03 に対応させるアップデートを記録していきます。

template block の削除

update_template_block_rta.sh を実行したら、Unsupported というレスポンスが返ってきました。そういえば随分前になくなったブロックでした。とりあえず、template block に関するファイルを一括で削除しました。その分テストも10個ほど減りました。retrieve 系、update 系はほぼスクリプトの修正が終わりました。

Finished in 0.99804 seconds (files took 0.78548 seconds to load)
2404 examples, 0 failures

search のテスト

create 系は準備が面倒なので、先に search をテストしました。database を検索しようとしていたので、エラーになっていました。このため、value の部分を data_source に変更しました。

#!/bin/sh
curl -X POST 'https://api.notion.com/v1/search' \\
  -H 'Notion-Version: 2025-09-03' \\
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \\
  -H 'Content-Type: application/json' \\
  --data '{"filter":{"value":"data_source","property":"object"},"query":"Sample table"}'

スクリプトを実行するとこのようにエラーになりました。

  1) NotionRubyMapping::Search exec
     Failure/Error: raise StandardError, json.inspect

data_source を検索しているので、objectdata_source が存在せずエラーになっていたのでした。いよいよ DataSource を実装する時のようです。

    # @param [Hash, Notion::Messages] json
    # @return [NotionRubyMapping::Base]
    def self.create_from_json(json)
      case json["object"]
      when "page"
        Page.new json: json
      when "database"
        Database.new json: json
      when "list"
        List.new json: json
      when "block"
        Block.decode_block json
      else
        raise StandardError, json.inspect
      end
    end

基本的に databasedata_source に変更になるので、まず spec ファイルをコピーします。

cp spec/notion_ruby_mapping/blocks/{database,data_source}_spec.rb

ファイル中の DatabaseDataSource, databasedata_source に変更していきます。当然、DataSource はまだ作っていないので、uninitialized constant になっています。

Failure/Error:
    RSpec.describe DataSource do
      let(:tc) { TestConnection.instance }
      let!(:nc) { tc.nc }

      describe "find" do
        subject { -> { DataSource.find data_source_id } }

        context "For an existing data_source" do
          let(:data_source_id) { TestConnection::DATABASE_ID }

NameError:
  uninitialized constant NotionRubyMapping::DataSource

data_sourcerequire するように blocks に登録します。

  blocks: %w[base block database data_source list page url_caption_base_block bookmark_block breadcrumb_block
             text_sub_block_color_base_block bulleted_list_item_block callout_block child_base_block
             child_database_block child_page_block code_block column_list_block column_block divider_block
             embed_block equation_block file_base_block file_block heading1_block heading2_block heading3_block
             image_block toggle_heading1_block toggle_heading2_block toggle_heading3_block url_base_block
             link_preview_block link_to_page_block numbered_list_item_block paragraph_block pdf_block quote_block
             synced_block table_block table_row_block table_of_contents_block to_do_block
             toggle_block video_block],

data_source の取得

data_source がどういう扱いになるのか、現在の Database を調べてみます。

db = Database.find "c37a2c66e3aa4a0da44773de3b80c253"
=> NotionRubyMapping::Database-c37a2c66e3aa4a0da44773de3b80c253

この json を確認すると、database というコンテナの中に複数の data_source が複数存在することができるようになっています。data_source_id については URL の中には含まれず、この data_sources の配列から取得する必要があります。

db.json
=>
{"object" => "database",
 "id" => "c37a2c66-e3aa-4a0d-a447-73de3b80c253",
 "title" =>
  [{"type" => "text",
    "text" => {"content" => "Sample table", "link" => nil},
    "annotations" =>
     {"bold" => false,
      "italic" => false,
      "strikethrough" => false,
      "underline" => false,
      "code" => false,
      "color" => "default"},
    "plain_text" => "Sample table",
    "href" => nil}],
 "parent" =>
  {"type" => "page_id", "page_id" => "c01166c6-13ae-45cb-b968-18b4ef2f5a77"},
 "is_inline" => true,
 "in_trash" => false,
 "created_time" => "2022-02-07T21:29:38.416+00:00",
 "last_edited_time" => "2025-09-01T06:31:12.223+00:00",
 "data_sources" =>
  [{"id" => "4f93db51-4e1d-4015-b07f-876e34c3b0b1", "name" => "Sample table"}],
 "icon" => nil,
 "cover" => nil,
 "url" => "https://www.notion.so/c37a2c66e3aa4a0da44773de3b80c253",
 "public_url" => "https://hkob.notion.site/c37a2c66e3aa4a0da44773de3b80c253",
 "request_id" => "a517bb59-7c32-4b56-82a2-39de65f321da"}

ここで取得した data_source_idDATA_SOURCE_ID として spec_helper.rb に追加しておきます。

    # datasource_id
    DATA_SOURCE_ID = "4f93db514e1d4015b07f876e34c3b0b1"

create_from_json でも DataSource の作成を追加します。

    # @param [Hash, Notion::Messages] json
    # @return [NotionRubyMapping::Base]
    def self.create_from_json(json)
      case json["object"]
      when "page"
        Page.new json: json
      when "data_source"
        DataSource.new json: json
      when "database"
        Database.new json: json
      when "list"
        List.new json: json
      when "block"
        Block.decode_block json
      else
        raise StandardError, json.inspect
      end
    end

順番にエラーをつぶしていきます。NotionCachedata_source がないと言われています。

      NoMethodError:
        undefined method 'data_source' for #<NotionRubyMapping::NotionCache:0x000000013327e528>

database(id) をコピーして data_source(id) を作成します。

    # @param [String] id data_source_id (with or without "-")
    # @return [NotionRubyMapping::Base] DataSource object or nil
    def data_source(id)
      object_for_key(id) { data_source_request id }
    end

この中で呼ばれている data_source_request を同様に追加します。

    # @param [String] data_source_id
    # @return [Hash] response
    def data_source_request(data_source_id)
      request :get, data_source_path(data_source_id)
    end

data_source_path のテストは以下のようになります。

    describe "data_source_path" do
      it { expect(nc.data_source_path("ABC")).to eq "v1/data_sources/ABC" }
    end

ここまで実行したところで、テストデータが足りないというエラーになっていました。ここから data_source に関するテストデータを準備していくことになります。

WebMock::NetConnectNotAllowedError:
        Real HTTP connections are disabled. Please stub: GET https://api.notion.com/v1/data_sources/c7697137d49f49c2bbcdd6a665c4f921 with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer secret_J08RBf9SlofiMhuIeJZc6AnI5qHXwDOaq4RolJFaaZ0', 'Notion-Version'=>'2025-09-03', 'User-Agent'=>'Faraday v2.13.1'}

テストデータの取得

先ほどの data_source のテストデータを取得するために、retrieve_database_database.sh をコピーして修正します。

cp retrieve_database_database.sh retrieve_data_source_data_source.sh

スクリプトは以下のようになります。

curl 'https://api.notion.com/v1/data_sources/4f93db514e1d4015b07f876e34c3b0b1' \\
    -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \\
    -H 'Notion-Version: 2025-09-03'

取得した JSON は以下のようになりました。興味があるのは、parent はコンテナに相当する database となり、その database の親は database_parent として別に用意されるようです。これらを取得できるようにメソッドを追加したいと思います。

{
  "object": "data_source",
  "id": "4f93db51-4e1d-4015-b07f-876e34c3b0b1",
  "cover": null,
  "icon": {
    "type": "external",
    "external": {
      "url": "https://cdn.profile-image.st-hatena.com/users/hkob/profile.png"
    }
  },
  "created_time": "2022-02-07T21:29:00.000Z",
  "created_by": {
    "object": "user",
    "id": "2200a911-6a96-44bb-bd38-6bfb1e01b9f6"
  },
  "last_edited_by": {
    "object": "user",
    "id": "2200a911-6a96-44bb-bd38-6bfb1e01b9f6"
  },
  "last_edited_time": "2025-09-01T06:31:00.000Z",
  "title": [
    {
      "type": "text",
      "text": {
        "content": "Sample table",
        "link": null
      },
      "annotations": {
        "bold": false,
        "italic": false,
        "strikethrough": false,
        "underline": false,
        "code": false,
        "color": "default"
      },
      "plain_text": "Sample table",
      "href": null
    }
  ],
  "description": [],
  "is_inline": true,
  "properties": {
    "RelationTitle": {
      "id": "%3CnJT",
      "name": "RelationTitle",
      "description": null,
      "type": "relation",
      "relation": {
        "database_id": "1d6b1040-a9fb-48d9-9a3d-041429816e9f",
        "data_source_id": "f0a1bf33-7ff0-4d24-b5b6-efb3ea006b15",
        "type": "dual_property",
        "dual_property": {
          "synced_property_name": "Related to Sample table (Column)",
          "synced_property_id": "jZZ%3E"
        }
      }
    },
    "MultiSelectTitle": {
      "id": "Kjx%7D",
      "name": "MultiSelectTitle",
      "description": null,
      "type": "multi_select",
      "multi_select": {
        "options": [
          {
            "id": "2a0eeeee-b3fd-4072-96a9-865f67cfa6ff",
            "name": "Multi Select 1",
            "color": "yellow",
            "description": null
          },
          {
            "id": "5f554552-b77a-474b-b5c7-4ae819966e32",
            "name": "Multi Select 2",
            "color": "default",
            "description": null
          },
          {
            "id": "d4bc3d6e-a6e1-4d57-af66-d8ecbbda1dd3",
            "name": "multi_select",
            "color": "red",
            "description": null
          }
        ]
      }
    },
    "LastEditedByTitle": {
      "id": "LQGa",
      "name": "LastEditedByTitle",
      "description": null,
      "type": "last_edited_by",
      "last_edited_by": {}
    },
    "CheckboxTitle": {
      "id": "Lbi%5D",
      "name": "CheckboxTitle",
      "description": null,
      "type": "checkbox",
      "checkbox": {}
    },
    "StatusTitle": {
      "id": "MNV~",
      "name": "StatusTitle",
      "description": null,
      "type": "status",
      "status": {
        "options": [
          {
            "id": "dfd8a326-f149-4b01-b01a-57d8aed6de1b",
            "name": "Not started",
            "color": "default",
            "description": null
          },
          {
            "id": "f17767fd-3186-4ba2-8d3a-01eef36e88ec",
            "name": "In progress",
            "color": "blue",
            "description": null
          },
          {
            "id": "F<XA",
            "name": "Design",
            "color": "purple",
            "description": null
          },
          {
            "id": "IRXI",
            "name": "Implementation",
            "color": "orange",
            "description": null
          },
          {
            "id": "ceb89388-7f74-4bd5-a52d-0fd572a409f6",
            "name": "Done",
            "color": "green",
            "description": null
          }
        ],
        "groups": [
          {
            "id": "2b356467-0400-420c-ad91-cdd5f98fc799",
            "name": "To-do",
            "color": "gray",
            "option_ids": [
              "dfd8a326-f149-4b01-b01a-57d8aed6de1b"
            ]
          },
          {
            "id": "ff88fa16-34b6-4818-ae5c-f8ce892d888b",
            "name": "In progress",
            "color": "blue",
            "option_ids": [
              "IRXI",
              "f17767fd-3186-4ba2-8d3a-01eef36e88ec",
              "F<XA"
            ]
          },
          {
            "id": "de6040ae-17d1-4dc7-939c-b7db49bcf482",
            "name": "Complete",
            "color": "green",
            "option_ids": [
              "ceb89388-7f74-4bd5-a52d-0fd572a409f6"
            ]
          }
        ]
      }
    },
    "DateTitle": {
      "id": "SPrp",
      "name": "DateTitle",
      "description": null,
      "type": "date",
      "date": {}
    },
    "RollupTitle": {
      "id": "STe_",
      "name": "RollupTitle",
      "description": null,
      "type": "rollup",
      "rollup": {
        "rollup_property_name": "Tags",
        "relation_property_name": "RelationTitle",
        "rollup_property_id": ":>Fq",
        "relation_property_id": "<nJT",
        "function": "show_original"
      }
    },
    "CreatedTimeTitle": {
      "id": "WsEj",
      "name": "CreatedTimeTitle",
      "description": null,
      "type": "created_time",
      "created_time": {}
    },
    "LastEditedTimeTitle": {
      "id": "X%3E%40X",
      "name": "LastEditedTimeTitle",
      "description": null,
      "type": "last_edited_time",
      "last_edited_time": {}
    },
    "Related to New database title(Added) (Relation)": {
      "id": "%5CaqD",
      "name": "Related to New database title(Added) (Relation)",
      "description": null,
      "type": "relation",
      "relation": {
        "database_id": "c7697137-d49f-49c2-bbcd-d6a665c4f921",
        "data_source_id": "34b5284f-60ac-4ec9-b168-224e4eaf68d2",
        "type": "dual_property",
        "dual_property": {
          "synced_property_name": "Relation",
          "synced_property_id": "N%60OG"
        }
      }
    },
    "FormulaTitle": {
      "id": "%5D~iZ",
      "name": "FormulaTitle",
      "description": null,
      "type": "formula",
      "formula": {
        "expression": "now()"
      }
    },
    "UserTitle": {
      "id": "_x%3E%3D",
      "name": "UserTitle",
      "description": null,
      "type": "people",
      "people": {}
    },
    "CreatedByTitle": {
      "id": "eR%3D~",
      "name": "CreatedByTitle",
      "description": null,
      "type": "created_by",
      "created_by": {}
    },
    "TextTitle": {
      "id": "flUp",
      "name": "TextTitle",
      "description": null,
      "type": "rich_text",
      "rich_text": {}
    },
    "Related to Database created by API (Relation)": {
      "id": "iijV",
      "name": "Related to Database created by API (Relation)",
      "description": null,
      "type": "relation",
      "relation": {
        "database_id": "d25d3500-044d-4959-8564-10198038d651",
        "data_source_id": "a4a29073-fe60-4e10-adc6-92168a2fb8c9",
        "type": "dual_property",
        "dual_property": {
          "synced_property_name": "Relation",
          "synced_property_id": "nLjJ"
        }
      }
    },
    "Related to New database title (Relation)": {
      "id": "mfBo",
      "name": "Related to New database title (Relation)",
      "description": null,
      "type": "relation",
      "relation": {
        "database_id": "c7697137-d49f-49c2-bbcd-d6a665c4f921",
        "data_source_id": "34b5284f-60ac-4ec9-b168-224e4eaf68d2",
        "type": "single_property",
        "single_property": {}
      }
    },
    "MailTitle": {
      "id": "p%7Ci%3F",
      "name": "MailTitle",
      "description": null,
      "type": "email",
      "email": {}
    },
    "File&MediaTitle": {
      "id": "qEdK",
      "name": "File&MediaTitle",
      "description": null,
      "type": "files",
      "files": {}
    },
    "NumberTitle": {
      "id": "swq%5C",
      "name": "NumberTitle",
      "description": null,
      "type": "number",
      "number": {
        "format": "number_with_commas"
      }
    },
    "UrlTitle": {
      "id": "tvis",
      "name": "UrlTitle",
      "description": null,
      "type": "url",
      "url": {}
    },
    "SelectTitle": {
      "id": "zE%7C%3F",
      "name": "SelectTitle",
      "description": null,
      "type": "select",
      "select": {
        "options": [
          {
            "id": "0fed8e50-c917-4b56-96d9-9691a5132fc4",
            "name": "Select 1",
            "color": "brown",
            "description": null
          },
          {
            "id": "0b71c5a8-ea82-4b21-970e-b155e7c68a7e",
            "name": "Select 2",
            "color": "default",
            "description": null
          },
          {
            "id": "b32c83bb-c9af-49e8-9b88-122139affdb7",
            "name": "Select 3",
            "color": "purple",
            "description": null
          }
        ]
      }
    },
    "ID": {
      "id": "%7BGE%7C",
      "name": "ID",
      "description": null,
      "type": "unique_id",
      "unique_id": {
        "prefix": "ST"
      }
    },
    "TelTitle": {
      "id": "%7CNHO",
      "name": "TelTitle",
      "description": null,
      "type": "phone_number",
      "phone_number": {}
    },
    "Title": {
      "id": "title",
      "name": "Title",
      "description": null,
      "type": "title",
      "title": {}
    }
  },
  "parent": {
    "type": "database_id",
    "database_id": "c37a2c66-e3aa-4a0d-a447-73de3b80c253"
  },
  "database_parent": {
    "type": "page_id",
    "page_id": "c01166c6-13ae-45cb-b968-18b4ef2f5a77"
  },
  "url": "https://www.notion.so/c37a2c66e3aa4a0da44773de3b80c253",
  "public_url": "https://hkob.notion.site/c37a2c66e3aa4a0da44773de3b80c253",
  "archived": false,
  "in_trash": false,
  "request_id": "a13d0fd8-5e2e-4974-b1ff-2cca15a2d37f"
}

これらを取得するための stubs の読み込みに retrieve_data_source を追加します。

    def generate_stubs
      WebMock.enable!
      retrieve_page
      retrieve_database
      retrieve_data_source
      (後略)

とりあえず DATA_SOURCE_ID の取得だけ stub を追加しました。

    def retrieve_data_source
      generate_stubs_sub :get, __method__, :data_source_path, {
        data_source: [DATA_SOURCE_ID, 200],
      }
    end

これでこの Sample table を取得していたテストの大部分が通過しました。それ以外のものをつぶしていきます。

おわりに

database を除く retrive 系、update 系のテストデータ更新は終わっています。今回の主なアップデート目的である data_source について、順次修正していきます。やろうとしていることはほぼ解説したので、これ以外の細かい手順は解説を省略して作業を進めていきたいと思います。

hkob.notion.site