NotionRubyMapping の 2026-03-11 対応(3) : hkob の雑記録 (439)

はじめに

hkob の雑記録の第439回目(通算13日目)は、NotionRubyMapping の 2026-03-11 への対応を記録していきます。今日は先日追加された「見出し4」に対応します。

見出し4の読み込み

まずはテストデータを作成します。Heading 4 と Toggle Heading 4 の取得スクリプトを作成しました。

curl 'https://api.notion.com/v1/blocks/32ad8e4e98ab80f1ae91faf02bbb50d8' \
    -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
    -H 'Notion-Version: 2026-03-11'
curl 'https://api.notion.com/v1/blocks/32ad8e4e98ab80fc8ee9d16833fce3a7' \
    -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
    -H 'Notion-Version: 2026-03-11'

次に block への Heading 4 と Toggle Heading 4 の追加スクリプトを作成します。

#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8035a5b4ea240d930619/children' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"children":[{"type":"heading_4","object":"block","heading_4":{"rich_text":[{"type":"text","text":{"content":"Heading 4","link":null},"plain_text":"Heading 4","href":null}],"color":"yellow_background","is_toggleable":false}}]}'
#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8035a5b4ea240d930619/children' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"children":[{"type":"heading_4","object":"block","heading_4":{"rich_text":[{"type":"text","text":{"content":"Toggle Heading 4","link":null},"plain_text":"Toggle Heading 4","href":null}],"color":"yellow_background","is_toggleable":true,"children":[{"type":"bulleted_list_item","object":"block","bulleted_list_item":{"rich_text":[{"type":"text","text":{"content":"inside Toggle Heading 4","link":null},"plain_text":"inside Toggle Heading 4","href":null}],"color":"default"}}]}}]}'

同様に page への Heading 4 と Toggle Heading 4 の追加スクリプトを作成します。

#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8061b880f8f45db00383/children' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"children":[{"type":"heading_4","object":"block","heading_4":{"rich_text":[{"type":"text","text":{"content":"Heading 4","link":null},"plain_text":"Heading 4","href":null}],"color":"yellow_background","is_toggleable":false}}]}'
#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8061b880f8f45db00383/children' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"children":[{"type":"heading_4","object":"block","heading_4":{"rich_text":[{"type":"text","text":{"content":"Toggle Heading 4","link":null},"plain_text":"Toggle Heading 4","href":null}],"color":"yellow_background","is_toggleable":true,"children":[{"type":"bulleted_list_item","object":"block","bulleted_list_item":{"rich_text":[{"type":"text","text":{"content":"inside Toggle Heading 4","link":null},"plain_text":"inside Toggle Heading 4","href":null}],"color":"default"}}]}}]}'

次に color 変更の update スクリプトを作成します。

#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab804c9173c15694f45c5f' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"heading_4":{"color":"green_background","rich_text":[{"type":"text","text":{"content":"Heading 4","link":null},"plain_text":"Heading 4","href":null}]}}'
#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab808998eae6b408766d68' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"heading_4":{"color":"green_background","rich_text":[{"type":"text","text":{"content":"Toggle Heading 4","link":null},"plain_text":"Toggle Heading 4","href":null}]}}'

次に文字列変更の update スクリプトを作成します。

#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab804c9173c15694f45c5f' \
  -H 'Notion-Version: 2025-09-03' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"heading_4":{"rich_text":[{"type":"text","text":{"content":"New Heading 4","link":null},"plain_text":"New Heading 4","href":null}]}}'
#!/bin/sh
curl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab808998eae6b408766d68' \
  -H 'Notion-Version: 2026-03-11' \
  -H 'Authorization: Bearer '"$NOTION_API_KEY"'' \
  -H 'Content-Type: application/json' \
  --data '{"heading_4":{"rich_text":[{"type":"text","text":{"content":"New Toggle Heading 4","link":null},"plain_text":"New Toggle Heading 4","href":null}]}}'

テストの作成

heading_4 のテストを作成します。

# frozen_string_literal: true

require_relative "../../spec_helper"

module NotionRubyMapping
  RSpec.describe Heading4Block do
    type = "heading_4"

    it_behaves_like "retrieve block", described_class, TestConnection.block_id(type), false, {
      "object" => "block",
      "type" => "heading_4",
      "heading_4" => {
        "rich_text" => [
          {
            "type" => "text",
            "text" => {
              "content" => "Heading 4",
              "link" => nil,
            },
            "annotations" => {
              "bold" => false,
              "italic" => false,
              "strikethrough" => false,
              "underline" => false,
              "code" => false,
              "color" => "default",
            },
            "plain_text" => "Heading 4",
            "href" => nil,
          },
        ],
        "color" => "default",
        "is_toggleable" => false,
      },
    }

    describe "create_child_block" do
      let(:sub_block) { ParagraphBlock.new "with children" }
      let(:target) { described_class.new "Heading 4", color: "yellow_background" }

      it_behaves_like "create child block", described_class,
                      "32ad8e4e98ab810b9039c3cdaf566c42", "32ad8e4e98ab8150a545dd6fa02c14dd"
    end

    describe "save (update)" do
      let(:update_id) { TestConnection.update_block_id(type) }
      let(:target) { described_class.new "Heading 4", color: "blue_background", id: update_id }

      it_behaves_like "update block rich text array", type, "New Heading 4"
      it_behaves_like "update block color", type, "green_background", true
    end
  end
