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
Rubyでもモノリポしたい - 調査、おわわり編 -
wtnabe
0
61
Ruby de Railway Oriented Programming
wtnabe
0
110
Bindanのススメ
wtnabe
0
65
Effective Jekyll
wtnabe
0
98
5 min Jekyll/Liquid Plugin cooking
wtnabe
0
67
Ruby de Wasm
wtnabe
0
94
Cloud Native Buildpacksって結局どうなの?
wtnabe
0
82
Decoupled System with Turbo Frame
wtnabe
1
180
join-kanazawarb-or-7years-passed-since-it-was-borned
wtnabe
0
850
Other Decks in Programming
See All in Programming
そこに3びきプロダクトがいるじゃろう——生成AI時代における“価値が届かない理由”の構造
kosuket
0
370
TSX の <Hoge<Fuga>> という構文に驚いた話 / tsx-type-argument-syntax
kanaru0928
0
160
AI Engineeringは、AIプロダクトだけのものか? 〜AIがソフトウェアを作る時代の新しい当たり前〜 / No AI in your product. AI Engineering in your development.
rkaga
4
320
「寝てても仕事が進む」Claude Codeで組む第二の脳
tomoyafujita2016
0
240
全PRの83%がAIレビューだけでマージできるようになった開発組織はその後どうなったか
athug
0
1k
メールのエイリアス機能を履き違えない
isshinfunada
0
180
AWS CDK を「作」ってみた 〜フルスクラッチで見えた CDK の裏側〜 / aws-cdk-from-scratch
gotok365
3
2.7k
為什麼你並不需要ViewModel / No, you don't need a ViewModel
lovee
1
460
テーブルをDELETEした
yuzneri
0
130
Apache Hive: そしてCloud Native Lakehouseへ
okumin
1
190
コーディングルールの鮮度を保ちたい for SRE NEXT 2026 / keep-fresh-go-internal-conventions-sre-next-2026
handlename
0
160
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
150
Featured
See All Featured
Code Review Best Practice
trishagee
74
20k
The Cult of Friendly URLs
andyhume
79
7k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
470
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.5k
Music & Morning Musume
bryan
47
7.3k
Unsuck your backbone
ammeep
672
58k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
The Language of Interfaces
destraynor
162
27k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
330
Designing for Performance
lara
611
70k
AI Search: Implications for SEO and How to Move Forward - #ShenzhenSEOConference
aleyda
1
1.3k
How Software Deployment tools have changed in the past 20 years
geshan
1
34k
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) | あーありがち