NotionRubyMapping の markdown 対応(1) : hkob の雑記録 (423)

はじめに

hkob の雑記録の第423回目(通算17日目)は、NotionRubyMapping を makedown に対応させます。最初は create a page です。

Notion API の markdown 対応について

昨日紹介したように Notion API が markdown に対応しました。

makedown でどんなコンテンツが対応しているかはこちらに書かれています。

テストデータの作成

まず、いつものようにテストのようにスクリプトを作成しました。プロパティで title を指定しない場合は、最初の h1 markdown がページタイトルになるそうです。せっかくなのでそれを試してみます。

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": "f0a1bf337ff04d24b5b6efb3ea006b15"
    },
    "markdown": "# New page with markdown\n## Paragraph 2\n- [ ] To Do\n- [x] Checked To Do\n"
  }'

実行してみます。

> make
sh create_page_parent_data_source_with_markdown.sh > create_page_parent_data_source_with_markdown.json
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1457    0  1277  100   180   1560    219 --:--:-- --:--:-- --:--:--  1781
sleep 1

ページが作成されました。Checked の To Do も作成できるのですね。

markdown から作成したページ

テストの作成

テストは以下のように作成してみました。build_child_pagecreate_child_page に markdown キーワード引数を追加してみます。

    describe "create with markdown" do
      markdown = "# New page with markdown\n## Paragraph 2\n- [ ] To Do\n- [x] Checked To Do\n"
      let(:parent_data_source) { DataSource.new id: TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID }

      context "when build_child_page" do
        let(:target) { parent_data_source.build_child_page markdown: markdown }

        it { expect(target).to be_new_record }

        it_behaves_like "property values json", {
          "parent" => {
            "data_source_id" => TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID,
          },
          "markdown" => markdown,
        }

        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 "315d8e4e98ab8108901dcfa2d3993f6e" }
          it { expect(target).not_to be_new_record }
        end
      end

      context "create_child_page" do
        context "not dry_run" do
          let(:target) { parent_data_source.create_child_page markdown: markdown }

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

        context "dry_run" do
          let(:target) { parent_data_source.build_child_page markdown: markdown }
          let(:dry_run) { parent_data_source.create_child_page markdown: markdown, dry_run: true }

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

当然ながら unknown keyword となるので、これを実装してみます。

     ArgumentError:
       unknown keyword: :markdown

markdown 引数を追加

まず、DataSource#build_child_pageDataSource#create_child_pagemarkdown 引数を追加します。

    # @param [Array<Property, Class, String>] assign
    # @return [NotionRubyMapping::Base]
    # @see https://www.notion.so/hkob/DataSource-1462b24502424539a4231bedc07dc2f5#c217ce78020a4de79b720790fce3092d
    # @param [String, NilClass] template_page
    # @param [String, NilClass] markdown
    def build_child_page(*assign, template_page: nil, markdown: nil)
      assign = properties.map { |p| [p.class, p.name] }.flatten if assign.empty?
      page = Page.new assign: assign, parent: {"data_source_id" => @id}, template_page: template_page,
                      markdown: markdown
      pp = page.properties
      pp.clear_will_update
      yield page, pp if block_given?
      page
    end

    # @param [Array<Property, Class, String>] assign
    # @param [String, NilClass] template_page
    # @param [String, NilClass] markdown
    # @param [Boolean] dry_run true if dry_run
    # @return [NotionRubyMapping::Base]
    # @see https://www.notion.so/hkob/DataSource-1462b24502424539a4231bedc07dc2f5#c217ce78020a4de79b720790fce3092d
    def create_child_page(*assign, template_page: nil, markdown: nil, dry_run: false)
      assign = properties.map { |p| [p.class, p.name] }.flatten if assign.empty?
      page = Page.new assign: assign, parent: {"data_source_id" => @id}, template_page: template_page,
                      markdown: markdown
      pp = page.properties
      pp.clear_will_update
      yield page, pp if block_given?
      page.save dry_run: dry_run
    end

この中で Page#new にも markdown 引数が必要となります。これは Page の親クラスである Base の initialize に追加します。

  class Base
    # @param [Hash, nil] json
    # @param [String, nil] id
    # @param [Array<Property, Class, String>] assign
    # @param [Block, nil] parent
    # @param [String, nil] template_page
    # @param [String, nil] position
    # @param [String, nil] markdown
    def initialize(json: nil, id: nil, assign: [], parent: nil, template_page: nil, position: nil, markdown: 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
      if template_page == "default"
        payload_json["template"] = {"type" => "default"}
      elsif template_page
        payload_json["template"] = {"type" => "template_id", "template_id" => template_page.id}
      end
      if %w[page_start page_end].include? position
        payload_json["position"] = {"type" => position}
      elsif position
        payload_json["position"] = {"type" => "after_block", "after_block" => {"id" => position}}
      end
      payload_json["markdown"] = markdown if markdown
      @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

ここまで実装するとテストで WebMock の呼び出しとなります。当然 WebMock に該当する payload を登録していませんので、WebMock のテンプレートエラーになりました。markdown の payload が与えられたら、 create_page_parent_data_source_with_markdown.json が読み込まれるように stubs を設定しました。

    def create_page
      generate_stubs_sub :post, __method__, :pages_path, {
        (中略)
        parent_data_source_with_markdown: [nil, 200, {
          "parent" => {
            "data_source_id" => TestConnection::TEST_TEMPLATE_DATA_SOURCE_ID,
          },
          "markdown" => "# New page with markdown\n## Paragraph 2\n- [ ] To Do\n- [x] Checked To Do\n",
        }],
      }
    end

テスト結果

テスト結果は以下のようになりました。

(前略)
  create with markdown
    when build_child_page
      is expected to be new record
      behaves like property values json
        is expected to eq {"markdown" => "# New page with markdown\n## Paragraph 2\n- [ ] To Do\n- [x] Checked To Do\n", "parent" => {"data_source_id" => "293d8e4e98ab80e7842e000befaa8ed5"}}
      dry_run
        behaves like dry run
          is expected to eq "#!/bin/sh\ncurl -X POST 'https://api.notion.com/v1/pages' \\\n  -H 'Notion-Version: 2025-09-03' \\\n...\"markdown\":\"# New page with markdown\\n## Paragraph 2\\n- [ ] To Do\\n- [x] Checked To Do\\n\"}'"
      create
        is expected to eq "315d8e4e98ab8108901dcfa2d3993f6e"
        is expected not to be new record
        behaves like property values json
          is expected to eq {}
    create_child_page
      not dry_run
        is expected to eq "315d8e4e98ab8108901dcfa2d3993f6e"
        is expected not to be new record
        behaves like property values json
          is expected to eq {}
      dry_run
        behaves like dry run
          is expected to eq "#!/bin/sh\ncurl -X POST 'https://api.notion.com/v1/pages' \\\n  -H 'Notion-Version: 2025-09-03' \\\n...\"markdown\":\"# New page with markdown\\n## Paragraph 2\\n- [ ] To Do\\n- [x] Checked To Do\\n\"}'"
(後略)

Finished in 0.14716 seconds (files took 0.82486 seconds to load)
139 examples, 0 failures

おわりに

とりあえず、まず data_soruce を親とした create_child_page, build_child_page のテストおよび実装が完了しました。明日はさらに続きの処理をテスト・実装していきます。

hkob.notion.site