はじめに
こんにちは!
delyサーバーサイドエンジニアの望月 (@0000_pg)です
クラシルのアプリを中心にサーバーサイドを担当しています
今年もdelyのアドベントカレンダーが始まりました 🎉
今年は開発部の人数も増えてきたので
カレンダーを1と2にわけて行うことになりました
去年は2日目だったので
今年はトップバッターをやることにしました💪
本日公開された
dely #2 Advent Calendar 2020
のほうの記事は
デザイナーのsakoさんの
ノンデザイナーでも大丈夫!見やすいプレゼン資料をつくる6つの手順
です!
note.com
これをみれば、誰でもイケてる資料がつくれるようになっています😎✨
とても勉強になりました!
さて、dely #1 Advent Calendar 2020
1日目の記事は
Ruby 3.0へ向けて、型周りをさわってみた
ことを書きたいと思います
Ruby 3.0
Ruby 3.0.0 Preview 1 に関しては現時点で触れるようになっています
Ruby 3.0 はいまから触るRBSや、並列処理のRactorなど
多数の新機能が追加される予定です
型周りの機能は、すでにgemとして配布されているので
必ずしも3.0を使う必要はありません
準備
レシピ決め
一番重要なことは、今日作るレシピを決めることです
クリスマスが近づいてくるので クリスマスっぽいレシピにしましょう🎄
いちごで作る サンタクロース🎅
\ めちゃかわ💖 /
\ かわいいの暴力です💖 /
これにしましょう!!🎅🎄
コード
レシピが決まったので、買い物に行きつつ
コードをつくっていきます
Gemfile
source 'https://rubygems.org' gem 'rbs' gem 'typeprof' gem 'steep'
(※あえてgemとして記載してあります)
recipe.rb
class Recipe attr_reader :title def initialize(title:, ingredients:, instructions:) @title = title @ingredients = ingredients @instructions = instructions end def ingredients Ingredient.summary(@ingredients) end def instructions Instruction.summary(@instructions) end end
ingredient.rb
class Ingredient attr_reader :name, :quantity, :unit def initialize(name:, quantity:, unit:) @name = name @quantity = quantity @unit = unit end class << self def summary(ingredients) ingredients_text = "材料\n" ingredients.each do |ingredient| ingredients_text << <<-INGREDIENTS_TEXT #{ingredient.name} #{ingredient.quantity}#{ingredient.unit} INGREDIENTS_TEXT end ingredients_text end end end
instruction.rb
class Instruction attr_reader :text def initialize(text:) @text = text end class << self def summary(instructions) instructions_text = "手順\n" instructions.each do |instruction| instructions_text << <<-INSTRUCTIONS_TEXT #{instruction.text} INSTRUCTIONS_TEXT end instructions_text end end end
app.rb
require_relative 'recipe' require_relative 'ingredient' require_relative 'instruction' title = 'いちごで作る サンタクロース' ingredients = [ Ingredient.new(name: 'いちご', quantity: 2, unit: '個'), Ingredient.new(name: 'ホイップクリーム', quantity: 10, unit: 'g'), Ingredient.new(name: 'チョコレートペン (黒)', quantity: 1, unit: '本') ] instructions = [ Instruction.new(text: 'チョコレートペンは湯煎にかけて溶かしておきます。 ホイップクリームは絞り袋に入れておきます。'), Instruction.new(text: 'いちごはヘタを切り落とします。'), Instruction.new(text: 'ヘタの部分から2/3のところを切ります。'), Instruction.new(text: 'ヘタの部分を下にして切り口にホイップクリームを絞り、挟みます。上にホイップクリームを直径5mm程絞り、帽子をつくります。'), Instruction.new(text: 'チョコレートペンで顔とボタンを描いて完成です。') ] recipe = Recipe.new(title: title, ingredients: ingredients, instructions: instructions) puts recipe.title puts recipe.ingredients puts recipe.instructions
$ bundle exec ruby app.rb いちごで作る サンタクロース 材料 いちご 2個 ホイップクリーム 10g チョコレートペン (黒) 1本 手順 チョコレートペンは湯煎にかけて溶かしておきます。 ホイップクリームは絞り袋に入れておきます。 いちごはヘタを切り落とします。 ヘタの部分から2/3のところを切ります。 ヘタの部分を下にして切り口にホイップクリームを絞り、挟みます。上にホイップクリームを直径5mm程絞り、帽子をつくります。 チョコレートペンで顔とボタンを描いて完成です。
本題
我々はいちごでサンタさんをつくりながら
型と向き合っていかないといけません🎅🎄
rbs でも雛形はつくれるのですが
$ rbs prototype rb recipe.rb
今回は typeprof をつかっていきます
typeprof
雛形をつくります (色々とオプションはありますが割愛します)
$ typeprof lib/recipe.rb -o sig/recipe.rbs $ typeprof lib/ingredient.rb -o sig/ingredient.rbs $ typeprof lib/recipe.rb -o sig/recipe.rbs
sig/recipe.rbs
# Classes class Recipe @ingredients : untyped @instructions : untyped attr_reader title : untyped def initialize : (title: untyped, ingredients: untyped, instructions: untyped) -> untyped def ingredients : -> untyped def instructions : -> untyped end
sig/ingredient.rbs
# Classes class Ingredient attr_reader name : untyped attr_reader quantity : untyped attr_reader unit : untyped def initialize : (name: untyped, quantity: untyped, unit: untyped) -> untyped def self.summary : (untyped) -> String end
sig/instruction.rbs
# Classes class Instruction attr_reader text : untyped def initialize : (text: untyped) -> untyped def self.summary : (untyped) -> String end
余談ですが、rbi
と rbs
について
itoさんのブログに記載がありました
koic.hatenablog.com
steep
今回はsteep
をつかって型チェックをしていきます
準備
Steepfile
をつくります
$ steep init
# target :lib do # signature "sig" # # check "lib" # Directory name # check "Gemfile" # File name # check "app/models/**/*.rb" # Glob # # ignore "lib/templates/*.rb" # # # library "pathname", "set" # Standard libraries # # library "strong_json" # Gems # end # target :spec do # signature "sig", "sig-private" # # check "spec" # # # library "pathname", "set" # Standard libraries # # library "rspec" # end
今回はこうしました
target :lib do check "lib" signature "sig" end
型定義
先程のrbs
に型を定義していきます
sig/recipe.rbs
# Classes class Recipe @ingredients : Array[Ingredient] @instructions : Array[Instruction] attr_reader title : String def initialize: (title: String, ingredients: Array[Ingredient], instructions: Array[Instruction]) -> void def ingredients: -> String def instructions: -> String end
sig/ingredient.rbs
# Classes class Ingredient attr_reader name : String attr_reader quantity : Integer attr_reader unit : String def initialize: (name: String, quantity: Integer, unit: String) -> void def self.summary: (Array[Ingredient]) -> String end
sig/instruction.rbs
# Classes class Instruction attr_reader text : String def initialize: (text: String) -> void def self.summary: (Array[Instruction]) -> String end
sig/app.rbs
# Classes class Recipe attr_reader title : String def initialize : (title: String, ingredients: Array[Ingredient], instructions: Array[Instruction]) -> void def ingredients : -> String def instructions : -> String end class Ingredient attr_reader name : String attr_reader quantity : Integer attr_reader unit : String def initialize : (name: String, quantity: Integer, unit: String) -> void def self.summary : (Array[Ingredient]) -> String end class Instruction attr_reader text : String def initialize : (text: String) -> String def self.summary : (Array[Instruction]) -> String end
型チェック
$ bundle exec steep check
(色々とオプションはありますが割愛します)
失敗してみる
title
の型をInteger
にし、
recipe.instructions
の戻り値を Integer
にしてみます
sig/recipe.rbs
# Classes class Recipe @ingredients: Array[Ingredient] @instructions: Array[Instruction] + attr_reader title: Integer def initialize: (title: String, ingredients: Array[Ingredient], instructions: Array[Instruction]) -> void + def instructions: -> Integer end
lib/recipe.rb:5:4: IncompatibleAssignment: lhs_type=::Integer, rhs_type=::String (@title = title) lib/recipe.rb:14:2: MethodBodyTypeMismatch: method=instructions, expected=::Integer, actual=::String (def instructions)
最後に
明日の dely #1 Advent Calendar 2020
は
GENさんの 木も見て森も見るための Athena(Presto) 集計術
です!
お楽しみに!
delyではRailsエンジニアを募集しています!
サーバーサイドのカジュアル面談は自分が担当しています!
少しでも興味があれば、お気軽にお話ししましょう〜
また、定期的に開発組織についてイベントを行っています!
こちらもカジュアルに話を聞きにきてもらえればと思います〜