Slide 1

Slide 1 text

RAILS WAY, RAILSの道か、 OR THE HIGHWAY さもなくば帰り道はあっちだ Vladimir Dementyev Evil Martians

Slide 2

Slide 2 text

2 github.com/palkan

Slide 3

Slide 3 text

3 evilmartians.jp

Slide 4

Slide 4 text

4 contributors.rubyonrails.org RUBY ON RAILSへの最初のコミット10周年

Slide 5

Slide 5 text

5 contributors.rubyonrails.org RUBY ON RAILSに取り組み始めて10周年

Slide 6

Slide 6 text

MY RAILS WAY 今⽇は、RAILSの旅で学んだことを提供したいと思います。

Slide 7

Slide 7 text

MY RAILS WAY 予想外の展開が続いた素晴らしい旅でした

Slide 8

Slide 8 text

Agenda What's the Rails Way? Challenges & temptations How to not get lost 8 次の退屈な30分は英語です RAILS WAY とは何ですか? 挑戦と誘惑 迷⼦にならない⽅法

Slide 9

Slide 9 text

For many developers, the Rails Way is just the title of the book 多くの開発者にとって、 RAILS WAY は単なる本 のタイトルに過ぎない

Slide 10

Slide 10 text

So I thought back in 2014... 2014年当時の私もそう思っていた...

Slide 11

Slide 11 text

It took me years to realize the actual meaning goes far beyond code コードを超えた本当の意味に気づくまで に何年もかかった

Slide 12

Slide 12 text

Rails Way is a methodology of building Rails applications 12 RAILS WAYはRAILSアプリケーションを構築する⽅法論です

Slide 13

Slide 13 text

Rails Way is a philosophy of building Rails applications 13 RAILS WAYはRAILSアプリケーションを構築する哲学です

Slide 14

Slide 14 text

Rails Way Optimize for productivity & happiness Freedom from decision making (Omakase) Design for scalability 14 ⽣産性と幸福のための最適化 意思決定からの解放(おまかせ) スケーラビリティを考慮した設計 スケーラビリティとは「必要に応じて、必要なリソースで、必要な速さで実⾏すること」

Slide 15

Slide 15 text

15 Ruby MVC Convention over configuration Complexity compression Rails Way rails new 設定より規約 複雑さの圧縮

Slide 16

Slide 16 text

16 rails new 0 ∞ IPO 1 RAILS は「HELLO WORLD」からIPOまでの道のりをサポートすると主張している

Slide 17

Slide 17 text

17 rails new 0 ∞ IPO 1 Rails 特に RAILS 8 は、「HELLO WORLD」から最初の動作バージョンまで にできるだけ早く着くことに集中している

Slide 18

Slide 18 text

Rails 8 Authentication Solid infrastructure solid_queue/solid_cache/solid_cable Deployment Kamal 18 認証機能 SOLID なインフラ基盤 デプロイメント

Slide 19

Slide 19 text

Rails 8 Way Optimize for getting from zero to one 19 「ゼロ」から「イチ」への最適化

Slide 20

Slide 20 text

20 Rails Way rails new 0 IPO 1 ∞ 2 3 4 5 ··· ? しかし、「イチ」とIPOの間には無限のステップがある— そこでもRAILS WAYは私たちをカバーしてくれるのだろうか?

Slide 21

Slide 21 text

CHALLENGES OF GOING BEYOND ONE 「イチ」を超えることの課題

Slide 22

Slide 22 text

Rails as a web app 22 RAILS をウェブアプリケーションとして⾒てみましょう

Slide 23

Slide 23 text

Rails as a web app 23 Request ウェブアプリケーションの主な⽬的とは何か?ウェブリクエストに応えることです

Slide 24

Slide 24 text

Rails as a web app 24 Request Response それは組⽴ラインのように想像できます: ユーザー⼊⼒が各⼯程を通過させて、最終製品であるレスポンスを形成する

Slide 25

Slide 25 text

Rails as a web app 25 Model Controller View Request Response そしてデフォルトでは、3つの⼯程がある: 処理はコントローラーで始まり、モデルと通信し、ビューを使ってレスポンスを準備する

Slide 26

Slide 26 text

26 Model View Controller

Slide 27

Slide 27 text

27 アプリケーションが成⻑するにつれて、ロジックを⼊れる3つだけの箱は、 すぐに⾜りなくなる