end

同様に ToggleHeading4Block のテストを作成します。

# frozen_string_literal: true

require_relative "../../spec_helper"

module NotionRubyMapping
  RSpec.describe ToggleHeading4Block do
    type = "heading_4"

    it_behaves_like "retrieve block", described_class, TestConnection::BLOCK_ID_HASH[:toggle_heading_4], true, {
      "object" => "block",
      "type" => "heading_4",
      "heading_4" => {
        "rich_text" => [
          {
            "type" => "text",
            "text" => {
              "content" => "Toggle Heading 4",
              "link" => nil,
            },
            "annotations" => {
              "bold" => false,
              "italic" => false,
              "strikethrough" => false,
              "underline" => false,
              "code" => false,
              "color" => "default",
            },
            "plain_text" => "Toggle Heading 4",
            "href" => nil,
          },
        ],
        "color" => "default",
        "is_toggleable" => true,
      },
    }

    describe "create_child_block" do
      let(:sub_block) { ParagraphBlock.new "with children" }
      let(:target) do
        ToggleHeading4Block.new "Toggle Heading 4", color: "yellow_background", sub_blocks: [
          BulletedListItemBlock.new("inside Toggle Heading 4"),
        ]
      end

      it_behaves_like "create child block", described_class,
                      "32ad8e4e98ab813f9496d1ffe8a6b351", "32ad8e4e98ab810faed8df938cf3dfb9"
    end

    describe "save (update)" do
      let(:update_id) { TestConnection.update_block_id(:toggle_heading_4) }
      let(:target) do
        described_class.new "Toggle Heading 4", color: "green_background", id: update_id, sub_blocks: [
          BulletedListItemBlock.new("inside Toggle Heading 4"),
        ]
      end

      it_behaves_like "update block rich text array", type, "New Toggle Heading 4"
      it_behaves_like "update block color", type, "green_background", true
    end
  end
end

Heading4Block の作成

Heading4Block を追加します。

# frozen_string_literal: true

module NotionRubyMapping
  # Notion block
  class Heading4Block < TextSubBlockColorBaseBlock
    # @param [RichTextArray, String, Array<String>, RichTextObject, Array<RichTextObject>] text_info
    # @param [String] color
    def initialize(text_info = nil, color: nil, json: nil, id: nil, parent: nil)
      super(text_info, color: color, json: json, id: id, parent: parent)
      @can_have_children = false
    end

    # @return [String]
    def type
      "heading_4"
    end

    # @param [Boolean] not_update false when update
    # @return [Hash{String (frozen)->Hash}]
    def block_json(not_update: true)
      ans = super
      ans[type]["is_toggleable"] = false
      ans
    end
  end
end

ToggleHeading4Block も追加します。

# frozen_string_literal: true

module NotionRubyMapping
  # Notion block
  class ToggleHeading4Block < TextSubBlockColorBaseBlock
    # @return [String]
    def type
      "heading_4"
    end

    # @param [Boolean] not_update false when update
    # @return [Hash{String (frozen)->Hash}]
    def block_json(not_update: true)
      ans = super
      ans[type]["is_toggleable"] = true
      ans
    end
  end
end

notion_ruby_mapping.rb で require します。

# frozen_string_literal: true

require "yaml"

require_relative "notion_ruby_mapping/version"
{
  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 audio_block heading4_block toggle_heading4_block],
  controllers: %w[notion_cache payload property_cache query rich_text_array discussion_thread search mermaid
                  mermaid_data_source],
  objects: %w[rich_text_object emoji_object equation_object file_object mention_object text_object user_object
              comment_object file_upload_object template_object],
  properties: %w[property checkbox_property multi_property created_by_property date_base_property created_time_property
                 date_property email_property files_property formula_property last_edited_by_property
                 last_edited_time_property multi_select_property number_property people_property phone_number_property
                 relation_property text_property rich_text_property rollup_property select_property status_property
                 title_property unique_id_property url_property button_property verification_property place_property],
}.each do |key, values|
  values.each do |klass|
    require_relative "notion_ruby_mapping/#{key}/#{klass}"
  end
end

module NotionRubyMapping
  def configure
    yield NotionRubyMapping::NotionCache.instance
  end
  module_function :configure
end

WebMock の修正

今回、WebMock の修正は多岐に渡るので、掲載は省略します。変更の結果、heading_4 のテストは以下のように全て通過しました。

