NotionRubyMapping v3.0.3 へのアップデート (Create page 時にテンプレート指定) : hkob の雑記録 (300)

はじめに

hkob の雑記録の第300回目は、NotionRubyMapping の v3.0.3 へのアップデートを解説します。今回は、Create page 時にテンプレートが指定できるようになったことへの対応です。

v3.0.2 へのアップデート

まず、template オプションを付けた shell script を作成しました。

curl '<https://api.notion.com/v1/pages>' \\
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \\
  -H "Content-Type: application/json" \\
  -H "Notion-Version: 2025-09-03" \\
  --data '{
    "parent": {
      "data_source_id": "293d8e4e98ab80e7842e000befaa8ed5"
    },
    "template": {
      "type": "template_id",
      "template_id": "293d8e4e98ab80e8b622cd46b8ccb0cb"
    },
    "properties": {
      "Name": {
        "type": "title",
        "title": [
          {
            "type": "text",
            "text": {
              "content": "New Page by data_source_id with template",
              "link": null
            },
            "plain_text": "New Page by data_source_id with template",
            "href": null
          }
        ]
      }
    }
  }'

シェルスクリプトを作成すると make するだけで、レスポンスの JSON が作成されるようになっています。

make
sh create_page_parent_data_source_with_template.sh > create_page_parent_data_source_with_template.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1710    0  1129  100   581   2139   1101 --:--:-- --:--:-- --:--:--  3238
sleep 1

取得した JSON は以下のようになりました。

{
  "object": "page",
  "id": "293d8e4e-98ab-81f9-b18d-e613fc770ebb",
  "created_time": "2025-10-21T23:09:00.000Z",
  "last_edited_time": "2025-10-21T23:09:00.000Z",
  "created_by": {
    "object": "user",
    "id": "40673a87-d8ed-41e0-aa55-7f0e8ace24cd"
  },
  "last_edited_by": {
    "object": "user",
    "id": "40673a87-d8ed-41e0-aa55-7f0e8ace24cd"
  },
  "cover": null,
  "icon": null,
  "parent": {
    "type": "data_source_id",
    "data_source_id": "293d8e4e-98ab-80e7-842e-000befaa8ed5",
    "database_id": "293d8e4e-98ab-807c-9513-da2359ef1748"
  },
  "archived": false,
  "in_trash": false,
  "is_locked": false,
  "properties": {
    "Name": {
      "id": "title",
      "type": "title",
      "title": [
        {
          "type": "text",
          "text": {
            "content": "New Page by data_source_id with template",
            "link": null
          },
          "annotations": {
            "bold": false,
            "italic": false,
            "strikethrough": false,
            "underline": false,
            "code": false,
            "color": "default"
          },
          "plain_text": "New Page by data_source_id with template",
          "href": null
        }
      ]
    }
  },
  "url": "<https://www.notion.so/New-Page-by-data_source_id-with-template-293d8e4e98ab81f9b18de613fc770ebb>",
  "public_url": "<https://hkob.notion.site/New-Page-by-data_source_id-with-template-293d8e4e98ab81f9b18de613fc770ebb>",
  "request_id": "089c47a5-f6db-4901-a38e-aa63abe9bfd7"
}

これを元にテストを作成します。テスト用のページタイトルは定数にしておきました。

    PAGE_TITLE_FOR_CREATED_PAGE = "New Page by data_source_id"
    PAGE_TITLE_FOR_CREATED_PAGE_WITH_TEMPLATE = "New Page by data_source_id with template"

作成したテンプレートページの page_id も記録しておきます。

    TEMPLATE_PAGE_ID = "293d8e4e98ab80e8b622cd46b8ccb0cb"

create のテストを複製し、これらを使った build_child_page、 create_child_page のテストを記述しました。それぞれに template_page オプションを追加しています。

    describe "create with template" do
      create_page_title = {
        "properties" => {
          "Name" => {
            "title" => [
              {
                "href" => nil,
                "plain_text" => PAGE_TITLE_FOR_CREATED_PAGE_WITH_TEMPLATE,
                "text" => {
                  "content" => PAGE_TITLE_FOR_CREATED_PAGE_WITH_TEMPLATE,
                  "link" => nil,
                },
                "type" => "text",
              },
            ],
            "type" => "title",
          },
        },
      }
      let(:parent_data_source) { DataSource.new id: TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID }
      let(:template_page) { instance_double Page, id: TestConnection::TEMPLATE_PAGE_ID }

      context "when build_child_page" do
        let(:target) do
          parent_data_source.build_child_page TitleProperty, "Name", template_page: template_page do |_, ps|
            ps["Name"] << PAGE_TITLE_FOR_CREATED_PAGE_WITH_TEMPLATE
          end
        end

        it { expect(target).to be_new_record }

        it_behaves_like "property values json", {
          "parent" => {
            "data_source_id" => TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID,
          },
          "template" => {
            "type" => "template_id",
            "template_id" => TestConnection::TEMPLATE_PAGE_ID,
          },
        }.merge(create_page_title)

        describe "dry_run" do
          let(:dry_run) { target.save dry_run: true }

          it_behaves_like "dry run", :post, :pages_path, json_method: :property_values_json
        end

        describe "create" do
          before { target.save }

          it_behaves_like "property values json", {}
          it { expect(target.id).to eq "293d8e4e98ab81f9b18de613fc770ebb" }
          it { expect(target).not_to be_new_record }
        end
      end

      context "create_child_page" do
        context "not dry_run" do
          let(:target) do
            parent_data_source.create_child_page TitleProperty, "Name", template_page: template_page do |_, ps|
              ps["Name"] << PAGE_TITLE_FOR_CREATED_PAGE_WITH_TEMPLATE
            end
          end

          it_behaves_like "property values json", {}
          it { expect(target.id).to eq "293d8e4e98ab81f9b18de613fc770ebb" }
          it { expect(target).not_to be_new_record }
        end

        context "dry_run" do
          let(:target) do
            parent_data_source.build_child_page TitleProperty, "Name", template_page: template_page do |_, ps|
              ps["Name"] << "New Page Title"
            end
          end
          let(:dry_run) do
            parent_data_source.create_child_page TitleProperty, "Name", template_page: template_page,
                                                                        dry_run: true do |_, ps|
              ps["Name"] << "New Page Title"
            end
          end

          it_behaves_like "dry run", :post, :pages_path, json_method: :property_values_json
        end
      end
    end

