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

レールの伸ばし方

 レールの伸ばし方

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

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

Shinichi Maeshima

December 09, 2017
Tweet

More Decks by Shinichi Maeshima

Other Decks in Technology

Transcript

  1. ద੾ͳந৅Խͷྫ 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
  2. ద੾Ͱͳ͍ந৅Խͷྫ 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]
  3. ѱ͍ྫ '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
  4. मਖ਼ޙ 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
  5. 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 ͜ͷ͘Β͍͸ڐ༰
  6. '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
  7. 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
  8. େྔͷ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]
  9. આ໌༻ͷ΋ͬͱ؆୯ͳྫ 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
  10. 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
  11. ΫΤϦϝιουԽ 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
  12. Πϯελϯεม਺͕ଟ͍ͱʜ 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
  13. 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
  14. 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
  15. '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
  16. '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
  17. ͻͱͭͷΞΫγϣϯͰ ͨ͘͞Μ΍Δ͜ͱ͕͋Δ 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
  18. 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
  19. 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
  20. :VCBϙΤϜ w 7JFX.PEFM 'PSN0CKFDU 4FSWJDF0CKFDUͷ࡞੒ิॿ༻HFN w ن໿ʹΑΓ࡞ΓํͰ໎Θͳ͍Α͏ʹ w 1030ͩͱຖճ΍Βͳ͖Ό͍͚ͳ͍໘౗ͳهड़͸؆୯ʹ͔͚ΔΑ͏ʹ w

    ඞཁͳͱ͜Ζ͚ͩಋೖͰ͖ΔΑ͏ʹ w ಋೖ͠΍ࣺͯ͘͢΍͍͢࡞Γʹ w ബ͘࡞Δ w 3BJMTΤϯδχΞ͕ҧ࿨ײͳ͘࢖͑ΔΠϯλϑΣʔε
  21. 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
  22. ຖճಉ͡Α͏ͳॲཧΛ ॻ͔ͳ͚Ε͹ͳΒͳ͍ 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
  23. :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
  24. !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 %>
  25. ඞͣ!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 %>
  26. 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 %>
  27. :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
  28. :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