Slide 1

Slide 1 text

"ܕ"ͷ͋ΔRailsΞϓϦέʔγϣϯ։ൃ Rubyηϛφʔ ౦ژ 2024/09/20 1

Slide 2

Slide 2 text

ࣗݾ঺հ • ໊લ: ਖ਼ಙ ޼(aka: ਆ଎) • GitHub: @sinsoku (ը૾ӈ্) • X: @sinsoku_listy (ը૾ӈԼ) • Railsྺ: 10೥͘Β͍ 2

Slide 3

Slide 3 text

Omotesando.rb ΦϑϥΠϯ։࠵Ͱɺຖ݄ୈҰ໦༵೔ɻ 3

Slide 4

Slide 4 text

Asakusa-bashi.rbs ΦϯϥΠϯ։࠵Ͱɺෆఆظʢ݄ʹ1ճ͘Β͍ʣ 4

Slide 5

Slide 5 text

ΞδΣϯμ • ! RBSʹ͍ͭͯ • ⭐ "ܕ" ͷϝϦοτ • # RBSಋೖͷखॱ • $ rbs-traceͷ঺հ 5

Slide 6

Slide 6 text

! RBSʹ͍ͭͯ 6

Slide 7

Slide 7 text

RBS • Ruby 3.0Ͱຊମʹಉࠝ͞Εͨ • RubyͷܕఆٛΛॻ͘จ๏·ͨ͸gem • ಈతͳܕఆٛʢϝλϓϩʣ͸Ͱ͖ͳ͍ • RBSࣗମ͸ ܕݕࠪͷػೳΛ࣋ͨͳ͍ 7

Slide 8

Slide 8 text

RBS Ruby RBS 8

Slide 9

Slide 9 text

⭐ "ܕ" ͷϝϦοτ 9

Slide 10

Slide 10 text

"ܕ" ͷϝϦοτ • υΩϡϝϯτ • ίʔυδϟϯϓ • ೖྗิ׬ • ܕݕࠪ 10

Slide 11

Slide 11 text

υΩϡϝϯτʢVSCodeʣ 11

Slide 12

Slide 12 text

υΩϡϝϯτʢRubyMineʣ 12

Slide 13

Slide 13 text

ίʔυδϟϯϓʢVSCodeʣ 13

Slide 14

Slide 14 text

ೖྗิ׬ʢVSCodeʣ 14

Slide 15

Slide 15 text

ܕݕࠪʢSteepʣ 15

Slide 16

Slide 16 text

⚠ ܕݕࠪͷਫ਼౓͸RBSʹґଘ͢Δ Ruby͸࣮ߦՄೳͰ΋RBSͱҰக͍ͯ͠ͳ͚Ε͹ΤϥʔʹͳΓ· ͢ɻ 16

Slide 17

Slide 17 text

! RBSಋೖͷखॱ 17

Slide 18

Slide 18 text

RBSΛಋೖ͢Δखॱʢجຊʣ ͜Ε͚ͩ 1. Gemfile ʹ steep Λ௥Ճ͢Δ 2. sig/".rbs Λ࣮૷͢Δ 18

Slide 19

Slide 19 text

RBSΛಋೖ͢Δखॱʢجຊʣ ͜Ε͚ͩ 1. Gemfile ʹ steep Λ௥Ճ͢Δ 2. sig/".rbs Λ࣮૷͢Δ • ! શͯͷΫϥεɺϞδϡʔϧɺϝιουͷܕఆ͕ٛඞཁ • " ෆे෼ͩͱܕݕࠪͷਫ਼౓͸ѱ͍ 19

Slide 20

Slide 20 text

طଘͷRailsΞϓϦͰ શͯͷܕఆٛΛ༻ҙ͢Δͷ͸೉͍͠ 20

Slide 21

Slide 21 text