このオプションを受け付けるように Base の initialize に template_page オプションを追加しました。もともと、Payload の引数は parent だけでしたが、そこに template を追加することにしました。Payload は受け取った JSON はそのまま出力するので、Payload クラスは特に変更が必要ありませんでした。

    def initialize(json: nil, id: nil, assign: [], parent: nil, template_page: nil)
      @nc = NotionCache.instance
      @json = json
      @id = @nc.hex_id(id || @json && @json["id"])
      @archived = @json && @json["archived"]
      @has_children = @json && @json["has_children"]
      @new_record = true unless parent.nil?
      raise StandardError, "Unknown id" if !is_a?(List) && !is_a?(Block) && @id.nil? && parent.nil?

      payload_json = {}
      payload_json["parent"] = parent if !is_a?(Block) && parent
      payload_json["tempalte"] = {"type" => "template_page_id", "template_page_id" => template_page.id} if template_page
      @payload = Payload.new(payload_json)
      @property_cache = nil
      @created_time = nil
      @last_edited_time = nil
      return if assign.empty?

      assign.each_slice(2) { |(klass, key)| assign_property(klass, key) }
      @json ||= {}
    end

ここまでで property_values_json までのテストは通過しました。あとは WebMock の API 呼び出しのパラメータを追加するだけです。ここでは、最終引数に渡された payload が検出されると、 create_page_parent_data_source_with_template.json というファイルの内容が WebMock の返り値として返るように設定しています。これで、すべてのテストが通過しました。

        parent_data_source_with_template: [nil, 200, {
          "properties" => {
            "Name" => {
              "type" => "title",
              "title" => [
                {
                  "type" => "text",
                  "text" => {
                    "content" => "New Page by data_source_id with template",
                    "link" => nil,
                  },
                  "plain_text" => "New Page by data_source_id with template",
                  "href" => nil,
                },
              ],
            },
          },
          "parent" => {
            "data_source_id" => "293d8e4e98ab80e7842e000befaa8ed5",
          },
          "template" => {
            "type": "template_id",
            "template_id": TEMPLATE_PAGE_ID,
          },
        }]

以上をもって、NotionRubyMapping の v3.0.2 をリリースしました。

v3.0.3 へのアップデート

金曜日の座談会で、Notion API のアップデートを紹介したときに、 "type": "default" とすることでデフォルトテンプレートが設定できることに気づきました。ほぼ一緒なので、すぐに対応してみました。まずは、その設定をした shell script を作成しました。

curl '<https://api.notion.com/v1/pages>' \\
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \\
  -H "Content-Type: application/json" \\
  -H "Notion-Version: 2025-09-03" \\
  --data '{
    "parent": {
      "data_source_id": "293d8e4e98ab80e7842e000befaa8ed5"
    },
    "template": {
      "type": "default",
    },
    "properties": {
      "Name": {
        "type": "title",
        "title": [
          {
            "type": "text",
            "text": {
              "content": "New Page by data_source_id with default template",
              "link": null
            },
            "plain_text": "New Page by data_source_id with default template",
            "href": null
          }
        ]
      }
    }
  }'

make すると、以下の JSON が取得できました。

