devise에서 여러 모델로 저장

실현하고 싶은 일



devise의 계정 등록시에 2개의 모델에 기록시키고 싶다.
(사용자 정보 및 회사 정보)

왜?



다음 문제가 발생하기 때문입니다.
  • Follow 테이블
    Campany와 User 각각에서 User A 씨를 팔로우하고 싶었다.

  • 문제점



    팔로우 기능 구현 시 Campany(모델)와 User(모델)로 등록한 사용자는 같은 번호의 id를 가지고 있다.
    ┗Campany 페이지에서 계정 등록을 실시했을 때의 1번째의 유저 id는 1번.
    ┗User 페이지에서 계정 계정 등록을 했을 때의 1번째 사용자 id는 1번.
    Follow 테이블에 follower로서 정보를 기록할 때 뒤의 연결을 위해 id를 남기고 싶지만 Campany와 User는 각각 다른 테이블에서 온 id 정보.
    id 1이 되어 중복하기 때문에, 이 방법은 좋지 않았다.


    생각한 해결 방법



    법인(Campany)도 User로 간주 User 테이블에 등록한다.
    다만 법인정보는 자식모델로서 Campanies 테이블에 등록한다.

    준비



    devise의 기본 설정 열에 임의 열 추가

    devise의 registrations_controller.rb
      def configure_sign_up_params
        devise_parameter_sanitizer.permit(:sign_up,
          keys: [:name, :category_id, campany_attributes: [:campany_name, :campany_logo, :category_id, :campany_url, :tel, :staff_last_name_kana, :staff_first_name_kana]])
      end
    

    campany_attributes: [:campany 레코드의 열 이름, :campany 레코드의 열 이름, ]
    모델명 _attributes로 하는 것으로 부모 모델에 연결하고 있다.
    (보충 : campany_attributes의 campany는 has_one 관계라면 단수, has_many라면 복수)

    연관 설정



    user.rb
      # userモデルの中でcampanyモデルへも同時書き込み
      has_one :campany, inverse_of: :user
      accepts_nested_attributes_for :campany
    
      # Userモデル中でcampanyモデルへも書き込む
      belongs_to :user, inverse_of: :campany
    

    이번에는 new 액션은 user 등록으로 사용하고 있다.
    법인 등록은 다른 화면으로 하고 싶었으므로 아래의 루트로 준비.

    routes.rb
        get '/campany/new' => 'users/registrations#campany' ,as: :new_campany
    

    참고로 한 기사에서는
    @user.campany.build로 중첩 모델의 구조로 할 수 있다고 했지만, 이 기술이지만 잘 가지 않았다.
    하기의 기법으로 하는 것으로 해결할 수 있었다.
    또한 create 액션은 devise의 기능을 덮어쓰기 때문에 super 아래에 기술한다.
    마지막으로 user.save로 하지 않으면 아이 모델(campany)에는 보존되지 않기 때문에 잊지 말고.

    devise의 registrations_controller.rb
      # 企業の登録フォーム
      def campany
        @user = User.new
        @user.build_campany
      end
    
      def create
        super
        user = User.new(configure_sign_up_params)
        user.save
      end
    

    주의:
    하위 모델에 데이터를 전달하는 방법은 다음과 같이 f.fields_for를 사용합니다.
    상하와 같이 들여쓰기가 제대로 갖추어져 있으면 실현하고 싶은 레이아웃에 맞추어 User 모델, Campany 모델에 건네주는 데이터의 위치가 흩어져 있어도 좋다.
    (부모 form은 form_for임)

    campany.html.erb
    <h2><%= t('.sign_up') %></h2>
    
    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
      <%= render "devise/shared/error_messages", resource: resource %>
    
      <div class="field">
        <%= f.label "会社名" %><br />
        <%= f.text_field :name, autocomplete: "off" %>
      </div>
    
      <%= f.fields_for :campany do |campany| %>
        <div class="field">
          <%= campany.label "会社URL" %><br />
          <%= campany.text_field :campany_url, autocomplete: "off" %>
        </div>
    
        <div class="field">
          <%= campany.label :tel %><br />
          <%= campany.telephone_field :tel, autocomplete: "off" %>
        </div>
      <% end %>
    
      <div class="field">
        <%= f.label "カテゴリー" %><br />
        <%= f.collection_select :category_id, Category.all, :id, :name, {prompt: "選択してください"}%>
      </div>
    
      <%= f.fields_for :campany do |campany| %>
        <div class="field">
          <%= campany.label "ご担当者様 姓(カナ)" %><br />
          <%= campany.text_field :staff_last_name_kana, autocomplete: "off" %>
        </div>
    
        <div class="field">
          <%= campany.label "ご担当者様 名(カナ)" %><br />
          <%= campany.text_field :staff_first_name_kana, autocomplete: "off" %>
        </div>
      <% end %>
    
      <div class="field">
        <%= f.label :email %><br />
        <%= f.email_field :email, autofocus: true, autocomplete: "off" %>
      </div>
    
      <div class="field">
        <%= f.label :password %>
        <% if @minimum_password_length %>
        <em>(<%= @minimum_password_length %> characters minimum)</em>
        <% end %><br />
        <%= f.password_field :password, autocomplete: "off" %>
      </div>
    
      <div class="field">
        <%= f.label :password_confirmation %><br />
        <%= f.password_field :password_confirmation, autocomplete: "off" %>
      </div>
    
      <div class="actions">
        <%= f.submit t('.sign_up')%>
      </div>
    <% end %>
    
    <%= render "devise/shared/links" %>
    



    무사히 2개의 모델에 보존되었습니다.

    실패한 것



    registrations_controller 안에서 campany 메소드를 처음에는 new_campany라는 이름으로 하고 있었다.
    오류로 new_campany_path? 라고 말했지만 그런 것은 아니다.
    애초에 메소드명이 prefix 패스로 에러 문 토출한다는 것은 어떤 의미?
    고민하는 것 3시간, 예약어 키워드가 고개를 끄덕였다.
    설마 생각 메소드 이름을 바꾸자 뷰에 무사히 날아 줬다.
    예약어에는 조심하자.

    좋은 웹페이지 즐겨찾기