지난 글에서 수정 할 것이라 언급했던 내용을 분류하여 간단히 정리하면 다음과 같다.

1. 마이그레이션
 History 모델 변경 - Detail_history 모델 생성
 회원 성별, 나이
 DB 제약조건

2. 커피스크립트
 자바스크립트 코드 버그
 포인트 사용 폼
 회원 검색

3. 레이아웃
 공통 레이아웃

  가장 먼저 마이그레이션 수정을 진행하였다. 수정 전 ERD는 다음과 같다.

 먼저 History 모델을 세부적으로 바꾸기 위해 다음과 같은 작업을 하였다.

 - 필드명 변경

    price → total_price

    point → point_used

    is_cash → is_credit

- 필드 추가

    point_accumulated :integer

- 필드 삭제

    note :text

    content_id :integer

- 하위 테이블 생성

    detail_histories history:references content:references price:integer note:text


  위의 작업을 db에서 직접 ddl을 통해 변경할 수 있지만 rails의 마이그레이션을 통해 루비 코드 형태로 정의된 데이터베이스 스키마를 만들 수 있다.

  먼저 마이그레이션 파일을 만든다.

$ rails g migration AddDetailsToHistory

/db/migrate 폴더에 새로운 마이그레이션 파일이 생성된다.


db/migrate/20161216164045_add_details_to_history.rb

class AddDetailsToHistory < ActiveRecord::Migration[5.0]
  def change
    add_column :histories, :point_used, :integer
    rename_column :histories, :point, :point_accumulated
    rename_column :histories, :is_cash, :is_credit
    rename_column :histories, :price, :total_price
    remove_column :histories, :note, :text
    remove_column :histories, :content_id, :integer
  end
end

  필드 추가는 add_column 메소드를, 필드명 변경은 rename_column, 필드 삭제는 remove_column 메소드를 사용한다.


  다음으로 detail_history 모델을 생성하기 위해 아래의 명령어를 입력한다.

$ rails g model detail_history history:references content:references price:integer note:text

  모델을 생성하면 데이터베이스에 해당 모델 형식을 갖는 테이블을 생성하기 위한 마이그레이션 파일이 /db/migrate에 자동으로 생성된다.


  지난 시간에 association관계 설정을 위해 모델 클래스에 belongs_to, has_many 등의 관계를 설정해 두었다. 아래와 같이 수정하였다.


app/models/content.rb

class Content < ApplicationRecord
  belongs_to :category
  has_many :detail_histories

  # 생략
end

app/models/history.rb

class History < ApplicationRecord
  belongs_to :member
  has_many :details_histories
end

app/models/detail_history.rb

class DetailHistory < ApplicationRecord
  belongs_to :history
  belongs_to :content
end

같은 방법으로 Member 모델에 성별과 연령대를 추가한다.

$ rails g migration AddGenderAndAgesToMembers gender:string ages:string

마이그레이션명 뒤에 직접 추가할 필드명과 타입을 적어 자동으로 마이그레이션 파일에 추가시킨다.


이제 migrate를 하고 적용이 되었는지 확인해보자.
$ rake db:migrate


rails erd 플러그인을 이용하여 erd를 확인해 본다.

플러그인: http://rails-erd.rubyforge.org

$ rake erd


  현재 테이블에는 not null이나 default 제약조건이 전혀 설정되어있지 않다. 모든 테이블에 필요한 제약조건을 추가하였다. member테이블의 예는 다음과 같다.

$ rails g migration AlterConstraintToMembers


db/migrate/20161216182645_alter_constraint_to_members.rb

class AlterConstraintToMembers < ActiveRecord::Migration[5.0]
  def change
    change_column_null :members, :name, false
    change_column_default :members, :ages, 'unknown'
    change_column_null :members, :gender, false
    change_column_default :members, :acc_point, 0
    change_column_default :members, :use_point, 0
    change_column_default :members, :rem_point, 0    
  end
end

  포인트의 기본값을 0으로 설정했으므로 앞선 개발과정에서 컨트롤러에서 기본값을 추가 했던 코드는 삭제해도 좋을 것이다.


  마지막으로 외래키로 연결된 데이터가 삭제 될 때 CASCADE 형태로 삭제 될 것인지, NOT NULL 형태로 유지할 것인지 조건을 설정하려 했다. 그러나 rails는 references 헬퍼로 외래키를 연결하는 듯 하지만 실제로는 외래키 제약조건을 설정하지 않는다. dbconsole을 열어서 contents 테이블을 확인해 보면 다음과 같다.

$ rails dbconsole