Slide 28

Slide 28 text

28 reddit.com/r/rails RAILSアプリケーションは、成⻑するにつれてRAILS WAYから外れる傾向がある。 なぜのか? なぜ Ruby on Rails のコードベースは、それぞれがこんなにも異なるのか?

Slide 29

Slide 29 text

Rails gives you a solid frame and building blocks to assemble your application Rails はアプリケーションを組み⽴てるための堅牢なフレームと 構成要素を提供する

Slide 30

Slide 30 text

But Omakase is not complete, there are gaps to fill authorization, complex UI logic, workflows, AI agents しかし、「おまかせ」は完全ではなく、 埋めるべき隙間がある: 認可、複雑なUI、ワークフロー、AIエージェント

Slide 31

Slide 31 text

Filling the gaps, we often distort the frame our productivity & happiness decrease 隙間を埋めることで、私たちはしばしばフレームを歪めてしまいます ⽣産性と幸福度は低下する our codebase turns into a monster Ruby on Railsは、「怪獣 on Rails」になってしまう

Slide 32

Slide 32 text

32 reddit.com/r/rails これは恐らく私の昔のコードのことだ: 考え抜くの代わりに、隙間を埋めるために⼈気のあるGEMなら何でも持ち込もうとしていた Rails のベテラン達よ、仕事での最⼤のチャレンジは何ですか? 「東欧のコードを普通の⼈が理解できるものに修正すること」

Slide 33

Slide 33 text

33 怪獣は、開発者がRAILS WAYを完全に理解していないか、 もしくは誤解するかことで⽣まれる... reddit.com/r/rails なぜ Ruby on Rails のコードベースは、それぞれがこんなにも異なるのでしょうか? Rails のエコシステムはある程度までは構造を提供しますが、 その先は各チームが独⾃の決定で進まなければなりません

Slide 34

Slide 34 text

Rails framework gives you structure 34 RAILS フレームワークは構造を与えてくれる

Slide 35

Slide 35 text

Rails Way gives you guidance 35 RAILS WAY はあなたの導きの星だ

Slide 36

Slide 36 text

HOW NOT TO GET LOST 迷⼦にならない⽅法

Slide 37

Slide 37 text

The guide on filling gaps the Rails Way RAILS WAY に従って隙間を埋める ためのガイド

Slide 38

Slide 38 text

Master & extend Rails 39 RAILS を習得し、拡張する

Slide 39

Slide 39 text

Master Rails Learn Rails design patterns adapter/plugin/middleware 40 RAILS を習得する RAILS の設計パターンを学ぶ

Slide 40

Slide 40 text

41 gem "rails" # Database gem "sqlite3" # Real-time backend gem "solid_cable" # Background jobs gem "solid_queue" # Image transformations gem "image_processing" From 1...

Slide 41

Slide 41 text

42 gem "rails" # Database gem "pg" # Real-time backend gem "anycable-rails" # Background jobs gem "sidekiq" # Image transformations gem "imgproxy-rails" ...to N

Slide 42

Slide 42 text

Master Rails Learn Rails design patterns adapter/plugin/middleware Embrace the principle of conventions Make Rails building blocks a part of your toolbox active_model/active_support/zeitwerk 43 RAILS を習得する RAILS の設計パターンを学ぶ 規約の原則を受け⼊れる RAILSの構成要素をあなたのツールボックスに⼊れる

Slide 43

Slide 43 text

Extend Rails instead of melting it with something else 44 RAILS を他のものと混ぜ合わせるのでは なく、拡張しよう

Slide 44

Slide 44 text

45 Request Response より効率的にするために、私たちの組⽴ラインをどのように修正すべきか? Extended Rails Way? Model Controller View

Slide 45

Slide 45 text

46 *** Extended Rails Way 新しい処理ユニットを導⼊のため、担当分野を分離して、 各ユニットをプロセスの⼀部に集中させよう Model Controller View Request Response

Slide 46

Slide 46 text

47 SEPARATE CONCERNS 担当分野の分離には新しい抽象化が必要だ

Slide 47

Slide 47 text

How to add a new abstraction and stay on the Rails Way 48 RAILSアプリケーションに新しい抽象化レイヤーを導⼊する 際に、私が従う4つのルールだ

Slide 48

