NotionRubyMapping の場所プロパティ対応 : hkob の雑記録 (279)

はじめに

hkob の雑記録の第279回目は、 NotionRubyMapping で昨日解説した場所プロパティを扱えるようにした件を解説していきます。

テストページ作成

まずはテストページを作成します。Place Property だけを持つデータベースを作成し、1ページ用意しています。

作成したテストデータベース

まずは、データソースIDを取得します。

データソースIDを取得

DataSource の確認

データソースを取得します。

ds = DataSource.find "282d8e4e-98ab-804b-b7dc-000bf0ca279e"
=> NotionRubyMapping::DataSource-282d8e4e98ab804bb7dc000bf0ca279e

Place プロパティを確認します。当然ながら Irregular property type となります。

ds.properties["Place"]
/Users/hkob/.local/share/mise/installs/ruby/3.4.6/lib/ruby/gems/3.4.0/gems/notion_ruby_mapping-3.0.0/lib/notion_ruby_mapping/properties/property.rb:125:in 'NotionRubyMapping::Property.create_from_json': Irregular property type: place (StandardError)
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/lib/ruby/gems/3.4.0/gems/notion_ruby_mapping-3.0.0/lib/notion_ruby_mapping/controllers/property_cache.rb:20:in 'NotionRubyMapping::PropertyCache#[]'
    from (irb):3:in '<main>'
    from <internal:kernel>:168:in 'Kernel#loop'
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/lib/ruby/gems/3.4.0/gems/irb-1.15.2/exe/irb:9:in '<top (required)>'
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/bin/irb:25:in 'Kernel#load'
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/bin/irb:25:in '<main>'

JSON を確認してみます。設定項目もないので、特に値も何もありません。