RBSΛஈ֊తʹಋೖ͢Δखॱʢਪ঑ʣ • ⛔ ॳظ͸ܕݕࠪΛແޮԽ • υΩϡϝϯτɺίʔυδϟϯϓɺೖྗิ׬Λ໨తʹ͢Δ • ♻ Ͱ͖Δ͚ͩRBSΛੜ੒͢Δ • # ਖ਼͍͠ܕఆٛΛ૿΍͢ • $ Ұ෦ͷϑΝΠϧͷΈܕݕࠪΛ༗ޮԽ 21

Slide 22

Slide 22 text

۩ମతͳಋೖखॱ 1. Gemͷ௥Ճͱઃఆ • steep, rbs_rails, rbs-inline 2. RBSͷΠϯετʔϧͱੜ੒ 3. RBSͷݕূ 4. ΤσΟλͷઃఆ 22

Slide 23

Slide 23 text

sinsoku/redmine1 1 https://github.com/sinsoku/redmine/tree/setup-rbs 23

Slide 24

Slide 24 text

1. Steepʢsoutaro/steepʣ Gemfileʹ steep Λ௥Ճͯ͠ bundle install Λ࣮ߦ͢Δɻ # Gemfile group :development do gem "steep", require: false end ͦͯ͠ steep init Λ࣮ߦ͢Δɻ $ bundle exec steep init Writing Steepfile..# 24

Slide 25

Slide 25 text

1. Steepʢsoutaro/steepʣ Steepfile Λฤू͠ɺܕݕࠪΤϥʔΛແࢹ͠·͢ɻ # Steepfile D = Steep:#Diagnostic target :app do signature "sig" check "app", "lib" configure_code_diagnostics(D:#Ruby.silent) end 25

Slide 26

Slide 26 text

2. RBS Railsʢpocke/rbs_railsʣ • ActiveRecord͕ఆٛ͢ΔϝιουͷRBSΛੜ੒Ͱ͖Δ • σʔλϕʔεͷΧϥϜ໊ͷϝιου • ؔ࿈ʢbelongs_to, has_one, has_manyʣͷϝιου • ActiveRecord::Enum2ͷϝιου • ύεϔϧύʔʢxxx_pathʣͷRBSΛੜ੒Ͱ͖Δ 2 Enumͷ৽͍͠จ๏ʹ͸ະରԠͳͷͰ஫ҙɻࢀߟ: https://github.com/pocke/rbs_rails/pull/268 26

Slide 27

Slide 27 text

2. RBS Railsʢpocke/rbs_railsʣ Gemfileʹ௥Ճͯ͠ bundle install Λ࣮ߦ͢Δɻ # Gemfile group :development do gem "rbs_rails", require: false end ͦͯ͠ bin/rails g rbs_rails:install Λ࣮ߦ͢Δɻ $ bin/rails g rbs_rails:install create lib/tasks/rbs.rake 27

Slide 28

Slide 28 text

3. RBS::Inlineʢsoutaro/rbs-inlineʣ • ίϝϯτͰRBSΛهड़Ͱ͖ Δ • ίϝϯτ͕ແ͍৔߹ͷҾ਺ ͱ໭Γ஋͸ untyped ʹͳΔ class Person attr_reader :name #" String attr_reader :addresses #" Array[String] # @rbs name: String # @rbs addresses: Array[String] # @rbs return: void def initialize(name:, addresses:) @name = name @addresses = addresses end def to_s #" String # ུ end # @rbs &block: (String) -$ void def each_address(&block) #:& void addresses.each(&block) end end 28

Slide 29

Slide 29 text

rbs-inline Ͱੜ੒ͨ͠RBS # Generated from app/models/wiki.rb with RBS:"Inline class Wiki < ApplicationRecord include Redmine:"SafeAttributes def visible?: (?untyped user) -$ untyped # Returns the wiki page that acts as the sidebar content # or nil if no such page exists def sidebar: () -$ untyped # find the page with the given title # if page doesn't exist, return a new page def find_or_new_page: (untyped title) -$ untyped 29

