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
37
そのオブジェクト、何を保証してくれますか? - 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
32
Bindanのススメ
wtnabe
0
25
Effective Jekyll
wtnabe
0
65
5 min Jekyll/Liquid Plugin cooking
wtnabe
0
32
Ruby de Wasm
wtnabe
0
60
Cloud Native Buildpacksって結局どうなの?
wtnabe
0
49
Decoupled System with Turbo Frame
wtnabe
1
130
join-kanazawarb-or-7years-passed-since-it-was-borned
wtnabe
0
800
let-me-edit-with-editor
wtnabe
0
350
Other Decks in Programming
See All in Programming
CSC509 Lecture 06
javiergs
PRO
0
260
大規模アプリのDIフレームワーク刷新戦略 ~過去最大規模の並行開発を止めずにアプリ全体に導入するまで~
mot_techtalk
1
470
なぜGoのジェネリクスはこの形なのか? Featherweight Goが明かす設計の核心
ryotaros
7
1.1k
What Spring Developers Should Know About Jakarta EE
ivargrimstad
0
260
Introduce Hono CLI
yusukebe
6
2.8k
iOSエンジニア向けの英語学習アプリを作る!
yukawashouhei
0
200
One Enishi After Another
snoozer05
PRO
0
120
Pull-Requestの内容を1クリックで動作確認可能にするワークフロー
natmark
2
520
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
450
What's new in Spring Modulith?
olivergierke
1
160
Domain-centric? Why Hexagonal, Onion, and Clean Architecture Are Answers to the Wrong Question
olivergierke
3
910
Writing Better Go: Lessons from 10 Code Reviews
konradreiche
0
1.8k
Featured
See All Featured
The Cult of Friendly URLs
andyhume
79
6.6k
Into the Great Unknown - MozCon
thekraken
40
2.1k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.5k
Music & Morning Musume
bryan
46
6.8k
Principles of Awesome APIs and How to Build Them.
keavy
127
17k
Fantastic passwords and where to find them - at NoRuKo
philnash
52
3.4k
VelocityConf: Rendering Performance Case Studies
addyosmani
332
24k
A Tale of Four Properties
chriscoyier
161
23k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
Java REST API Framework Comparison - PWX 2021
mraible
34
8.9k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4k
What's in a price? How to price your products and services
michaelherold
246
12k
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) | あーありがち