> .schema contents


 어디에도 외래키 제약조건은 없고, category_id와 관련된 인덱스가 생성되는 코드가 있음을 알 수 있다. 따라서 DDL로 CASCADE나 NOT NULL 제약조건을 추가 할 수 없는 듯 하다. 만약 이렇게 하고 싶다면 외래키 제약조건을 직접 걸어야 한다.



참고: http://rubykr.github.io/rails_guides/migrations.html


  references 헬퍼를 없애고 외래키 제약조건을 설정하기 보다 컨트롤러를 통해 유사한 기능을 추가할 것이다.


  위의 제약조건대로 설정하고 rails db:migrate를 하면 에러가 발생한다. 기존의 데이터들이 gender를 가지고 있지 않은데 not null로 설정되어 있기에 에러가 발생하는 듯 하다. db를 초기화 하고 다시 마이그레이션 한다.

$ rake db:reset

$ rake db:migrate


  이제 수정된 모델 형태로 뷰와 컨트롤러를 수정하자. members에는 gender와 ages가 추가된 필드를 모두 추가해 주고, history에도 변경된 필드를 추가해 주었다. 그리고 회원의 시술이력에서 큰 History객체와 여러개의 Detail_history 객체를 동시에 생성하는 코드를 작성하였다. fields_for 폼 헬퍼를 이용하여 부모와 자식 객체를 동시에 생성 할 수 있었다.

참고: https://withrails.com/2016/01/09/1187/


app/models/history.rb

class History < ApplicationRecord
  belongs_to :member
  has_many :detail_histories
  accepts_nested_attributes_for :detail_histories, reject_if: :all_blank
end

  하나의 history객체 밑에 여러 개의 detail_history객체를 가지므로 1:n 관계가 형성된다. has_many 관계를 설정하고 nested attributes로 detail_history의 필드를 가져온다. 모든 필드가 비어있으면 자식 객체를 생성하지 않도록 reject_if: :all_blank를 추가한다.


app/models/detail_history.rb

class DetailHistory < ApplicationRecord
  belongs_to :history, optional: true
  belongs_to :content
end

  현재 개발하는 rails의 버전이 5버전이므로 optional: true를 꼭 설정해 주어야 한다. 그렇지 않으면 아래 사진처럼 뷰에서 컨트롤러로 파라미터는 정확히 전달되나 rollback transaction을 수행하면서 부모와 자식객체를 동시에 생성하여 저장하지 않는다. 

app/controllers/histories_controller.rb

class HistoriesController < ApplicationController
# 생략

  private
    # 생략

    # Never trust parameters from the scary internet, only allow the white list through.
    def history_params
      params.require(:history).permit(:member_id, :date, :total_price, :point_used, :point_accumulated, :is_credit,
        detail_histories_attributes: [:content_id, :price, :note])  # fields_for 메서드를 사용하기 위해 nested attribute 추가
    end
end

  위 모델에서 nested_attributes를 선언한 것 처럼 컨트롤러에서도 뷰에서 전달되는 파라미터를 history_params 메서드를 통해 접근할 수 있도록 detail_histories_attributes를 추가해준다.


app/controllers/members_controller.rb

class MembersController < ApplicationController
  # 생략

  # GET /members/1
  # GET /members/1.json
  def show
    # 생략

    @history = History.new
    2.times do
      @history.detail_histories.build
    end

    # 생략
  end

  # 생략
end
  현재 애플리케이션에서는 history 생성폼이 members/show에 있기 때문에 해당 메서드에서 새로운 @history 액티브레코드를 생성해준다. @history.detail_histories.build 메서드를 통해 새로운 객체를 생성한다. 현재는 두개의 자식 객체를 저장할 수 있도록 설정되어있다.

app/views/members/show.html.erb
<!-- 생략 -->

<h2>시술 기록</h2>
<%= form_for @history do |f| %>
  <!-- 생략 -->

  <table>
    <thead>
      <tr>
        <th>시술종류</th>
        <th>시술별 가격</th>
        <th>비고</th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <%= f.fields_for :detail_histories do |df| %>
      <tr>
        <td><%= df.grouped_collection_select :content_id, @categories, :contents, :name, :id, :name %></td>
        <td><%= df.number_field :price %></td>
        <td><%= df.text_area :note %></td>
        <td><!-- 추가 버튼 --></td>
      </tr>
      <% end %>
    </tbody>
  </table>

  <!-- 생략 -->
<% end %>

<!-- 생략 -->

  history 객체 생성을 위한 form_for 헬퍼 안에 fields_for 헬퍼를 통해 하위 객체를 함께 생성 할 수 있다. 컨트롤러에서 두개의 객체를 생성했으므로 여기서도 두개의 하위 객체에 대한 필드가 생성된다.



  위와 같이 동시에 저장 되는걸 확인 할 수 있다.



+ Recent posts