Slide 30

Slide 30 text

3. RBS::Inlineʢsoutaro/rbs-inlineʣ Gemfileʹ௥Ճͯ͠ bundle install Λ࣮ߦ͢Δɻ # Gemfile group :development do gem "rbs-inline", require: false end 30

Slide 31

Slide 31 text

! rbs-inlineͷه๏͸RuboCopͱিಥ 31

Slide 32

Slide 32 text

RuboCopͷϧʔϧΛௐ੔3 # rbs-inlinde ͰϝιουͷޙΖʹ `##` Λ࢖͏ͨΊ Style/CommentedKeyword: Enabled: false # rbs-inline Ͱ `##` Λ࢖͏ͨΊ Layout/LeadingCommentSpace: Enabled: false # rbs-inline Ͱ͸ attr_* Λ1ͭͣͭॻ͘ඞཁ͕͋ΔͨΊ Style/AccessorGrouping: Enabled: false 3 Style/CommentedKeyword ͷରԠPR͸طʹ͋Δ https://github.com/rubocop/rubocop/pull/13222 32

Slide 33

Slide 33 text

4. RBSͷΠϯετʔϧͱੜ੒ 1. gemͷRBS • ruby/gem_rbs_collection͔Βऔಘ͢Δ 2. RailsΞϓϦͷRBS • rbs_rails ͱ rbs-inline Ͱੜ੒͢Δ 33

Slide 34

Slide 34 text

gemͷRBSΛऔಘ͢Δ $ bundle exec rbs collection init created: rbs_collection.yaml $ bundle exec rbs collection install औಘͨ͠RBSΛGitͰ؅ཧ͢Δඞཁ͸͋Γ·ͤΜɻ # .gitignore /.gem_rbs_collection/ 34

Slide 35

Slide 35 text

RBSΛੜ੒͢Δ ίϚϯυΛ࣮ߦͯ͠ sig/rbs_rails ͱ sig/generated ʹ RBSΛੜ੒͠·͢ɻ $ bin/rails rbs_rails:all $ bundle exec rbs-inline -#output -#opt-out app lib 35

Slide 36

Slide 36 text

! rbs-inline ͷղੳࣦഊ 8 lib/plugins/gravatar/spec/gravatar_spec.rb ͰΤϥ ʔ͕ൃੜ͠·ͨ͠ɻ # rbs_inline: disabled # refs: https:/"github.com/soutaro/rbs-inline/issues/105 require 'rubygems' require 'erb' # to get "h" require 'active_support' # to get "returning" require File.dirname(__FILE__) + '/.'/lib/gravatar' include GravatarHelper, GravatarHelper:)PublicMethods, ERB:)Util 8 https://github.com/soutaro/rbs-inline/issues/105 36

Slide 37

Slide 37 text

! RakeλεΫ namespace :rbs do task setup: %i[collection rbs_rails:all inline] task update: %i[rbs_rails:all inline] task reset: %i[clean setup] task :collection do sh "rbs", "collection", "install", "-"frozen" end task :inline do sh "rbs-inline", "-"output", "-"opt-out", "app", "lib" end task :clean do sh "rm", "-rf", ".gem_rbs_collection/", "sig/rbs_rails/", "sig/generated/" end end 37

Slide 38

Slide 38 text

! .gitattributes ੜ੒ͨ͠RBSͷଐੑʹ linguist-generated ʹઃఆ͢Δɻ # .gitattributes sig/generated/**/*.rbs linguist-generated sig/rbs_rails/**/*.rbs linguist-generated GitHubͷPRͰมߋͨ͠ϑΝΠϧΛදࣔ͢Δͱ͖ɺطఆͰ diff ͕ද ࣔ͞Εͳ͘ͳΓ·͢ɻ 38

Slide 39

Slide 39 text

! .gitattributes 39

Slide 40

Slide 40 text

