Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
そのオブジェクト、何を保証してくれますか? - GuideRailのススメ -
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
wtnabe
August 16, 2025
Programming
80
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
42
Rubyでもモノリポしたい - 調査、おさわり編 -
wtnabe
0
71
Ruby de Railway Oriented Programming
wtnabe
0
120
Bindanのススメ
wtnabe
0
70
Effective Jekyll
wtnabe
0
100
5 min Jekyll/Liquid Plugin cooking
wtnabe
0
73
Ruby de Wasm
wtnabe
0
100
Cloud Native Buildpacksって結局どうなの?
wtnabe
0
89
Decoupled System with Turbo Frame
wtnabe
1
180
Other Decks in Programming
See All in Programming
Hello, Hiroshima Geospatial Data! — Exploring DoboX with Python
ra0kley
0
180
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
0
140
AWS Step Functions 大規模並列の壁を越える / jaws-sonic-2026-niigata-step-functions
kasacchiful
PRO
0
400
Can LLMs Replicate 4 Years of Compose Migration? Exploring the boundaries of automation with 279 XML files from a real product
makun
0
130
「AI時代、配布するPythonコードをどう守るか: 難読化の実験と判断軸」 #PyconJP2026
pkshadeck
PRO
2
180
Hono + Inertia + React で LP を構築した話
oukayuka
2
220
信頼性の目標を誰も求めてない
shubox
0
500
iOS開発×AI駆動開発 〜最近使って便利だったスキルの話〜
nogu66
0
140
Gmail/Google DriveをトリガーにAIエージェントを動かそう! / Run AI agents with Gmail/Google Drive as triggers!
har1101
3
470
AIに既存システムを理解させる技術 ~レガシーを見捨てないハーネスエンジニアリング入門~
ochtum
0
210
AI駆動開発にグラフDBを重ねてみた
satoshi256kbyte
2
760
Building an Out-of-Order CPU
latte72
1
720
Featured
See All Featured
Measuring & Analyzing Core Web Vitals
bluesmoon
9
990
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.1k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
Automating Front-end Workflow
addyosmani
1369
210k
Documentation Writing (for coders)
carmenintech
77
5.5k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.5k
WENDY [Excerpt]
tessaabrams
12
39k
ラッコキーワード サービス紹介資料
rakko
1
4.8M
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
370
Fashionably flexible responsive web design (full day workshop)
malarkey
408
67k
[SF Ruby Conf 2025] Rails X
palkan
2
1.4k
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) | あーありがち