{
  "object": "page",
  "id": "297d8e4e-98ab-8179-ba96-cdd34784e3f3",
  "created_time": "2025-10-25T11:04:00.000Z",
  "last_edited_time": "2025-10-25T11:04:00.000Z",
  "created_by": {
    "object": "user",
    "id": "40673a87-d8ed-41e0-aa55-7f0e8ace24cd"
  },
  "last_edited_by": {
    "object": "user",
    "id": "40673a87-d8ed-41e0-aa55-7f0e8ace24cd"
  },
  "cover": null,
  "icon": null,
  "parent": {
    "type": "data_source_id",
    "data_source_id": "293d8e4e-98ab-80e7-842e-000befaa8ed5",
    "database_id": "293d8e4e-98ab-807c-9513-da2359ef1748"
  },
  "archived": false,
  "in_trash": false,
  "is_locked": false,
  "properties": {
    "Name": {
      "id": "title",
      "type": "title",
      "title": [
        {
          "type": "text",
          "text": {
            "content": "New Page by data_source_id with default template",
            "link": null
          },
          "annotations": {
            "bold": false,
            "italic": false,
            "strikethrough": false,
            "underline": false,
            "code": false,
            "color": "default"
          },
          "plain_text": "New Page by data_source_id with default template",
          "href": null
        }
      ]
    }
  },
  "url": "<https://www.notion.so/New-Page-by-data_source_id-with-default-template-297d8e4e98ab8179ba96cdd34784e3f3>",
  "public_url": "<https://hkob.notion.site/New-Page-by-data_source_id-with-default-template-297d8e4e98ab8179ba96cdd34784e3f3>",
  "request_id": "a58190e2-a146-4867-811e-5188ccc3bdca"
}

テストもコピーして修正しました。今回は、 template_page: "default" とすることで、デフォルトテンプレートを指定できるようにしました。

    describe "create with default template" do
      create_page_title = {
        "properties" => {
          "Name" => {
            "title" => [
              {
                "href" => nil,
                "plain_text" => PAGE_TITLE_FOR_CREATED_PAGE_WITH_DEFAULT_TEMPLATE,
                "text" => {
                  "content" => PAGE_TITLE_FOR_CREATED_PAGE_WITH_DEFAULT_TEMPLATE,
                  "link" => nil,
                },
                "type" => "text",
              },
            ],
            "type" => "title",
          },
        },
      }
      let(:parent_data_source) { DataSource.new id: TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID }

      context "when build_child_page" do
        let(:target) do
          parent_data_source.build_child_page TitleProperty, "Name", template_page: "default" do |_, ps|
            ps["Name"] << PAGE_TITLE_FOR_CREATED_PAGE_WITH_DEFAULT_TEMPLATE
          end
        end

        it { expect(target).to be_new_record }

        it_behaves_like "property values json", {
          "parent" => {
            "data_source_id" => TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID,
          },
          "template" => {
            "type" => "default",
          },
        }.merge(create_page_title)

        describe "dry_run" do
          let(:dry_run) { target.save dry_run: true }

          it_behaves_like "dry run", :post, :pages_path, json_method: :property_values_json
        end

        describe "create" do
          before { target.save }

          it_behaves_like "property values json", {}
          it { expect(target.id).to eq "297d8e4e98ab8179ba96cdd34784e3f3" }
          it { expect(target).not_to be_new_record }
        end
      end

      context "create_child_page" do
        context "not dry_run" do
          let(:target) do
            parent_data_source.create_child_page TitleProperty, "Name", template_page: "default" do |_, ps|
              ps["Name"] << PAGE_TITLE_FOR_CREATED_PAGE_WITH_DEFAULT_TEMPLATE
            end
          end

          it_behaves_like "property values json", {}
          it { expect(target.id).to eq "297d8e4e98ab8179ba96cdd34784e3f3" }
          it { expect(target).not_to be_new_record }
        end

        context "dry_run" do
          let(:target) do
            parent_data_source.build_child_page TitleProperty, "Name", template_page: "default" do |_, ps|
              ps["Name"] << "New Page Title"
            end
          end
          let(:dry_run) do
            parent_data_source.create_child_page TitleProperty, "Name", template_page: "default",
                                                                        dry_run: true do |_, ps|
              ps["Name"] << "New Page Title"
            end
          end

          it_behaves_like "dry run", :post, :pages_path, json_method: :property_values_json
        end
      end
    end

これに対応するために Base の initialize の部分を以下のように修正しました。

      payload_json = {}
      payload_json["parent"] = parent if !is_a?(Block) && parent
      if template_page == "default"
        payload_json["template"] = {"type" => "default"}
      elsif template_page
        payload_json["template"] = {"type" => "template_id", "template_id" => template_page.id}
      end

これで、 property_values_json の作成まで通過したことを確認したので、WebMock の設定を追加しました。これでテストがすべて通過したことを確認しました。

        parent_data_source_with_default_template: [nil, 200, {
          "properties" => {
            "Name" => {
              "type" => "title",
              "title" => [
                {
                  "type" => "text",
                  "text" => {
                    "content" => "New Page by data_source_id with template",
                    "link" => nil,
                  },
                  "plain_text" => "New Page by data_source_id with template",
                  "href" => nil,
                },
              ],
            },
          },
          "parent" => {
            "data_source_id" => "293d8e4e98ab80e7842e000befaa8ed5",
          },
          "template" => {
            "type": "default",
          },
        }],

これで NotionRubyMapping v3.0.3 としてリリースしました。

おわりに

今回は、Page 作成時のテンプレート指定に対応した NotionRubyMapping のアップデートについて解説しました。今後も Notion API に変更があるときには、NotionRubyMapping も付いていくつもりです。

https://hkob.notion.site/hkob-16dd8e4e98ab807cbe3cf3cc94cdfe0f?pvs=4