ds.json
=>
{"object" => "data_source",
 "id" => "282d8e4e-98ab-804b-b7dc-000bf0ca279e",
 (中略)
 "properties" =>
  {"Place" =>
    {"id" => "AmdZ",
     "name" => "Place",
     "description" => nil,
     "type" => "place",
     "place" => {}}, 

Page の確認

同様にページプロパティの確認のために、ページデータも取得します。

page = Page.find "https://www.notion.so/hkob/TMCIT-Shinagawa-282d8e4e98ab8048bd3ecd8a933392c7?v=282d8e4e98ab801e873c000c24b95e43&source=copy_link"
=> NotionRubyMapping::Page-282d8e4e98ab8048bd3ecd8a933392c7

当然ながらこちらも Place プロパティにアクセスするとエラーになります。

page.properties["Place"]
/Users/hkob/.local/share/mise/installs/ruby/3.4.6/lib/ruby/gems/3.4.0/gems/notion_ruby_mapping-3.0.0/lib/notion_ruby_mapping/properties/property.rb:125:in 'NotionRubyMapping::Property.create_from_json': Irregular property type: place (StandardError)
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/lib/ruby/gems/3.4.0/gems/notion_ruby_mapping-3.0.0/lib/notion_ruby_mapping/controllers/property_cache.rb:20:in 'NotionRubyMapping::PropertyCache#[]'
    from (irb):6:in '<main>'
    from <internal:kernel>:168:in 'Kernel#loop'
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/lib/ruby/gems/3.4.0/gems/irb-1.15.2/exe/irb:9:in '<top (required)>'
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/bin/irb:25:in 'Kernel#load'
    from /Users/hkob/.local/share/mise/installs/ruby/3.4.6/bin/irb:25:in '<main>'

こちらも同じように JSON を確認してみます。こちらは place には nil が入っていました。

page.json
=>
{"object" => "page",
 "id" => "282d8e4e-98ab-8048-bd3e-cd8a933392c7",
 (中略)
 "properties" =>
  {"Place" => {"id" => "AmdZ", "type" => "place", "place" => nil},

テストデータの準備

まず、DataSource の Property 表現である Property object を用意します。 spec/fixtures/place_property_object.json を用意しました。

{
  "id": "AmdZ",
  "name": "Place",
  "type": "place",
  "place": {}
}

Page property は API で取得できるので、 retrieve_property_place.sh を用意しました。

curl 'https://api.notion.com/v1/pages/282d8e4e98ab8048bd3ecd8a933392c7/properties/AmdZ' \
    -H 'Notion-Version: 2025-09-03' \
    -H 'Authorization: Bearer '"$NOTION_API_KEY"''

make すると retrieve_property_place.json を取得できました。最近は、request_id が返ってくるのですね。

{
  "object": "property_item",
  "type": "place",
  "id": "AmdZ",
  "place": null,
  "request_id": "3ebb46a3-f127-4ff7-9d20-050afd32fd4d"
}

テストの作成

テストである spec/notion_ruby_mapping/place_property_spec.rbbutton_property_spec.rb を書き換えて作成しました。

# frozen_string_literal: true

module NotionRubyMapping
  RSpec.describe PlaceProperty do
    tc = TestConnection.instance
    let(:no_content_json) { {"id" => "AmdZ"} }
    let(:place_page_id) { TestConnection::PLACE_PAGE_ID }
    let(:property_cache_first) { PropertyCache.new base_type: "page", page_id: place_page_id }

    correct = {"Place" => {"type" => "place", "place" => nil}}

    context "Database property" do
      context "created from json" do
        let(:target) { Property.create_from_json "Place", tc.read_json("place_property_object"), "database" }

        it_behaves_like "has name as", "Place"
        it_behaves_like "will not update"
        it_behaves_like "assert different property", :property_values_json
        it_behaves_like "update property schema json", {}
        it_behaves_like "raw json", "place", {}
      end
    end

    context "DataSource property" do
      context "created from json" do
        let(:target) { Property.create_from_json "Place", tc.read_json("place_property_object"), "data_source" }

        it_behaves_like "has name as", "Place"
        it_behaves_like "will not update"
        it_behaves_like "assert different property", :property_values_json
        it_behaves_like "update property schema json", {}
        it_behaves_like "raw json", "place", {}
      end
    end

    context "Page property" do
      context "created from json" do
        let(:target) { Property.create_from_json "Place", tc.read_json("retrieve_property_place") }

        it_behaves_like "has name as", "Place"
        it_behaves_like "will not update"
        it_behaves_like "property values json", correct
        it_behaves_like "assert different property", :update_property_schema_json
        it_behaves_like "assert different property", :property_schema_json
      end

      context "created from json (no content)" do
        let(:target) { Property.create_from_json "Place", no_content_json, "page", property_cache_first }

        it_behaves_like "has name as", "Place"
        it_behaves_like "will not update"
        it { expect(target.contents?).to be_falsey }

        it_behaves_like "assert different property", :update_property_schema_json

        # hook property_values_json / created_by to retrieve a property item
        it_behaves_like "property values json", correct
      end
    end
  end
end

ここで、テスト内で Property 読み込みの API 呼び出しを実行するので、WebMock による stub を追加しておきます。API 呼び出しが行われると、上で取得しておいた retrieve_property_place.json をこっそり返す処理をしています。

    def retrieve_property
      generate_stubs_sub :get, __method__, :page_property_path, {
        button: [[BUTTON_PAGE_ID, "%7CyVi"], 200],
        checkbox: [[DB_FIRST_PAGE_ID, "Lbi%5D"], 200],
        created_by: [[DB_FIRST_PAGE_ID, "eR%3D~"], 200],
        created_time: [[DB_FIRST_PAGE_ID, "WsEj"], 200],
        date: [[DB_FIRST_PAGE_ID, "SPrp"], 200],
        email: [[DB_FIRST_PAGE_ID, "p%7Ci%3F"], 200],
        files_external: [[DB_FIRST_PAGE_ID, "qEdK"], 200],
        files_internal: [[DB_SECOND_PAGE_ID, "qEdK"], 200],
        formula: [[DB_FIRST_PAGE_ID, "%5D~iZ"], 200],
        last_edited_by: [[DB_FIRST_PAGE_ID, "LQGa"], 200],
        last_edited_time: [[DB_FIRST_PAGE_ID, "X%3E%40X"], 200],
        multi_select: [[DB_FIRST_PAGE_ID, "Kjx%7D"], 200],
        number: [[DB_FIRST_PAGE_ID, "swq%5C"], 200],
        people: [[DB_FIRST_PAGE_ID, "_x%3E%3D"], 200],
        place: [[PLACE_PAGE_ID, "AmdZ"], 200],
        phone_number: [[DB_FIRST_PAGE_ID, "%7CNHO"], 200],
        relation: [[DB_FIRST_PAGE_ID, "%3CnJT"], 200],
        relation_with_many_relations: [[DB_MANY_CHILDREN_PAGE_ID, "%60%5B%3E%7B"], 200],
        rich_text: [[DB_FIRST_PAGE_ID, "flUp"], 200],
        rich_text_limit_5: [[DB_SECOND_PAGE_ID, "flUp?page_size=5"], 200],
        rich_text_next_5: [[DB_SECOND_PAGE_ID, "flUp?page_size=5&start_cursor=PLuaRX"], 200],
        rich_text_last_5: [[DB_SECOND_PAGE_ID, "flUp?page_size=5&start_cursor=xQVa1H"], 200],
        rollup: [[DB_FIRST_PAGE_ID, "STe_"], 200],
        select: [[DB_FIRST_PAGE_ID, "zE%7C%3F"], 200],
        status: [[STATUS_PAGE_ID, "Qy~%3E"], 200],
        title: [[DB_FIRST_PAGE_ID, "title"], 200],
        title_top: [[TOP_PAGE_ID, "title"], 200],
        unique_id: [[DB_FIRST_PAGE_ID, "%7BGE%7C"], 200],
        verification: [[WIKI_PAGE_ID, "verification"], 200],
        url: [[DB_FIRST_PAGE_ID, "tvis"], 200],
      }
    end

実装の追加

実装である lib/notion_ruby_mapping/place_property.rb もほぼ同様に作成しました。

# frozen_string_literal: true

module NotionRubyMapping
  # Button property
  class PlaceProperty < Property
    TYPE = "place"

    ### Public announced methods

    ## Common methods

    # @return [Boolean, Hash, nil]
    def place
      @json
    end

    # @param [String, Symbol] name Property name
    # @param [Boolean, Hash] json
    def initialize(name, will_update: false, base_type: "page", property_id: nil, property_cache: nil, json: nil)
      super name, will_update: will_update, base_type: base_type, property_id: property_id,
                  property_cache: property_cache
      @json = json
    end

    ## Page property only methods

    # @return [Hash]
    def property_values_json
      assert_page_property __method__
      {@name => {"place" => @json, "type" => "place"}}
    end
  end
end

このファイルを require するように notion_ruby_mapping.rb のプロパティに追加しています。

  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],

あとは、親クラスの Property におけるクラス振り分けを記載すればいいはずです。type が place の場合には、子クラスである PlaceProperty が選択できるようにします。

        klass = {
          button: ButtonProperty,
          checkbox: CheckboxProperty,
          created_time: CreatedTimeProperty,
          date: DateProperty,
          formula: FormulaProperty,
          last_edited_time: LastEditedTimeProperty,
          rollup: RollupProperty,
          email: EmailProperty,
          files: FilesProperty,
          created_by: CreatedByProperty,
          last_edited_by: LastEditedByProperty,
          multi_select: MultiSelectProperty,
          relation: RelationProperty,
          number: NumberProperty,
          people: PeopleProperty,
          phone_number: PhoneNumberProperty,
          place: PlaceProperty,
          select: SelectProperty,
          status: StatusProperty,
          title: TitleProperty,
          rich_text: RichTextProperty,
          unique_id: UniqueIdProperty,
          url: UrlProperty,
          verification: VerificationProperty,
        }[type.to_sym]
        raise StandardError, "Irregular property type: #{type}" unless klass

テスト結果確認

ここまで記載して、テストは無事に成功しました。

> be guard
20:20:04 - INFO - Guard::RSpec is running
20:20:04 - INFO - Guard is now watching at '/Users/hkob/Library/CloudStorage/Dropbox/ruby/notion_ruby_mapping'
20:20:20 - INFO - Running: spec/notion_ruby_mapping/properties/place_property_spec.rb

NotionRubyMapping::PlaceProperty
  Database property
    created from json
      behaves like has name as
        has name as Place
      behaves like will not update
        does not update
      behaves like assert different property
        property_values_json
      behaves like update property schema json
        is expected to eq {}
      behaves like raw json
        is expected to eq {}
  DataSource property
    created from json
      behaves like has name as
        has name as Place
      behaves like will not update
        does not update
      behaves like assert different property
        property_values_json
      behaves like update property schema json
        is expected to eq {}
      behaves like raw json
        is expected to eq {}
  Page property
    created from json
      behaves like has name as
        has name as Place
      behaves like will not update
        does not update
      behaves like property values json
        is expected to eq {"Place" => {"place" => nil, "type" => "place"}}
      behaves like assert different property
        update_property_schema_json
      behaves like assert different property
        property_schema_json
    created from json (no content)
      is expected to be falsey
      behaves like has name as
        has name as Place
      behaves like will not update
        does not update
      behaves like assert different property
        update_property_schema_json
      behaves like property values json
        is expected to eq {"Place" => {"place" => nil, "type" => "place"}}

Finished in 0.01158 seconds (files took 0.44386 seconds to load)
20 examples, 0 failures

NotionRubyMapping のアップデート

この変更を GitHub にコミットし、rake release して v3.0.1 をリリースしました。

rubygems.org

X にもポストしています。

おわりに

とりあえずこれで場所プロパティを使ったページにアクセスしてもエラーにならなくなりました。特に API から利用はできないのですが、JSON として type が返ってくるものについては対応しないといけないので、これからも迅速に対応したいと思います。

hkob.notion.site