여러개의 모델 객체를 하나의 form_for
헬퍼메소드에서 사용하는 것이 그렇게 쉽게 되지 않는 것 같습니다.
여기에는 약간의 꽁수가 필요하더군요. 설명을 위해서 리소스를 아래와 같이 가정하겠습니다.
class Person :destroy
attr_accessible :age, :name, :sex, :contact_attribute
def contact_attribute=(attribute)
build_contact(attribute)
end
end
class Contact < ActiveRecord::Base
belongs_to :person
attr_accessible :person, :address, :telephone
end
config/routes.rb 파일에서 리소스 정의는 다음과 같습니다.
Hasone::Application.routes.draw do
root :to => "people#index"
resources :people do
resource :contact
end
end
여기서 주의해야 할 것은, has_one
관계시에 resource :contact
와 같이 단수형 resource
와 단수형 :contact
를 사용해야 합니다. 그리고, app/controllers/people_controller.rb 파일에서 new
액션과 edit
액션에서 각각 @contact
인스터스를 생성해 주었습니다.
class PeopleController < ApplicationController
# GET /people
# GET /people.json
def index
@people = Person.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @people }
end
end
# GET /people/1
# GET /people/1.json
def show
@person = Person.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @person }
end
end
# GET /people/new
# GET /people/new.json
def new
@person = Person.new
@contact = @person.build_contact
respond_to do |format|
format.html # new.html.erb
format.json { render json: @person }
end
end
# GET /people/1/edit
def edit
@person = Person.find(params[:id])
@contact = @person.contact
end
# POST /people
# POST /people.json
def create
@person = Person.new(params[:person])
respond_to do |format|
if @person.save
format.html { redirect_to @person, notice: 'Person was successfully created.' }
format.json { render json: @person, status: :created, location: @person }
else
format.html { render action: "new" }
format.json { render json: @person.errors, status: :unprocessable_entity }
end
end
end
# PUT /people/1
# PUT /people/1.json
def update
@person = Person.find(params[:id])
respond_to do |format|
if @person.update_attributes(params[:person])
format.html { redirect_to @person, notice: 'Person was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @person.errors, status: :unprocessable_entity }
end
end
end
# DELETE /people/1
# DELETE /people/1.json
def destroy
@person = Person.find(params[:id])
@person.destroy
respond_to do |format|
format.html { redirect_to people_url }
format.json { head :no_content }
end
end
end
이 @contact
인스턴스는 fields_for
헬퍼메스드의 매개변수로 넘겨 주었구요. people
컨트롤러에서 params[:person]
으로 넘겨 받을 때 contact
속성값들도 함께 받기 위해서는 Person
모델에서 가상 속성을 정의해 주어야 합니다.
이미 위의 모델 정의에서 기술된 바와 같이 contact_attribute=(attribute)
라고 정의를 하게 되는 것입니다. 이 속성의 구현내용은 메소드로 넘겨받은 attribute
에 대해서 person
객체의 contact
객체를 만들게 됩니다.
그것은 has_one
관계로 연결되어 있기 때문에 self.contact.build
와 같이 생성하는 것이 아니라 self.build_contact(attribute)
와 같이 코딩해 주어야 합니다. 이것은 레일스의 약속입니다.
다음은 app/views/people/_form.html.rb 파일을 보겠습니다.
지금까지 설명한 내용을 생각하면서 아래의 fields_for
로 넘기는 매개변수 항목들을 눈여겨 볼 필요가 있습니다. 여기서는 첫번째 인수로 person[contact_attribute]
라고 문자열을 넘겨 주었지만, Person
모델에 추가로 정의한 가상 속성인 contact_attribute
를 이용해서 @person.contact_attribute
라고 해도 될 것이라고 생각했었습니다만, 실제로 그렇게 했을 때, contact_attribute
라는 메소드가 정의되어 있지 않다는 에러를 발생시키더군요. 이렇게 가상 속성을 구현해서 fields_for
를 사용시, 실제 가상속성에 대해서 구현하는 방법에 대해서는 rdoc
과 레일스 가이드에 설명이 부족하더군요.
<%= form_for(@person) do |f| %>
<% if @person.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@person.errors.count, "error") %> prohibited this person from being saved:</h2>
<ul>
<% @person.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :age %>
<%= f.text_field :age %>
</div>
<div class="field">
<%= f.label :sex %>
<%= f.text_field :sex %>
</div>
<%= fields_for "person[contact_attribute]", @contact do |contact_form| %>
<div class="field">
<%= contact_form.label :address %>
<%= contact_form.text_field :address %>
</div>
<div class="field">
<%= contact_form.label :telephone %>
<%= contact_form.text_field :telephone %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
역시 Ryan의 railscasts.com에 있는 http://railscasts.com/episodes/73-complex-forms-part-1 를 보고 많은 공부가 되었습니다.
이렇게 has_one
관계로 여러개의 모델을 연결하면, 테이블의 컬럼이 너무 많아 여러개의 테이블로 나누어서 작업해야 할 경우에 유용할 것같습니다. 그러나 실제로 하나의 테이블에 어느 정도의 컬럼갯수를 가지는 것이 효율적인지는 모르겠습니다. 여기서는 has_one
연결을 한 예를 소개했지만, has_many
관계로 연결된 모델객체를 사용할 때는 가상 속성의 정의와 폼 뷰 템플릿의 fields_for
로 넘겨주는 첫번째 문자열 인수를 약간 변경해야 합니다. 예를 들면, Person
모델이 has_many :contacts
이고, Contact
가 belongs_to :person
이라고 가정하면,
class Person :destroy
attr_accessible :age, :name, :sex, :contact_attributes
def contact_attributes=(attributes)
attributes.each { |attribute| contact.build(attribute) }
end
end
와 같이 수정해 주어야 하고, people_controller.rb파일에서는
# GET /people/new
# GET /people/new.json
def new
@person = Person.new
3.times { @person.contacts.build }
respond_to do |format|
format.html # new.html.erb
format.json { render json: @person }
end
end
# GET /people/1/edit
def edit
@person = Person.find(params[:id])
end
_form.html.erb 에서는
<% for contact in @contacts %>
<% fields_for "person[contact_attributes][]", contact do |contact_form| %>
<div class="field">
<%= contact_form.label :address %>
<%= contact_form.text_field :address %>
</div>
<div class="field">
<%= contact_form.label :telephone %>
<%= contact_form.text_field :telephone %>
</div>
<% end %>
<% end %>
와 같이 수정해야 합니다. 물론 라우터에서는
Hasone::Application.routes.draw do
root :to => "people#index"
resources :people do
resources :contacts
end
end
와 복수형으로 resources :contacts 라고 수정해 주어야 하겠죠.
레일스를 처음시작하는 분들을 위해서 저의 작은 경험을 함께 공유합니다.
루비에 막 입문한 초보자 입니다. 다른언어와 다른 특성때문에 잘 이해가 안되는 부분이 많아 이것저것 찾아보는 중입니다. 특히 DB관련이 그렇네요. 좋은글 감사합니다.