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
0
12
そのオブジェクト、何を保証してくれますか? - GuideRailのススメ -
"そのオブジェクト、何を保証してくれますか?" LTの資料です。5分に収まってないです。
Kanazawa.rb meetup #156 at ビブリオバウムでの発表です。
wtnabe
August 16, 2025
Tweet
Share
More Decks by wtnabe
See All by wtnabe
Ruby de Railway Oriented Programming
wtnabe
0
6
Bindanのススメ
wtnabe
0
9
Effective Jekyll
wtnabe
0
59
5 min Jekyll/Liquid Plugin cooking
wtnabe
0
26
Ruby de Wasm
wtnabe
0
52
Cloud Native Buildpacksって結局どうなの?
wtnabe
0
44
Decoupled System with Turbo Frame
wtnabe
1
120
join-kanazawarb-or-7years-passed-since-it-was-borned
wtnabe
0
790
let-me-edit-with-editor
wtnabe
0
340
Other Decks in Programming
See All in Programming
Webinar: AI-Powered Development: Transformiere deinen Workflow mit Coding Tools und MCP Servern
danielsogl
0
110
PHPカンファレンス関西2025 基調講演
sugimotokei
6
1.1k
JetBrainsのAI機能の紹介 #jjug
yusuke
0
200
パスタの技術
yusukebe
1
300
Understanding Kotlin Multiplatform
l2hyunwoo
0
250
大規模FlutterプロジェクトのCI実行時間を約8割削減した話
teamlab
PRO
0
460
[DevinMeetupTokyo2025] コード書かせないDevinの使い方
takumiyoshikawa
2
280
プロダクトという一杯を作る - プロダクトチームが味の責任を持つまでの煮込み奮闘記
hiliteeternal
0
450
サイトを作ったらNFCタグキーホルダーを爆速で作れ!
yuukis
0
140
Reactの歴史を振り返る
tutinoko
1
180
自作OSでDOOMを動かしてみた
zakki0925224
1
1.3k
Google I/O Extended Incheon 2025 ~ What's new in Android development tools
pluu
1
260
Featured
See All Featured
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
Rails Girls Zürich Keynote
gr2m
95
14k
Documentation Writing (for coders)
carmenintech
73
5k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
1k
StorybookのUI Testing Handbookを読んだ
zakiyama
30
6k
Visualization
eitanlees
146
16k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
36
2.5k
Producing Creativity
orderedlist
PRO
347
40k
What's in a price? How to price your products and services
michaelherold
246
12k
Code Review Best Practice
trishagee
69
19k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
Speed Design
sergeychernyshev
32
1.1k
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) | あーありがち