Upgrade to Pro — share decks privately, control downloads, hide ads and more …

そのオブジェクト、何を保証してくれますか? - GuideRailのススメ -

Avatar for wtnabe wtnabe
August 16, 2025

そのオブジェクト、何を保証してくれますか? - GuideRailのススメ -

"そのオブジェクト、何を保証してくれますか?" LTの資料です。5分に収まってないです。

Kanazawa.rb meetup #156 at ビブリオバウムでの発表です。

Avatar for wtnabe

wtnabe

August 16, 2025
Tweet

More Decks by wtnabe

Other Decks in Programming

Transcript

  1. プリミティブ(?) で確認するのが簡単 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 になる
  2. A1. Hashie::Dash を使う class MyHash1 < Hashie::Dash property :name end

    class MyHash2 < Hashie::Dash property :key end MyHash1 == MyHash1 # => true MyHash1 == Myhash2 # => false
  3. こんなモデル 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 今回は承認フローについては無視
  4. 基本はこんな感じ 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
  5. でもこれ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? もモノによる
  6. 合わせておかないと例外が上がってしまう BlogEntryPublishable = Data.define(:id, :title, :body, :tags) BlogEntryPublishable.new(name: "foo") #

    `initialize': unknown keyword: :name (ArgumentError) ※ 例外の扱いはミスると致命傷なのでやりたくない
  7. 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 辺りから
  8. Creator Class を定義して使う class BlogEntryPublishableCreator extend GuideRail schema BlogEntryPublishableSchema class_name

    :BlogEntryPublishable end BlogEntryPublishableCreator.from(blog_entry) # => #<data BlogEntryPublishable> # or # => Dry::Schema::Result
  9. いろんなデータを受け付けてほしい # @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
  10. 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">
  11. 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
  12. 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 })
  13. 参考 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) | あーありがち