Freelance Orgsin Official Site

ごゆっくりしていって下さい

Service Objectについて <uninitialized constant の対応>

Service Objectとは

DDDで言うところのDomain層のビジネスロジックの責務を抜き出したものだという認識.Railsで言えば,app/models配下の依存するモデルにロジックを記述して,肥大化しそうならQuery Objectパターンを使って切り出すリファクタリングをするだろう.それに近しいがもう少し柔軟なかたちで定義できる. 例えば,Query Objectはあくまでもクエリに興味があるものであるが,Service Objectはそれ以外のビジネスロジックが含まれていても問題はない.

実際のコード

ユーザ情報を保存する.ただし,条件によってデータを入れ替えたりするのでロジックが多くなりがち

# app/controllers/user_controller.rb
class UserController < ApplicationController

  def save
    user = User.new
    user.lastname = @lastname if @lastname.present?
    user.firstname = @firstname if @firstname.present?
    user.set_birthday(@year, @month, @day) # モデル側で誕生日を格納
    user.set_type # モデル側で特定の条件の時に対象タイプを格納
    # 省略
    user.save
    user
    # 省略
  end
end
# app/controllers/user_controller.rb
class UserController < ApplicationController
  def save
    UserService.new(params).call
    # 省略
  end
end

# app/services/user_service.rb
class UserService
  def initialize(params)
    lastname = params[:lastname]
    firstname = params[:firstname]
    @user = User.new
    @user.lastname = lastname if lastname.present?
    @user.firstname = firstname if firstname.present?
    @user.set_birthday(params[:year], params[:month], params[:day])
  end

  def call
    return if @user.birthday.nil?
    user_params
    save_user
    @user
  end

  private

  def user_params
    @user.set_type
    @user.set_sample_type
    # 省略
  end


  def save_user
    @user.save if @user.type != 'NONE'
    logger.info("【登録】ユーザ #{@user.inspect}")
  end
end

起動するとエラーが出た

NameError in UserController#save
uninitialized constant UserControlle::UserService
パスを見てみる

app/servicesディレクトリは読み込まれていたが,該当サービスクラスがrequireされていない

> ActiveSupport::Dependencies.autoload_paths.grep(/services/)
=> ["xxxxxxxxxxxxxxx/orgsin/app/services"]

> $LOADED_FEATURES.grep(/user_service/)
=> []

解決策1

依存ファイルを該当コントローラ内で直接requireする
※毎回指定するのは面倒

# app/controllers/user_controller.rb
require_dependency 'app/services/user_service.rb'
class UserController < ApplicationController; end

解決策2

初期化時にapp/serviceディレクトリ直下を全requireする(個別のファイルを指定しても問題ない)

# config/initializers/dependencies.rb
Dir[Rails.root.join("app/services/**/*.rb")].sort.each { |f| require f }