Slide 48 text

I. Provide Rails-like DX and compatibility with core components 49 RAILS らしい開発者体験とコアコンポーネントとの互換性 を提供する

Slide 49

Slide 49 text

Think as a framework author, not a custom application developer 50 カスタムプリケーション開発者の考え⽅ではなく、フレーム ワーク作者の考え⽅を持つ

Slide 50

Slide 50 text

II. Draw clear boundaries between abstraction layers 51 抽象化レイヤーの間に明確な境界を引く

Slide 51

Slide 51 text

Layered Architecture 52 抽象化レイヤーを分離するために、レイヤードアーキテクチャの原則を参照できる Presentation Application Domain Infrastructure

Slide 52

Slide 52 text

Layered Architecture 53 各抽象化は、特定のアーキテクチャレイヤーに属する必要がある Presentation Application Domain Infrastructure

Slide 53

Slide 53 text

Layered Architecture 54 データは原則として上から下に流れて、 そこで各抽象化は⾃分より下位のものだけを知ることができる Presentation Application Domain Infrastructure

Slide 54

Slide 54 text

Presentation Application Domain Infrastructure Layered Architecture 55 それぞれの層からどこまで下位層にアクセスできるかを制限することも可能だ。 これで疎結合が⾃然に実現できる。

Slide 55

Slide 55 text

56 Controllers Presentation Channels Views Application Jobs Mailers Domain Infrastructure Models Adapters (DB, mail) API clients それぞれのRAILSの抽象化を、特定のアーキテクチャ層に対応付けれる。 私たちの独⾃の抽象化はも同じようにしよう。

Slide 56

Slide 56 text

III. Prefer extraction over intervention: find existing abstractions in your code 57 介⼊より抽出: コード内で既存の抽象化を⾒つけよう

Slide 57

Slide 57 text

IV. Separate concerns and complexity 58 関⼼事と複雑さのレベルを分離する

Slide 58

Slide 58 text

Separate vanilla Rails from advanced techniques Keep the learning curve smooth but leave the opportunity to gain new knowledge 古典的なRailsと⾼度なテクニックを分離する 学習曲線をなめらかに保ちつつ 新しい知識を得る機会を 残しておく

Slide 59

Slide 59 text

60 class Post < ApplicationRecord has_many :comments, dependent: :destroy belongs_to :user validates :title, presence: true scope :tagged, ->(tag) { tags_table = Arel::Nodes::NamedFunction.new( "json_each", [arel_table[:tags]] ).then do name = Arel.sql(_1.to_sql) Arel::Table.new(name, as: :json_tags) end tags_subquery = arel_table. project(1). where(tags_table[:value].eq(tag)) where(tags_subquery.exists) } end Normal Rails 通 常のRails Unseparated complexity 分離されていない複雑さ Paranormal Rails " 超常的なRails

Slide 60

Slide 60 text

61 class Post < ApplicationRecord has_many :comments, dependent: :destroy belongs_to :user validates :title, presence: true scope :tagged, TaggedQuery end Separated complexity 分離された複雑さ

Slide 61

Slide 61 text

62 class Post::TaggedQuery < ApplicationQuery def resolve(tag) tags_subquery = tags_table.project(1). where(tags_table[:value].eq(tag)) relation.where(tags_subquery.exists) end private def tags_table @tags_arel ||= Arel::Nodes::NamedFunction.new( "json_each", [arel_table[:tags]] ).then do name = Arel.sql(_1.to_sql) Arel::Table.new(name, as: :json_tags) end end def arel_table = self.class.query_model.arel_table end Separated complexity 分離された複雑さ

Slide 62

Slide 62 text

63 PRACTICE TIME これらの原則を実践して⾒よう

Slide 63

Slide 63 text

<%= form_for @cable do |f| %> <%= f.text_field :name, required: true %> <%= f.text_field :region, required: true %> <%= f.radio_button :framework, "rails" %> <%= f.radio_button :framework, "js" %> <%= f.radio_button :framework, "hotwire" %> <%= f.radio_button :framework, "default" %> <%= f.text_field :rpc_host %> <%= f.text_field :rpc_secret %> <%= f.text_field :secret %> <%= f.text_field :turbo_secret %> <%= f.text_field :jwt_secret %> <%= f.submit "Create" %> <% end %> What we had 私たちが持っていたもの

