Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
そのオブジェクト、何を保証してくれますか? - GuideRailのススメ -
Search
wtnabe
August 16, 2025
Programming
78
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
そのオブジェクト、何を保証してくれますか? - GuideRailのススメ -
"そのオブジェクト、何を保証してくれますか?" LTの資料です。5分に収まってないです。
Kanazawa.rb meetup #156 at ビブリオバウムでの発表です。
wtnabe
August 16, 2025
More Decks by wtnabe
See All by wtnabe
新しいフロントプロセスとドキュメントフォーマットを考えてみた
wtnabe
0
23
Rubyでもモノリポしたい - 調査、おさわり編 -
wtnabe
0
65
Ruby de Railway Oriented Programming
wtnabe
0
110
Bindanのススメ
wtnabe
0
68
Effective Jekyll
wtnabe
0
100
5 min Jekyll/Liquid Plugin cooking
wtnabe
0
69
Ruby de Wasm
wtnabe
0
94
Cloud Native Buildpacksって結局どうなの?
wtnabe
0
84
Decoupled System with Turbo Frame
wtnabe
1
180
Other Decks in Programming
See All in Programming
リアルな遅延を測る仕様
kota_yata
1
120
ソフトウェアラスタライザ
fadis
1
280
What's New in Android 2026
veronikapj
0
270
FastAPI の並行処理モデルを完全に理解する
hoto17296
7
2.4k
自動化したのに回らない テスト運用の壁―AI時代の品質責任と生産性
mfunaki
0
370
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
230
170k Jobs a Day on GKE: Scaling Mercari's CI Platform - and What's Next for AI-Native Development
junyaokabe
0
120
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
450
「人を評価する AI」の設計と実装
ryoyanara
0
220
生成AI導入の「期待外れ」を乗り越える ー 開発フロー改革が目指す、真の組織変革
starfish719
0
5k
AI時代に設計が 最大の生産性レバーになる 意図駆動開発とデータを消さない設計|Don't Delete Your Data or Your Intent — Design as the Deepest Lever in the AI Era
tomohisa
1
1.1k
Go を使い始めて 2 ヶ月の学び / My first two months with Go
contour_gara
0
350
Featured
See All Featured
Being A Developer After 40
akosma
91
590k
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
460
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
420
The Language of Interfaces
destraynor
162
27k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
730
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.8k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
480
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.5k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
800
New Earth Scene 8
popppiees
3
2.5k
Prompt Engineering for Job Search
mfonobong
0
410
Transcript
そのオブジェクト、 何を保証してくれますか? - GuideRail のススメ - @wtnabe Kanazawa.rb meetup #156
at ビブリオバウム 2025-08-16 (Sat)
みなさん型書いてますか
Ruby はclass ベースオブジェクト指向 基本的に書けるものはオブジェクトでありclass がある class にはclass 固有の振る舞いがある
class として違いが書けているなら、 それは異なる型と言える
(静的型付けかどうかはのちほど)
プリミティブ(?) で確認するのが簡単 irb(main):001> "a" + 1 (irb):1:in `+': no implicit
conversion of \ Integer into String (TypeError) "a" + 1 ^ from (irb):1:in `<main>' String の + メソッドは引数に String を要求する Integer を与えても TypeError になる
では問題
以下を表現してみよう 1. 要素の定義の異なるHash 2. 公開可能なブログエントリ
Q1. 要素の定義の異なるHash
A1. Hashie::Dash を使う class MyHash1 < Hashie::Dash property :name end
class MyHash2 < Hashie::Dash property :key end MyHash1 == MyHash1 # => true MyHash1 == Myhash2 # => false
サブクラス作るだけで異なるっちゃ異なるけど、素のHash に要素の 定義はないので、違いを表現できてはいない 他にも実現方法はあるけどお手軽便利はHashie
Q2. 公開可能なブログエントリ
こんなモデル class BlogEntry include ActiveModel::API include ActiveModel::Attributes attribute :id, :big_integer
attribute :title, :string attribute :body, :string attribute :tags attribute :not_permitted, :boolean end 今回は承認フローについては無視
公開可能な条件 title, body, tags がちゃんと埋まっている not_permitted ではない これをどう表現するか?
すぐ思いつくのはメソッドを用意する class BlogEntry def publishable? @title.size > 0 && @body.size
> 0 && @tags.size > 0 && !@not_permitted end end
でも同じclass なので都度判定が必要 公開ボタンの ON/OFF attribute の nil を避ける (String に対するフィルタが
nil に対して動くと例外で死ぬ)
本当はこうなってると嬉しい class 内容 BlogEntry いろんな状態のエントリ BlogEntryPublishable 公開可能な状態のエントリ
でも状態は変わるやん?
そこでImmutable Value Object ですよ
Data ( Ruby 3.2+ )
基本はこんな感じ entry = BlogEntry.new BlogEntryPublishable = Data.define(:id, :title, :body, :tags)
# <data BlogEntryPublishable title=..> entry_pulishable = BlogEntryPublishable.new(*entry.attributes) if entry.publishable? def render(entry_publishable) # この中で呼ばれる処理では `&.` とか使わなくてもよい end
でもこれDRY じゃないし繊細 entry = BlogEntry.new # attributeの定義が二重化している BlogEntryPublishable = Data.define(:id,
:title, :body, :tags) # <data BlogEntryPublishable title=..> entry_publishable = BlogEntryPublishable.new(*entry.attributes) if entry.publishable? # * が繊細 attributes も publishable? もモノによる
合わせておかないと例外が上がってしまう BlogEntryPublishable = Data.define(:id, :title, :body, :tags) BlogEntryPublishable.new(name: "foo") #
`initialize': unknown keyword: :name (ArgumentError) ※ 例外の扱いはミスると致命傷なのでやりたくない
便利gem (GuideRail )作ったよ https://github.com/wtnabe/guide-rail
特徴 dry-schema を利用した宣言的な定義で記述ブレをなくす schema 定義に合わないデータも例外を上げずに処理できる 代表的なデータオブジェクトについては自動的に変換して処理する nil を潰せる 関数型のようにも書ける (
dry-monads を利用 )
schema 定義はdry-schema そのまま BlogEntryPublishableSchema = Dry::Schema.Params do required(:id).filled(:integer) required(:title).filled(:string) required(:body).filled(:string)
required(:not_permitted).maybe(:false) end schema validation の考え方は2020 年頃からある 流行ってきたのは2023 年のZod 辺りから
Creator Class を定義して使う class BlogEntryPublishableCreator extend GuideRail schema BlogEntryPublishableSchema class_name
:BlogEntryPublishable end BlogEntryPublishableCreator.from(blog_entry) # => #<data BlogEntryPublishable> # or # => Dry::Schema::Result
定義に合っていない場合は Dry::Schema::Result の形でエラーが返ってくる
さまざまなご要望にお応え
いろんなデータを受け付けてほしい # @param [untyped] data # @return [Hash] def accept(data)
if data.respond_to? :attributes # ActiveModel data.attributes.transform_keys(&:to_sym) elsif data.respond_to? :to_h # Struct, OpenStruct, Hashie, etc... data.to_h elsif data.is_a? Hash data elsif data.respond_to? :to_hash # Hash convertible data.to_hash end end
renderable option ( nil を潰したい ) SimpleNullableSchema = Dry::Schema.Params do
required(:name).maybe(:string) end class RenderableCreator extend GuideRail schema SimpleNullableSchema class_name :Renderable renderable true end RenderableCreator.from(name: nil) # => #<data Renderable name=""> RenderableCreator.from(name: "John") # => #<data Renderable name="John">
yield_block ( Data クラスを拡張したい ) .. class OptionalAndYieldAccepter extend GuideRail
class_name :ExtendedData schema OptionalSchema renderable true yield_block do alias_method :name_orig, :name define_method(:name) { "decorated #{name_orig}" } end end OptionalAndYieldAccepter.from({}).name.start_with? "decorated" # => true
monadic option ( if 書きたくない ) SimpleNonNullableSchema = Dry::Schema.Params do
required(:name).filled(:string) end class SimpleNonNullableMonadicCreator extend GuideRail schema SimpleNonNullableSchema class_name :SimpleNonNullableMonadic monadic true # 常に Dry::Monads::Result を返す end SimpleNonNullableMonadicCreator.from({}).either( ->(e) { e.value! }, # ^^^^^^ ->(e) { e.errors.to_h })
静的解析、静的型付けしたい できあがる class に対して RBS を書こう
まとめ Ruby 組み込みオブジェクトは class が挙動を明確にしている 「条件」あるいは「状態」に対して定義(class )を作ることで独自 オブジェクトもよりクリアになる そのために Immutable
Value Object が使える Data 周りの繊細さを取り除き、より安心して使える gem 作ったよ モナドいいかもよ?
参考 GuideRail というimmutable value object をうまく使うためのgem を 作ってみた (2025-07-06) |
あーありがち wtnabe/guide-rail: A opinionated factory library to create safe, immutable Data objects from various sources using dry-schema and Data ( Ruby 3.2+ ). 少しでも例外を安全に扱うために - Ruby とJavaScript 編 - (2024-08- 10) | あーありがち