レールの伸ばし方

 レールの伸ばし方

Rails Developers Meetup 2017での発表内容です。

大きいRailsアプリケーションの可読性を保つためのコツについてまとめました。

6ac7c50770603b53964d44db373e8e48?s=128

Shinichi Maeshima

December 09, 2017
Tweet

Transcript

  1. Ϩʔϧͷ৳͹͠ํ !XJMMOFU

  2. 3BJMT%FWFMPQFST .FFUVQ

  3. None
  4. $MFBO5FTU$PEF

  5. None
  6. IUUQTXJMMOFUHJUCPPLTJPSTQFDTUZMFHVJEF

  7. ӳޠ൛΋͋Γ·͢ 5IBOLT!HB[BZBT

  8. *❤$MFBO

  9. Shinichi Maeshima ! " # @netwillnet @willnet http://blog.willnet.in Rails Consultant

  10. 8FBSFOPUIJSJOH

  11. ͔͜͜Βຊ୊

  12. 8PSLJOHXJUI3BJMT XIJMFGFFMJOHTUSFTT

  13. ։ൃॳظ

  14. ։ൃதظ

  15. ։ൃޙظ

  16. Ͳ͏ͨ͠ΒͭΒ͘ͳ͍͔

  17. ద੾ͳந৅Խ

  18. ద੾ͳந৅Խ Կͯ͠Δͷ͔ͻͱ໨Ͱ ෼͔ΔΑ͏ʹ͢Δ

  19. ద੾ͳந৅Խͷྫ CFGPSF class Registration def save ApplicationRecord.transaction do case @auth_hash[:provider]

    when 'twitter' then register_by_twitter! when 'facebook' then register_by_facebook! when 'github' then register_by_github! end Friendship.create!(user: invitation.sender, friend: user) Friendship.create!(user: user, friend: invitation.sender) invitation.joined_at = Time.zone.now invitation.receiver = user invitation.save! end true rescue ActiveRecord::RecordInvalid false end
  20. ద੾ͳந৅Խͷྫ BGUFS class Registration def save ApplicationRecord.transaction do register! make_friends!

    finish_invite! end true rescue ActiveRecord::RecordInvalid false end
  21. ద੾ͳந৅Խ

  22. ద੾Ͱͳ͍ந৅Խͷྫ class IssuesController < ApplicationController before_filter :find_issue, :only => [:show,

    :edit, :update] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_project, :only => [:new, :create, :update_form] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
  23. 3BJMT.7$

  24. 3BJMTͷ.7$ʹ͓͚Δ՝୊ w.7$ɺਖ਼͔ͭ͑ͯ͘͠·͔͢ʁ w.7$͚ͩͰ͸ϨΠϠ଍Γͳ͍ͷͰ͸ʁ

  25. $POUSPMMFSΛਖ਼͘͠࢖͏

  26. ѱ͍ྫ 'BU$POUSPMMFS IUUQTHJUIVCDPNTIBLBDPEFGBUDPEFSFGBDUPSJOHUFDIOJRVFT def create @micropost = Micropost.new(micropost_params.merge(user: current_user)) if

    current_user.minor? && (profane_words_used = profane_words_in(@micropost.content)) current_user.increment(:profanity_count, profane_words_used.size) current_user.save(validate: false) send_parent_notifcation_of_profanity(profane_words_used) flash.now[:error] = <<-MSG.html_safe <p>Profanity: '#{profane_words_used.join(", ")}' not allowed! You've tried to use profanity #{view_context.pluralize(current_user.profanity_count, "time")}! </p><p class="parent-notification">Your parents have been notified!</p> MSG render 'static_pages/home' else if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else render 'static_pages/home' end end end end
  27. 'BU$POUSPMMFS㲈ద੾ͳ ந৅Խ͕Ͱ͖͍ͯͳ͍

  28. मਖ਼ޙ def create @micropost = Micropost.new(micropost_params.merge(user: current_user)) if @micropost.save_with_profanity_callbacks flash[:success]

    = "Micropost created!" redirect_to root_url else adjust_micropost_profanity_message render 'static_pages/home' end end
  29. $POUSPMMFSͷ੹຿ w ϞσϧͷϝιουΛݺͿ w Πϯελϯεม਺΁ͷ୅ೖ w Ϗϡʔͷબ୒ ΋͘͠͸ϦμΠϨΫτઌͷબ୒

  30. ϩδοΫͷ͢΂ͯΛ Ϟσϧʹ೚ͤΔؾ࣋ͪͰ

  31. class UsersController < ApplicationController def create @user = User.new(user_params) if

    @user.save UserMailer.thanks(@user).deliver_later redirect_to @user, notice: '登録完了しました' else render :new end end ͜ͷ͘Β͍͸ڐ༰
  32. ͺͬͱݟͯ෼͔ΔൣғͳΒ ίϯτϩʔϥʹॻ͍ͯ΋େৎ෉

  33. .PEFMΛਖ਼͘͠࢖͏

  34. ࠶ܝ ϩδοΫͷ͢΂ͯΛ Ϟσϧʹ೚ͤΔؾ࣋ͪͰ

  35. ϩδοΫͷ͢΂ͯΛ Ϟσϧʹ೚ͤΔ 'BU.PEFM

  36. 'BU.PEFMͷԿ͕ྑ͘ͳ͍ͷ w େ఍ͷ৔߹ɺϞσϧಉ͕࢜ີ݁߹ʹͳ͍ͬͯΔ w ີ݁߹͸मਖ਼͕࿈࠯͠΍͍͢ w "ΫϥεͷϝιουΛमਖ਼ͨ͠Β#Ϋϥεͷमਖ਼͕ඞཁ ʹͳͬͯɺͦΕΛमਖ਼ͨ͠Β$Ϋϥεͷमਖ਼͕ʜ w ˠόάͷݪҼʹͳΓ΍͍͢

  37. 'BU.PEFMͷྫ class User < ApplicationRecord has_many :posts has_many :friendships has_many

    :friends, through: :friendships def create_post_with_notifications!(body) transaction do posts.create!(body: body) friends.each do |friend| friend.notifications.create!("#{name}さんが投稿しました") end end end # 大量の似たようなメソッド end
  38. ղܾࡦ

  39. 1030ʹ੾Γग़͢ class PostWithNotifications def self.create!(creator:, body:) new(creator: creator, body: body).create!

    end def initialize(creator:, body:) @creator = creator @body = body end def create! ActiveRecord::Base.transaction do create_post! create_notifications! end end  private attr_reader :creator, :body def create_post! creator.posts.create!(body: body) end def create_notifications! creator.friends.each { |friend| create_notification!(friend) } end def create_notification!(friend) friend.notifications.create!( “#{creator.name}さんが投稿しました" ) end end
  40. IUUQUFDINFEQFFSDPKQFOUSZ

  41. 7JFXΛਖ਼͘͠࢖͏

  42. 7JFXʹ େ͖͍՝୊͸ͳ͍ ͸ͣ

  43. ͜͜·Ͱͷ·ͱΊ w ϩδοΫΛϞσϧʹدͤΔ w 1030 "DUJWF3FDPSEҎ֎ͷϞσϧ Λ׆༻͢Δ

  44. ͜Ε͚ͩͰ΋ฏۉҎ্ ͷՄಡੑ ౰ࣾൺ

  45. ͔࣍͠͠ͷΑ͏ͳέʔεͰ ·ͩࠔΔ w େྔͷCFGPSF@pMUFS w WBMJEBUJPO΍DBMMCBDLͷ৔߹෼͚ w ͻͱͭͷΞΫγϣϯͰͨ͘͞Μ΍Δ͜ͱ͕͋Δ

  46. େྔͷCFGPSF@pMUFS class IssuesController < ApplicationController before_filter :find_issue, :only => [:show,

    :edit, :update] before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy] before_filter :find_project, :only => [:new, :create, :update_form] before_filter :authorize, :except => [:index] before_filter :find_optional_project, :only => [:index] before_filter :check_for_default_issue_status, :only => [:new, :create] before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
  47. CFGPSF@pMUFS͕ଟ͍ͱ Կ͕໰୊͔ w ࣗ෼͕ؾʹ͍ͯ͠ΔίϯςΩετ ΞΫγϣϯ Ͱͳ ʹ͕࣮ߦ͞Ε͍ͯΔͷ͔͕Θ͔ΓͮΒ͍ w ࠷ऴతʹͲΜͳΠϯελϯεม਺͕ઃఆ͞Ε͍ͯΔ ͷ͔Θ͔ΓͮΒ͍

  48. આ໌༻ͷ΋ͬͱ؆୯ͳྫ class PostsController < ApplicationController before_filter :set_posts, only: :index before_filter

    :set_post, only: :show before_filter :set_recommendations def index end def show end private def set_posts @posts = post.page(params[:page).per(10) end def set_post @post = post.find(params[:id]) end def set_recommendations @recommendation_categories = Category.recommendations @recommendations = @recommendation_categories.posts end end
  49. CFGPSF@pMUFSBDUJPO class PostsController < ApplicationController def index set_recommendations set_posts end

    def show set_recommendations set_post end private def set_posts @posts = post.page(params[:page).per(10) end def set_post @post = post.find(params[:id]) end def set_recommendations @recommendation_categories = Category.recommendations @recommendations = Recommendations.new(@recommendation_categories) end end
  50. w ࣗ෼͕ؾʹ͍ͯ͠ΔίϯςΩετ ΞΫγϣϯ Ͱͳʹ͕࣮ߦ ͞Ε͍ͯΔͷ͔͕Θ͔ΓͮΒ͍ˠ  w ࠷ऴతʹͲΜͳΠϯελϯεม਺͕ઃఆ͞Ε͍ͯΔͷ͔Θ ͔ΓͮΒ͍ˠ

  51. ΫΤϦϝιουԽ class PostsController < ApplicationController def index @recommendation_categories = fetch_recommendation_categories

    @recommendations = fetch_recommendations(@recommendation_categories) @posts = fetch_posts end def show @recommendation_categories = fetch_recommendation_categories @recommendations = fetch_recommendations(@recommendation_categories) @post = fetch_post end private def fetch_posts post.page(params[:page).per(10) end def fetch_post post.find(params[:id]) end def fetch_recommendation_categories Category.recommendations end def fetch_recommendations(categories) Recommenadtions.new(categories) end end
  52. w ࣗ෼͕ؾʹ͍ͯ͠ΔίϯςΩετ ΞΫγϣϯ Ͱͳʹ͕࣮ߦ ͞Ε͍ͯΔͷ͔͕Θ͔ΓͮΒ͍ˠ  w ࠷ऴతʹͲΜͳΠϯελϯεม਺͕ઃఆ͞Ε͍ͯΔͷ͔Θ ͔ΓͮΒ͍ˠ

  53. Ұݟ໰୊ͳͦ͞͏ʹ ݟ͑Δ

  54. Πϯελϯεม਺͕ଟ͍ͱʜ class PostsController < ApplicationController def index @recommendation_categories = fetch_recommendation_categories

    @recommendations = fetch_recommendations(@recommendation_categories) @posts = fetch_posts @ad = fetch_ad @widget = fetch_widget @sidebar = fetch_sidebar @title = fetch_title @cart = current_cart @search = build_search_form end def show # indexと同じようにたくさんのインスタンス変数への代入がある end private # たくさんのプライベートメソッド end
  55. Πϯελϯεม਺͕ଟ͍ͱʜ w ϓϥΠϕʔτϝιου͕ଟ͘ͳΓݟ௨͕͠ѱ͘ͳΔ w ˢΛϞσϧ΁Ҡ͢ͱଟগ؇࿨͞ΕΔ

  56. Ϗϡʔʹ౉͢ΦϒδΣΫτ͕ ଟ͍ͱݟͮΒ͍

  57. ղܾࡦ ʮϏϡʔʹ౉͢ม਺Λந৅Խʯ

  58. 7JFX.PEFM 7JFX0CKFDU class PostsController < ApplicationController def index @view_model =

    PostIndexViewModel.new end end class PostIndexViewModel def posts @posts ||= Post.page(params[:page).per(10) end def recommendation_categories @recommendation_categories ||= Category.recommendations end def recommendations @recommendations ||= recommendation_categories.posts end end
  59. 7BMJEBUJPO DBMMCBDL ͷ ৔߹෼͚ class Post < ApplicationRecord validates :body,

    presence: true, length: { maximum: 1000 } validates :published_at, presence: true validate :publish_at_should_after_current, unless: :by_admin attr_reader :by_admin def update_by_admin(attrs) @by_admin = true update(attrs) end def publish_at_should_after_current return unless publish_at return if publish_at > Time.zone.now errors.add(:publish_at, 'は現在時刻より後にしてください') end end
  60. ద੾ͳந৅Խ Կͯ͠Δͷ͔ͻͱ໨Ͱ ෼͔ΔΑ͏ʹ͢Δ

  61. Կͯ͠Δͷ͔ͻͱ໨Ͱ ෼͔ΔΑ͏ʹ͢Δ ৔߹෼͚

  62. ղܾࡦ ϑΥʔϜ͝ͱʹΫϥεΛ࡞Δ

  63. 'PS̼0CKFDU class PostForm include ActiveModel::Model attr_accessor :post, :body, :publish_at validates

    :body, presence: true, length: { maximum: 1000 } validates :publish_at, presence: true validate :publish_at_should_after_current def save post.attributes = { body: body, publish_at: publish_at } post.save if valid? end private def publish_at_should_after_current return unless publish_at return if publish_at > Time.zone.now errors.add(:publish_at, 'は現在時刻より後にしてください') end end
  64. 'PS̼0CKFDU class AdminPostForm include ActiveModel::Model attr_accessor :post, :body, :publish_at validates

    :body, presence: true, length: { maximum: 1000 } validates :publish_at, presence: true def save post.attributes = { body: body, publish_at: publish_at } post.save if valid? end end
  65. IUUQUFDINFEQFFSDPKQFOUSZ

  66. ͻͱͭͷΞΫγϣϯͰ ͨ͘͞Μ΍Δ͜ͱ͕͋Δ class PostsController < ApplicationController def create Hoge.prepare_create1 #

    なんらかの前処理1 @post = current_user.post.build(post_params) Hoge.prepare_create2 # なんらかの前処理2 unless @post.save Fuga.prepare_new # new を表示するためのいろいろな前処理 render :new and return end Foo.after # なんらかの後処理 Bar.after # なんらかの後処理2 end end
  67. ϩδοΫΛϞσϧʹ Ҡͯ͠΋TMJNʹͳΓ͖Εͳ͍ έʔε

  68. ղܾࡦ ϞσϧͷϩδοΫΛݺͿՕॴΛ ந৅Խ͢Δ

  69. 4FSWJDF0CKFDU

  70. ஫ҙਓʹΑ͍ͬͯΖ͍Ζͳ 4FSWJDF͕͋Δ w 1P&""ʮ4FSWJDF-BZFSʯ w %%%ʮ4FSWJDFʯ w ͦΕΒΛ౿·͑ͨ3BJMTͷʮ4FSWJDF0CKFDUʯ w ͜Ε΋ෳ਺छྨ͕͋Δ

  71. 3BJMTͷ4FSWJDF0CKFDUʹ΋ ਓʹΑ༷ͬͯʑͳ࣮૷͕͋Δ w ͖ͬ͞ͷ1030ͷྫΛ4FSWJDF0CKFDUͱݺΜͰ͍Δ w ίϯτϩʔϥͷϩδοΫͷҰ෦ΛNPEVMFͷΫϥεϝιου ʹҠͯͦ͠ΕΛ4FSWJDFͱݺΜͰ͍Δ w ͦͷΞΫγϣϯͰ࣮ߦ͢Δ͜ͱΛ·ΔͬͱผΫϥεʹஔ͖ ׵͑Δ

  72. ͜ͷൃදͰͷ4FSWJDF0CKFDU w ΞΫγϣϯͷࡉʑͨ͠෦෼Λ·Δͬͱ୲͏ΦϒδΣΫτ w SFOEFS SFEJSFDU@UPͳͲ͸ίϯτϩʔϥ͕΍Δ w υϝΠϯϩδοΫ͸Ϟσϧ͕΍Δ

  73. 4FSWJDF0CKFDU class CreatePostService attr_reader :result def self.call(user:, params:) new(user: user,

    params: params).call end def initialize(user:, params:) @user = user @params = params @result = true end def call before_callback unless post.save fail! prepare_new return end after_callback self end def post @post ||= user.post.build(params) end private attr_reader :user, :paramas def before_callback Hoge.prepare_create1 Hoge.prepare_create2 end def after_callback Foo.after Bar.after end def prepare_new Fuga.prepare_new end def fail! @result = false end def post_params params.require(:post).permit(:body) end end
  74. 4FSWJDF0CKFDU class PostsController < ApplicationController def create service = CreatePostService.call(user:

    current_user, params: params) unless service.result @post = service.post render :new and return end redirect_to posts_path, notify: '作成されました' end end
  75. ͜Ε͚ͩ͋Ε͹͋Δఔ౓େ͖͍ 3BJMTΞϓϦέʔγϣϯͰ΋ઓ͑Δ w7JFX.PEFM w'PSN0CKFDU w4FSWJDF0CKFDU

  76. ͔͠͠Ͳ͏΍ͬͯݱ৔ ʹಋೖ͢Δʁ

  77. νʔϜ։ൃʹ͸۩ମతͳྫ΍ Θ͔Γ΍͍͢υΩϡϝϯτ͕ ෆՄܽ

  78. ౷Ұͨ͠ॻ͖ํʹ ༠ಋͰ͖Δπʔϧ͕༗Δͱྑ ͍ͷͰ͸

  79. IUUQTHJUIVCDPNXJMMOFUZVCB

  80. :VCBϙΤϜ w 7JFX.PEFM 'PSN0CKFDU 4FSWJDF0CKFDUͷ࡞੒ิॿ༻HFN w ن໿ʹΑΓ࡞ΓํͰ໎Θͳ͍Α͏ʹ w 1030ͩͱຖճ΍Βͳ͖Ό͍͚ͳ͍໘౗ͳهड़͸؆୯ʹ͔͚ΔΑ͏ʹ w

    ඞཁͳͱ͜Ζ͚ͩಋೖͰ͖ΔΑ͏ʹ w ಋೖ͠΍ࣺͯ͘͢΍͍͢࡞Γʹ w ബ͘࡞Δ w 3BJMTΤϯδχΞ͕ҧ࿨ײͳ͘࢖͑ΔΠϯλϑΣʔε
  81. ஫ҙ w WFSͰ͢ w ී௨ʹ࢖͑Δ͸ͣͰ͕͢ɺ͜Ε͔Β࢓༷͕มΘΔՄೳੑ͕ ͋Γ·͢ w ಛʹ:VCB'PSN

  82. 1030Ͱ7JFX.PEFM class PostViewModel attr_reader :post def initialize(post:, author:, other: nil)

    @post = post @author = author @other = other end def title post.title end def body post.body end private attr_reader :author, :other end
  83. ຖճಉ͡Α͏ͳॲཧΛ ॻ͔ͳ͚Ε͹ͳΒͳ͍ class PostViewModel attr_reader :post def initialize(post:, author:, other:

    nil) @post = post @author = author @other = other end def title post.title end def body post.body end private attr_reader :author, :other end
  84. :VCB7JFX.PEFM class PostViewModel < Yuba::ViewModel property :post property :author, public:

    true property :other, optional: true def title post.title end def body post.body end end
  85. !WJFX@NPEFM໰୊ class PostsController < ApplicationController def show @view_model = PostViewModel.new(post:

    post, author: author) end end <%= @view_model.post.title %> <%= @view_model.author.name %>
  86. ඞͣ!WJFX@NPEFMΛ ܦ༝͠ͳ͚Ε͹ͳΒͳ͍ class PostsController < ApplicationController def show @view_model =

    PostViewModel.new(post: post, author: author) end end <%= @view_model.post.title %> <%= @view_model.author.name %>
  87. 7JFX.PEFMΛల։ͯ͠ Πϯελϯεม਺ʹׂ౰ class PostsController < ApplicationController def show view_model =

    PostViewModel.new(post: post, author: author) render view_model: view_model end end <%= @post.title %> <%= @author.name %>
  88. :VCB4FSWJDF class PostController < ApplicationController def new @post = CreatePostService.call(user:

    current_user).post end def create service = CreatePostService.call(user: current_user, params: params) if service.success? redirect_to root_path else @post = service.post render :new end end end
  89. :VCB4FSWJDF class CreatePostService < Yuba::Service property :user, public: true property

    :params, optional: true def call if post.save notify_to_admin else fail! end end def post user.posts.build(post_params) end private def notify_to_admin AdminMailer.notify_create_post(post).deliver_later end def post_params params.require(:post).permit(:title, :body) end end
  90. :VCB4FSWJDFͷಛ௃ w 7JFX.PEFMͱಉ͡࢓༷ͷQSPQFSUZ w ࣮ߦ͸DBMM w DBMMͷҾ਺ΛQSPQFSUZʹׂΓ౰ͯͯDBMMΛݺͼग़͢ w TVDDFTT Ͱ੒ޭ൑ఆ

  91. :VCB'PSN w ݱঢ়  SFGPSNSBJMTͷϥούʔ w IUUQTHJUIVCDPNUSBJMCMB[FSSFGPSNSBJMT w ࣗલͷίʔυʹॻ͖׵͑தͳͷͰ͠͹Β͓͘଴ͪԼ͍͞ʜ

  92. ࢼͯ͠Έ͍ͯͩ͘͞

  93. ·ͱΊ w .7$ͷਖ਼͍͠࢖͍ํʹ͍ͭͯ w 'BU$POUSPMMFS 'BU.PEFMͷղܾํ๏ w 3BJMT͕ఏڙ͍ͯ͠Δ.7$͚ͩͰ͸ରԠ͖͠Εͳ͍໰୊ͷղܾํ๏ w 7JFX.PEFM

    w 'PSN0CKFDU w 4FSWJDF0CKFDU w ˢΛແཧͳ͘ಋೖ͢ΔͨΊͷϥΠϒϥϦ:VCBͷ঺հ
  94. )BQQZ3BJMT$PEJOH