NotionRubyMapping::Heading4Block
  behaves like retrieve block
    NotionRubyMapping::Heading4Block block
      receive id
      can NotionRubyMapping::Heading4Block have children?
      behaves like raw json
        is expected to eq {"heading_4" => {"color" => "default", "is_toggleable" => false, "rich_text" => [{"annotations" => {"...nt" => "Heading 4", "link" => nil}, "type" => "text"}]}, "object" => "block", "type" => "heading_4"}                                                                                                        
  create_child_block
    behaves like create child block
      create child block
        when for page
          when dry_run
            behaves like dry run
              is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8061b880f8f45db00383/children...n_text\":\"Heading 4\",\"href\":null}],\"color\":\"yellow_background\",\"is_toggleable\":false}}]}'"                                                                                                  
          when create
            is expected to eq "32ad8e4e98ab810b9039c3cdaf566c42"
        when for block
          when dry_run
            behaves like dry run
              is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8035a5b4ea240d930619/children...n_text\":\"Heading 4\",\"href\":null}],\"color\":\"yellow_background\",\"is_toggleable\":false}}]}'"                                                                                                  
          when create
            is expected to eq "32ad8e4e98ab8150a545dd6fa02c14dd"
  save (update)
    behaves like update block rich text array
      is expected to eq {"heading_4" => {"rich_text" => [{"href" => nil, "plain_text" => "New Heading 4", "text" => {"content" => "New Heading 4", "link" => nil}, "type" => "text"}]}}                                                                                                                                                       
      when dry_run
        behaves like dry run
          is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab804c9173c15694f45c5f' \\\n  -...":{\"content\":\"New Heading 4\",\"link\":null},\"plain_text\":\"New Heading 4\",\"href\":null}]}}'"                                                                                                      
      when save
        is expected to eq "New Heading 4"
    behaves like update block color
      is expected to eq {"heading_4" => {"color" => "green_background", "rich_text" => [{"href" => nil, "plain_text" => "Heading 4", "text" => {"content" => "Heading 4", "link" => nil}, "type" => "text"}]}}                                                                                                                                
      when dry_run
        behaves like dry run
          is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab804c9173c15694f45c5f' \\\n  -...,\"text\":{\"content\":\"Heading 4\",\"link\":null},\"plain_text\":\"Heading 4\",\"href\":null}]}}'"                                                                                                      
      when save
        is expected to eq "green_background"

Finished in 0.07212 seconds (files took 0.23818 seconds to load)
13 examples, 0 failures

同様に toggle_heading_4 のテストも通過しました。

NotionRubyMapping::ToggleHeading4Block
  behaves like retrieve block
    NotionRubyMapping::ToggleHeading4Block block
      receive id
      can NotionRubyMapping::ToggleHeading4Block have children?
      behaves like raw json
        is expected to eq {"heading_4" => {"color" => "default", "is_toggleable" => true, "rich_text" => [{"annotations" => {"b..."Toggle Heading 4", "link" => nil}, "type" => "text"}]}, "object" => "block", "type" => "heading_4"}                                                                                                        
  create_child_block
    behaves like create child block
      create child block
        when for page
          when dry_run
            behaves like dry run
              is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8061b880f8f45db00383/children...\":\"inside Toggle Heading 4\",\"href\":null}],\"color\":\"default\"}}],\"is_toggleable\":true}}]}'"                                                                                                  
          when create
            is expected to eq "32ad8e4e98ab813f9496d1ffe8a6b351"
        when for block
          when dry_run
            behaves like dry run
              is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/26cd8e4e98ab8035a5b4ea240d930619/children...\":\"inside Toggle Heading 4\",\"href\":null}],\"color\":\"default\"}}],\"is_toggleable\":true}}]}'"                                                                                                  
          when create
            is expected to eq "32ad8e4e98ab810faed8df938cf3dfb9"
  save (update)
    behaves like update block rich text array
      is expected to eq {"heading_4" => {"rich_text" => [{"href" => nil, "plain_text" => "New Toggle Heading 4", "text" => {"content" => "New Toggle Heading 4", "link" => nil}, "type" => "text"}]}}                                                                                                                                         
      when dry_run
        behaves like dry run
          is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab808998eae6b408766d68' \\\n  -...:\"New Toggle Heading 4\",\"link\":null},\"plain_text\":\"New Toggle Heading 4\",\"href\":null}]}}'"                                                                                                      
      when save
        is expected to eq "New Toggle Heading 4"
    behaves like update block color
      is expected to eq {"heading_4" => {"color" => "green_background", "rich_text" => [{"href" => nil, "plain_text" => "Toggle Heading 4", "text" => {"content" => "Toggle Heading 4", "link" => nil}, "type" => "text"}]}}                                                                                                                  
      when dry_run
        behaves like dry run
          is expected to eq "#!/bin/sh\ncurl -X PATCH 'https://api.notion.com/v1/blocks/32ad8e4e98ab808998eae6b408766d68' \\\n  -...ontent\":\"Toggle Heading 4\",\"link\":null},\"plain_text\":\"Toggle Heading 4\",\"href\":null}]}}'"                                                                                                      
      when save
        is expected to eq "green_background"

Finished in 0.04343 seconds (files took 0.17045 seconds to load)
13 examples, 0 failures

おわりに

Heading4Block と ToggleHeading4Block のテスト・実装を追加しました。

hkob.notion.site