はじめに
hkob の雑記録の第454回目(通算27日目)は、TableView をテスト・実装していきます。
spec/views/table_view_spec.rb の作成
まず、View API に関しては blocks とは別にディレクトリを作成してここにテスト・実装を追加していきます。まずはディレクトリとファイルを作成します。
mkdir -p {spec,lib}/notion_ruby_mapping/views touch spec/notion_ruby_mapping/views/{view,table_view}_spec.rb lib/notion_ruby_mapping/views/{view,table_view}.rb
まず、table_view_spec.rb を記載します。まずは retrieve して id が取得できることを確認します。
# frozen_string_literal: true require_relative "../../spec_helper" module NotionRubyMapping RSpec.describe TableView do tc = TestConnection.instance let!(:nc) { tc.nc } describe "find" do context "when real access" do let(:target) { View.find TestConnection::TABLE_VIEW_ID } it { expect(target).to be_a TableView } it { expect(target.id).to eq nc.hex_id(TestConnection::TABLE_VIEW_ID) } end context "when dry run" do let(:dry_run) { View.find TestConnection::TABLE_VIEW_ID, dry_run: true } it_behaves_like "dry run", :get, :view_path, id: TestConnection::TABLE_VIEW_ID end end end end
ここで、bundle exec guard を実行したところ、こんな感じで警告が出るようになってしまいました。
> be guard /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:241: warning: already initialized constant Gem::Platform::JAVA /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:279: warning: previous definition of JAVA was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:242: warning: already initialized constant Gem::Platform::MSWIN /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:280: warning: previous definition of MSWIN was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:243: warning: already initialized constant Gem::Platform::MSWIN64 /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:281: warning: previous definition of MSWIN64 was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:244: warning: already initialized constant Gem::Platform::MINGW /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:282: warning: previous definition of MINGW was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:245: warning: already initialized constant Gem::Platform::X64_MINGW /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:284: warning: previous definition of X64_MINGW was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:247: warning: already initialized constant Gem::Platform::WINDOWS /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:286: warning: previous definition of WINDOWS was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:248: warning: already initialized constant Gem::Platform::X64_LINUX /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:287: warning: previous definition of X64_LINUX was here /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/gems/4.0.0/gems/bundler-2.4.13/lib/bundler/rubygems_ext.rb:249: warning: already initialized constant Gem::Platform::X64_LINUX_MUSL /Users/hkob/.local/share/mise/installs/ruby/4.0.2/lib/ruby/site_ruby/4.0.0/rubygems/platform.rb:288: warning: previous definition of X64_LINUX_MUSL was here 18:34:16 - INFO - Guard::RSpec is running 18:34:16 - INFO - Guard is now watching at '/Users/hkob/Library/CloudStorage/Dropbox/ruby/notion_ruby_mapping' [1] guard(main)>
bundler が 2.4.13 と古いため、rubygems/platform.rb とぶつかっているようです。せっかくなので、久々に bundle update も実行してみました。
gem update bundler gem update --system bundle update
すると、ostruct の load error が出るようになってしまいました。development の時だけ、ostruct を読み込むようにしておきます。
spec.add_development_dependency "ostruct"
faraday も 2.14.1 に上がってしまったので、WebMock の User-Agent のバージョンも上げておきます。
def stub(method, json_file, path, code, payload = nil) request = { headers: { "Accept" => "*/*", "Accept-Encoding" => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "Authorization" => "Bearer #{notion_token}", "Notion-Version" => NotionRubyMapping::NOTION_VERSION, "User-Agent" => "Faraday v2.14.1", }, }
ここまででとりあえず、TableView 以外のテストは全て通過しました。
Finished in 0.74452 seconds (files took 0.75217 seconds to load) 3072 examples, 0 failures, 1 error occurred outside of examples
エラー箇所は以下のようになっています。
NameError: uninitialized constant NotionRubyMapping::TableView # ./spec/notion_ruby_mapping/views/table_view_spec.rb:6:in '<module:NotionRubyMapping>' # ./spec/notion_ruby_mapping/views/table_view_spec.rb:5:in '<top (required)>' No examples found.
TableView は当然作成していないので、まずファイルの読み込み部を記載します。
{
(中略)
views: %w[view table_view],
}.each do |key, values|
values.each do |klass|
require_relative "notion_ruby_mapping/#{key}/#{klass}"
end
次に TableView クラスを生成します。基本は親の View で設定をします。
# frozen_string_literal: true module NotionRubyMapping # Notion view class TableView < View # @return [String] def type "table" end end end
当然、今度は View クラスがないと言われます。
NameError: uninitialized constant NotionRubyMapping::View
View クラスを作成します。
# frozen_string_literal: true module NotionRubyMapping # Notion View object (Parent of TableView, BoardView, ListView, CalendarView, GalleryView, etc) class View # @return [String] def type raise NotImplementedError end end end
これで find のエラーになりました。テストと雛形の作成までは完了です。
Failures: 1) NotionRubyMapping::TableView find Failure/Error: let(:target) { View.find TestConnection::TABLE_VIEW_ID } NoMethodError: undefined method 'find' for class NotionRubyMapping::View # ./spec/notion_ruby_mapping/views/table_view_spec.rb:11:in 'block (3 levels) in <module:NotionRubyMapping>' # ./spec/notion_ruby_mapping/views/table_view_spec.rb:13:in 'block (3 levels) in <module:NotionRubyMapping>'
find の実装
まず、find を実装してみます。ほぼ Block#find と同様になります。dry_run_script は Base のものと変わらないので、間借りします。
# @param [String] id # @param [Boolean] dry_run # @return [View, String] def self.find(id, dry_run: false) nc = NotionCache.instance view_id = view_id id if dry_run Base.dry_run_script :get, nc.view_path(view_id) else nc.view view_id end end
find にはビューのリンクが渡される場合があるので、self.view_id で view_id を取得します。これもテストを先に記載します。
# frozen_string_literal: true require_relative "../../spec_helper" module NotionRubyMapping RSpec.describe View do describe "view_id" do it "extracts view id from url" do url = "https://www.notion.so/Notion-Ruby-Mapping-Table-View-1e5c8b9f0a3b4c8e9d2f1a2b3c4d5e6?v=1234567890abcdef1234567890abcdef" expect(View.view_id(url)).to eq "1234567890abcdef1234567890abcdef" end it "converts hex string to view id" do hex_str = "1e5c8b9f0a3b4c8e9d2f1a2b3c4d5e6" expect(View.view_id(hex_str)).to eq "1e5c8b9f0a3b4c8e9d2f1a2b3c4d5e6" end end end end
self.view_id の実装は以下のようになります。
# @param [String] str # @return [String] view_id def self.view_id(str) if /^http/.match str /\?v=([\da-f]{32})/.match(str)[1] else NotionCache.instance.hex_id str end end
次は NotionCache#view_path がないという警告が出ているので、view_path のテストを作成します。
describe "view_path" do it { expect(nc.view_path("ABC")).to eq "v1/views/ABC" } end
実装は簡単です。
# @param [String] view_id # @return [String] view_path def view_path(view_id) "v1/views/#{view_id}" end
NotionCache の実装追加
ここから先は既存の NotionCache への実装になります。NotionCache では名前の通り、一度読んだオブジェクトはキャッシュ保存されます。このため、object_for_key では object の状況で Base か View かを選択する必要が出てきます。
# @param [String] id id (with or without "-") # @return [NotionRubyMapping::Base] def object_for_key(id) key = hex_id(id) return @object_hash[key] if @use_cache && @object_hash.key?(key) json = yield(@client) base_class = json["object"] == "view" ? View : Base ans = base_class.create_from_json json @object_hash[key] = ans if @use_cache ans end
NotionCache#view は object_for_key を使って以下のようになります。
# @param [String] id # @return [NotionRubyMapping::View] def view(id) object_for_key(id) { view_request id } end
ここまで実装すると WebMock のエラーになりました。
Real HTTP connections are disabled. Please stub: GET https://api.notion.com/v1/views/32fd8e4e98ab81e88ba9000c227aaf76 with headers {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'Bearer secret_J08RBf9SlofiMhuIeJZc6AnI5qHXwDOaq4RolJFaaZ0', 'Notion-Version'=>'2026-03-11', 'User-Agent'=>'Faraday v2.14.1'}
まず、generate_stubs に retrieve_view を追加し、retrive_view メソッドを作成します。
def retrieve_view generate_stubs_sub :get, __method__, :view_path, { table_view: [TABLE_VIEW_ID, 200], } end
これで、View#create_from_json の実装を残すのみとなりました。
2) NotionRubyMapping::TableView find when real access Failure/Error: ans base_class.create_from_json json NoMethodError: undefined method 'create_from_json' for class NotionRubyMapping::View
View#create_from_json の実装
あとは JSON から View object を作成する create_from_json を実装します。
# @param [Hash] json # @return [NotionRubyMapping::View] def self.create_from_json(json) base_class = { "table" => TableView, }[json["type"]] raise NotImplementedError, "View type #{json["type"]} is not implemented yet." unless base_class base_class.new json: json end
TableView#initialize を実装します。
class TableView < View # @param [String, nil] id # @param [String, nil] json def initialize(id: nil, json: nil) super id: id, json: json end
親の initialize が呼ばれるので、View#initialize も実装します。
class View # @param [String, nil] id # @param [String, nil] json def initialize(id: nil, json: nil) @nc = NotionCache.instance @json = json @id = @nc.hex_id(id || @json && @json["id"]) end attr_reader :id, :json
ここまでで TableView オブジェクトが生成されたこと、id が取得されたこと、dry_run でスクリプトが生成されたことを確認できました。
NotionRubyMapping::TableView find when real access is expected to be a kind of NotionRubyMapping::TableView is expected to eq "32fd8e4e98ab81e88ba9000c227aaf76" when dry run behaves like dry run is expected to eq "#!/bin/sh\ncurl 'https://api.notion.com/v1/views/32fd8e4e98ab81e88ba9000c227aaf76' \\\n -H 'Notion-Version: 2026-03-11' \\\n -H 'Authorization: Bearer '\"$NOTION_API_KEY\"''" Finished in 0.00587 seconds (files took 0.21088 seconds to load) 3 examples, 0 failures
おわりに
とりあえず、JSON からオブジェクトを生成するところまで実装しました。まだ、JSON を内包しているだけなので、これらを取り込む処理を明日以降にテスト・実装していきます。