Slide 64

Slide 64 text

What we wanted 私たちが望んでいたもの

Slide 65

Slide 65 text

66 class Cable < ApplicationRecord validates :name, presence: true validates :region, presence: true, if: :region_step_completed? attr_accessor :current_step def current_step @current_step ||= "name" end def steps = %w[name framework rpc secrets region] def next_step = steps[steps.index(current_step) + 1] def previous_step = steps[steps.index(current_step) - 1] def first_step? = current_step == steps.first def last_step? = current_step == steps.last def region_step_completed? = steps.index(current_step) > steps.index("region") end What we got 私たちが得たもの This code belongs to Presentation layer このコードはプレゼンテー ション層に属している 7 new methods just for a single form! たった1つのフォームのため に7つの新しいメソッド!

Slide 66

Slide 66 text

66 class Cable < ApplicationRecord validates :name, presence: true validates :region, presence: true, if: :region_step_completed? attr_accessor :current_step def current_step @current_step ||= "name" end def steps = %w[name framework rpc secrets region] def next_step = steps[steps.index(current_step) + 1] def previous_step = steps[steps.index(current_step) - 1] def first_step? = current_step == steps.first def last_step? = current_step == steps.last def region_step_completed? = steps.index(current_step) > steps.index("region") end What we got 私たちが得たもの Written by senior AI developer # AI先⽣で作られたコード This code belongs to Presentation layer このコードはプレゼンテー ション層に属している 7 new methods just for a single form! たった1つのフォームのため に7つの新しいメソッド!

Slide 67

Slide 67 text

class CablesController < ApplicationController def create @cable = Cable.new(cable_params) if @cable.valid? if @cable.last_step? @cable.save session.delete(:cable_params) redirect_to @cable, notice: "Success!" else session[:cable_params] = cable_params.to_h redirect_to new_cable_path end else render :new, status: :unprocessable_entity end end def back @cable = Cable.new(session[:cable_params]) @cable.current_step = @cable.previous_step render :new end end 67 What we got 私たちが得たもの Written by senior AI developer # Ad hoc persistence (Infrastructure layer?) カスタムな永続化の実装 (インフラ層) Not a Rails-like action Railsらしくないアクション AI先⽣で作られたコード

Slide 68

Slide 68 text

Rails doesn't provide multi-step functionality out-of-the-box The code we wrote to fill the gap doesn't look like Rails Crossing the architecture layer boundaries leads to high coupling and poor maintainability 68 RAILSは複数ステップ機能を標準では提供してくれない その隙間を埋めるために書いたコードは、RAILSらしくない アーキテクチャ層の越境は、密結合と低いメンテナンス性に⾄る

Slide 69

Slide 69 text

69 *** Extended Rails Way 担当分野を分離するために新しい抽象化を追加しよう! Model Controller View Request Response

Slide 70

Slide 70 text

69 Extended Rails Way 担当分野を分離するために新しい抽象化を追加しよう! Model Controller View Request Response Form

Slide 71

Slide 71 text

Form object concerns Context-specific validations User input transformation User feedback Custom UI-driven logic (wizards) 70 コンテキストによりのバリデーション ユーザー⼊⼒の変換 ユーザーへフィードバックを提供すること UIに基づくカスタムロジック フォームオブジェクトの担当分野

Slide 72

Slide 72 text

71 class Cable class CreateForm < ApplicationForm self.model_name = "Cable" attribute :name attribute :region, default: -> { "sea" } attribute :framework, default: -> { "rails" } attributes :secret, :rpc_host, :rpc_secret, :turbo_secret, :jwt_secret attr_reader :cable def initialize(...) super @cable = Cable.new( name:, region:, metadata: {framework:}, configuration: { secret:, rpc_host:, rpc_secret:, turbo_secret:, jwt_secret: } ) end def submit! = # ... end end What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 73

Slide 73 text

71 class Cable class CreateForm < ApplicationForm self.model_name = "Cable" attribute :name attribute :region, default: -> { "sea" } attribute :framework, default: -> { "rails" } attributes :secret, :rpc_host, :rpc_secret, :turbo_secret, :jwt_secret attr_reader :cable def initialize(...) super @cable = Cable.new( name:, region:, metadata: {framework:}, configuration: { secret:, rpc_host:, rpc_secret:, turbo_secret:, jwt_secret: } ) end def submit! = # ... end end UI / schema decoupling What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 74