RBSͷݕূ औಘɾੜ੒ͨ͠RBSΛݕূ͠·͢ɻ $ bundle exec steep validate 1ճͰ੒ޭ͢Δ͜ͱ͸كͰɺ͓ͦΒ͘ʮRBSͷఆ͕ٛ଍Γͳ͍ʯͳ ͲͷΤϥʔ͕ى͖·͢ɻ ! 40

Slide 41

Slide 41 text

Cannot find typeʢඪ४ϥΠϒϥϦʣ rbs_collection.yaml ʹ௥Ճɺ·ͨ͸ແࢹ͠·͢ɻ gems: # - name: csv # - name: yaml # `steep:validate` Ͱ `RBS:"UnsatisfiableTypeApplication` ͕ى͖ΔͷͰແࢹ͢Δ - name: ffi ignore: true 41

Slide 42

Slide 42 text

Cannot find typeʢͦͷଞgemʣ4 sig/generated/redmine/wiki_formatting/markdown/formatter.rbs:6:6: [error] Cannot find type `Redcarpet:$Render:$HTML` │ Diagnostic ID: RBS:$UnknownTypeName │ └ class HTML < Redcarpet:$Render:$HTML ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ sig/generated/redmine/wiki_formatting/markdown/formatter.rbs:6:6: [error] Cannot find type `Redcarpet:$Render:$HTML` │ Diagnostic ID: RBS:$UnknownTypeName │ └ class HTML < Redcarpet:$Render:$HTML ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 ruby/gem_rbs_collection ʹܕఆ͕ٛͳ͍gem 42

Slide 43

Slide 43 text

Cannot find typeʢͦͷଞgemʣ ඞཁͳΫϥε΍ϞδϡʔϧΛ sig/handwritten/".rbs ʹ࣮૷ ͠·͢ɻ # sig/handwritten/redcarpet.rbs module Redcarpet module Render class HTML end end end 43

Slide 44

Slide 44 text

Non-overloading method definition ਌Ϋϥεͱಉ໊͡લͷϝιουΛఆ͍ٛͯ͠Δ৔߹ʹൃੜ͠· ͢ɻ class Project < ApplicationRecord # @rbs (*__todo__) -$ Project | ..& def self.find(*args) # ..& end end 44

Slide 45

Slide 45 text

Type XXX is generic but used as a non generic type5 ఆ਺ʹArrayͱHashΛ࢖͍ͬͯͯɺ͔ͭ freeze Λ࢖͍ͬͯͳ͍ ৔߹ʹൃੜ͠·͢ɻ - DEFAULT_RULES = [:textile, :markdown] + DEFAULT_RULES = [:textile, :markdown] #" Array[__todo__] 5 https://github.com/soutaro/rbs-inline/pull/104 45

Slide 46

Slide 46 text

! RBSͷݕূ ͜͜·Ͱͷमਖ਼Ͱݕূ͕௨ΔΑ͏ʹͳΓ·͢ɻ $ bundle exec steep validate $ echo $? 0 46

Slide 47

Slide 47 text

4. ΤσΟλͷઃఆ • VSCode • soutaro.steep-vscode ͷVSCode֦ு͕ඞཁ • RubyMine • ඪ४ͰRBSʹରԠ 47

Slide 48

Slide 48 text

! RBSͷಋೖ͕׬ྃ1 1 https://github.com/sinsoku/redmine/tree/setup-rbs 48

Slide 49

Slide 49 text

"ܕ" ͷϝϦοτ • ✅ υΩϡϝϯτ • ✅ ίʔυδϟϯϓ • ✅ ೖྗิ׬ • ⛔ ܕݕࠪʢແޮԽʣ 49

Slide 50

Slide 50 text

ਖ਼͍͠ܕఆٛΛ૿΍͢ 1. ίϝϯτͰܕΛॻ͘6 2. bin/rails rbs:update Λ࣮ߦ͢Δ • sig/**/*.rbs ͕ߋ৽͢Δ 3. steep validate Λ࣮ߦ͢Δ 6 https://github.com/soutaro/rbs-inline/wiki/Syntax-guide 50