Slide 74 text

72 class Cable class CreateForm < ApplicationForm self.model_name = "Cable" attribute :name attribute :region, default: -> { "sea" } attribute :framework, default: -> { "rails" } attributes :secret, :rpc_host, :rpc_secret, :turbo_secret, :jwt_secret validates :cable_is_valid validates :rpc_host, format: %r{\Ahttps?://}, allow_blank: true attr_reader :cable def initialize(...) = # ... def submit! = # ... private def cable_is_valid return if cable.valid? merge_errors!(cable) end end end Validations What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 75

Slide 75 text

73 class Cable class CreateForm < ApplicationForm self.model_name = "Cable" attribute :name attribute :region, default: -> { "sea" } attribute :framework, default: -> { "rails" } attributes :secret, :rpc_host, :rpc_secret, :turbo_secret, :jwt_secret validates :cable_is_valid validates :rpc_host, format: %r{\Ahttps?://}, allow_blank: true after_commit :enqueue_provisioning attr_reader :cable def initialize(...) = # ... def submit! = # ... private def enqueue_provisioning = cable.provision_later end end Trigger business operations What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 76

Slide 76 text

74 class Cable class CreateForm < ApplicationForm class Wizard < ApplicationWorkflow # ... end attribute :wizard_state, default: -> { "name" } attribute :wizard_action def submit! if wizard_action == "back" wizard.back! else wizard.submit! end return false unless wizard.complete? cable.save! end def wizard = @wizard ||= Wizard.new(self) end end Multi-step form logic What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 77

Slide 77 text

75 class Cable class CreateForm < ApplicationForm class Wizard < ApplicationWorkflow workflow do state :name do event :submit, transitions_to: :framework end state :framework do event :submit, transitions_to: :rpc, if: :needs_rpc? event :submit, transitions_to: :secrets event :back, transitions_to: :name end state :rpc do event :submit, transitions_to: :secrets event :back, transitions_to: :framework end state :secrets do event :submit, transitions_to: :region event :back, transitions_to: :rpc, if: :needs_rpc? event :back, transitions_to: :framework end state :complete end end end end Another abstraction — workflow! What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 78

Slide 78 text

76 class Cable class CreateForm < ApplicationForm class Wizard < ApplicationWorkflow workflow do state :name do event :submit, transitions_to: :framework end state :framework do event :submit, transitions_to: :rpc, if: :needs_rpc? event :submit, transitions_to: :secrets event :back, transitions_to: :name end state :rpc do event :submit, transitions_to: :secrets event :back, transitions_to: :framework end state :secrets do event :submit, transitions_to: :region event :back, transitions_to: :rpc, if: :needs_rpc? event :back, transitions_to: :framework end state :complete end end end end What we should have done 私たちがすべきだったこと Written by senior Martian developer ベテランの⽕星⼈開発者が書いたもの

Slide 79

Slide 79 text

I*. Every abstraction needs a base class 77 すべての抽象化にはベースクラスが必要です

Slide 80

Slide 80 text

78 class ApplicationForm include ActiveModel::API include ActiveModel::Attributes define_callbacks :save, only: :after define_callbacks :commit, only: :after def save return false unless valid? with_transaction do AfterCommitEverywhere.after_commit { run_callbacks(:commit) } run_callbacks(:save) { submit! } end end def model_name ActiveModel::Name.new(nil, nil, self.class.name.sub(/Form$/, "")) end private def with_transaction(&) = ApplicationRecord.transaction(&) def submit! raise NotImplementedError end end This is how we leverage Rails building blocks これはRailsの構成要素 を活⽤する⽅法だ

Slide 81

Slide 81 text

78 class ApplicationForm include ActiveModel::API include ActiveModel::Attributes define_callbacks :save, only: :after define_callbacks :commit, only: :after def save return false unless valid? with_transaction do AfterCommitEverywhere.after_commit { run_callbacks(:commit) } run_callbacks(:save) { submit! } end end def model_name ActiveModel::Name.new(nil, nil, self.class.name.sub(/Form$/, "")) end private def with_transaction(&) = ApplicationRecord.transaction(&) def submit! raise NotImplementedError end end Validations / Types Callbacks Transactions awareness Interface Action View compat This is how we leverage Rails building blocks これはRailsの構成要素 を活⽤する⽅法だ

Slide 82

Slide 82 text

79 <%= form_for @cable do |f| %> <%= f.text_field :name, required: true %> <%= f.text_field :region, required: true %> # ... <%= f.submit "Create" %> <% end %> Form objects can be used in place of model objects フォームオブジェクトを、モデルオブジェクト の代わりに使⽤できる

Slide 83

Slide 83 text

80 <%= form_for form do |f| %> <%= f.text_field :name, required: true %> <%= f.text_field :region, required: true %> # ... <%= f.submit "Create" %> <% end %> self.model_name = "Cable" Form objects can be used in place of model objects

Slide 84

Slide 84 text

81 class CablesController < ApplicationController def new @form = Cable::CreateForm.new end def create @form = Cable::CreateForm.from(params.require(:cable)) if @form.save redirect_to cable_path(@form.cable), notice: "Success!" else status = @form.valid? ? :created : :unprocessable_entity render :new, status: end end end Controller code resembles the scaffolded code コントローラーのコードは、 「rails g scaffold」 で⾃動⽣成されたコードに似ている

Slide 85

Slide 85 text

82 <%= form_for form do |f| %> <%= f.hidden_field :wizard_state %> <% if form.wizard.name? %> <%= f.text_field :name %> <% else %> <%= f.hidden_field :name %> <% end %> No storage required for intermediate state— just HTML! 中間の状態の保存は不要で、HTMLだけは⼗分だ

Slide 86

Slide 86 text

83 <%= form_for form do |f| %> <%= f.hidden_field :wizard_state %> # ... <% if form.wizard.framework? %> <%= f.radio_button :framework, "rails" %> <%= f.radio_button :framework, "js" %> <%= f.radio_button :framework, "hotwire" %> <%= f.radio_button :framework, "default" %> <% else %> <%= f.hidden_field :framework %> <% end %> No storage required for intermediate state— just HTML! 中間の状態の保存は不要で、HTMLだけは⼗分だ

Slide 87

Slide 87 text

84 <% if form.wizard.prerequisites? %> # ... <% end %> <%= form_for form do |f| %> <%= f.hidden_field :wizard_state %> # ... We can easily add UI only steps, too UIのみのステップも 簡単に追加できる

Slide 88

Slide 88 text

85 <% if form.wizard.rpc? %> <%= f.text_field :rpc_host %> <% else %> <%= f.hidden_field :rpc_host %> <% end %> <%= form_for form do |f| %> <%= f.hidden_field :wizard_state %> # ...

Slide 89

Slide 89 text

87 <% if form.wizard.can_complete? %> <%= f.submit "Create" %> <% else %> <%= f.submit "Next", formaction: new_cable_path %> <% end %> <% if form.wizard.can_back? %> <%= f.submit "Back", formaction: new_cable_path, value: "Back", name: "cable[wizard_action]" %> <% end %> <% end %> <%= form_for form do |f| %> <%= f.hidden_field :wizard_state %> # ... Now new action required for stepping back— "new" is enough 戻るための新アクションを追加する必要がなくて、 "new"は⼗分だ

Slide 90

Slide 90 text

89 Controllers Presentation Channels Views Application Jobs Mailers Domain Infrastructure Models Adapters (DB, mail) API clients Forms これはフレームワークの⼀部となる抽象化を導⼊する⽅法の例でした

Slide 91

Slide 91 text

Growing the Rails Way is possible if you don't fight the framework フレームワークと戦わなければ Rails Wayで成⻑することは可能です

Slide 92

Slide 92 text

Rails mastery allows you to extend Rails Railsマスタリーは Railsを拡張できるようにします

Slide 93

Slide 93 text

Introduce new Rails-like abstractions having clear boundaries 1) Railsに似た新しい抽象化を導⼊する 2) 明確な境界を引く separating concerns & complexity 3) 担当分野と複雑性を分離する belonging to your application 4) アプリケーションに属する

Slide 94

Slide 94 text

Make the pieces of your application puzzle fit together! Thank you! アプリケーションのパズルのピー スをうまく組み合わせましょう! ありがとう Slides: evilmartians.com/events Twitter: @palkan_tula, @evilmartians Credits: Misha Dementyev $%, Rina Sergeeva &, SoundStripe.com '