Slide 51

Slide 51 text

51

Slide 52

Slide 52 text

52

Slide 53

Slide 53 text

ஈ֊తʹܕݕࠪΛ༗ޮԽ # Steepfile D = Steep:#Diagnostic strict_paths = ["app/models"] target :app do signature "sig" check "app", "lib" ignore(*strict_paths) configure_code_diagnostics(D:#Ruby.silent) end target :strict do signature "sig" check(*strict_paths) end 53

Slide 54

Slide 54 text

ஈ֊తʹܕݕࠪΛ༗ޮԽ # Steepfile D = Steep:#Diagnostic strict_paths = ["app/models"] target :app do signature "sig" check "app", "lib" ignore(*strict_paths) configure_code_diagnostics(D:#Ruby.silent) end target :strict do signature "sig" check(*strict_paths) end 54

Slide 55

Slide 55 text

"ܕ" ͷϝϦοτ • ✅ υΩϡϝϯτ • ✅ ίʔυδϟϯϓ • ✅ ೖྗิ׬ • " ܕݕࠪʢҰ෦ʣ 55

Slide 56

Slide 56 text

ℹ ·ͱΊ • ஈ֊తͳಋೖͰ͋Ε͹ݱ࣮త • ؆୯ʹಋೖͰ͖Δͱ͸ݴ͑ͳ͍΋ͷͷ • ͥͻRBSΛ৮ͬͯΈͯཉ͍͠ • ίϛϡχςΟ΁ͷϑΟʔυόοΫ΋ཉ͍͠ 56

Slide 57

Slide 57 text

! rbs-traceͷ঺հ 57

Slide 58

Slide 58 text

! RBS::Traceʢsinsoku/rbs-traceʣ7 ςετͷ࣮ߦ࣌ʹܕ৘ใΛऩू͠ɺࣗಈతʹrbs-inlineͷίϝϯτ Λૠೖ͢ΔͨΊͷgemͰ͢ɻ • TracePointͰϝιουͷҾ਺ɾฦΓ஋ͷܕΛऩू • PrismͰ໭Γ஋ͷ୅ೖΛ൑ఆ • ໭Γ஋Λ࢖༻͍ͯ͠ͳ͍৔߹͸ void ʹͳΔͨΊ 7 https://github.com/sinsoku/rbs-trace 58

Slide 59

Slide 59 text

! RBS::Traceͷಋೖखॱ Gemfileʹ௥Ճͯ͠ bundle install Λ࣮ߦ͢Δɻ # Gemfile group :test do gem "rbs-trace" end 59

Slide 60

Slide 60 text

! RBS::Traceͷಋೖखॱ spec/support/rbs_trace.rb Λ࡞੒͢Δɻ return unless ENV["RBS_TRACE"] RSpec.configure do |config| tracing = RBS:#Trace:#MethodTracing.new config.before(:suite) { tracing.enable } config.after(:suite) do tracing.disable tracing.insert_rbs end end 60

Slide 61

Slide 61 text

! RBS::Traceͷಋೖखॱ ؀ڥม਺ RBS_TRACE Λࢦఆͯ͠RSpecΛ࣮ߦ͢Δͱɺࣗಈతʹ ܕ৘ใΛίϝϯτͰૠೖ͠·͢ɻ $ RBS_TRACE=1 bundle exec rspec 61

Slide 62

Slide 62 text

ͥͻࢼͯ͠Έ͍ͯͩ͘͞ 62

Slide 63

Slide 63 text

RBSʹؔͯ͠ࠔͬͨͱ͖ • ! ruby-jp Slackͷ #types νϟϯωϧ • https://ruby-jp.github.io/ • ✏ Asakusa-bashi.rbs • https://asakusa-bashi-rbs.connpass.com/ 63