Rails4 - nested form
Sep 13, 16
dev
rails
육아는 아이템전
을 만들면서 사용한 nested form에 대해 정리해 둔다.- Parenting_neees_money에서 각 Item은 여러개의 keyword를 가지고 있고, keyword는 여러개의 data 로그를 가지고 있다.
class Item < ActiveRecord::Base
has_many :keywords, dependent: :destroy
end
class Keyword < ActiveRecord::Base
belongs_to :item
has_many :logs, dependent: :destroy
end
class Logs < ActiveRecord::Base
belongs_to :keyword
end
- Item을 추가하는 form에서 keyword도 추가하고 싶고, keyword에 대한 log도 3rd party api를 통해 가져와서 생성해야 했다.
- Rails에서는 form에 association을 위한 field를 만들 수 있다.
- 우선
Item
model에accepts_nested_attributes_for :keywords, allow_destroy: true
를 추가한다.
class Item < ActiveRecord::Base
has_many :keywords, dependent: :destroy
accepts_nested_attributes_for :keywords, allow_destroy: true
end
accepts_nested_attributes_for :keywords
는keywords_attributes=
method를 추가시켜준다.- 기본적으로 create/update가 가능하고
allow_destroy: true
option을 통해서 destroy도 가능하게 할 수 있다. - 이제 strong parameter에서
keywords_attributes
를 허용시켜주면 item.create 또는 item.udpate_attributes method를 통해 Keyword 를 create/update 할 수 있게 된다.
class ItemsController < ApplicationController
private
def item_params
params.require(:item).permit(keywords_attributes: [:id, :name, '_destroy')
end
end
allow_destroy: true
옵션을 사용하려면 strong parameter에id, _destroy
를 허용시켜 줘야 한다._destroy
의 값은marked_for_destruction?
값에 대입되고, 해당 값이true
이면, item object가save
될 때, Keyword record가 삭제된다.accepts_nested_attributes_for
는 원하는 만큼 depth를 만들 수 있다.- Item form을 통해서 Keywrod, Log record를 만들어야 되기에 Keyword model에도 accepts_nested_attributes_for를 추가해준다.
class Keyword < ActiveRecord::Base
has_many :logs, dependent: :destroy
accepts_nested_attributes_for :logs
end
- 이제 Keyword model에
logs_attributes=
method가 추가되었다. - Item controller의 strong parameter에 logs_attributes를 추가로 허용해 준다.
class ItemsController < ApplicationController
private
def item_params
params.require(:item).permit(keywords_attributes: [:id, :name, '_destroy', logs_attributes: [:count, :log_date]]))
end
end
app/views/items/edit.html.erb
에 keyword field를 추가한다.
<%= form_for @item do |f| %>
...
<%= f.fields_for :keywords, :wrapper => false do |builder| %>
<%= render 'keyword_fields', f: builder %>
<% end %>
:wrapper => false
는 기본적으로 생성되는<div class="fields">
를 없애준다.- table 내부에 추가할 때는 이 옵션을 사용한다.참고
app/views/items/_keyword_fields.html.erb
<%= f.text_field :name %>
<%= link_to_add_log f, object_id %>
<%= f.hidden_field :_destroy %>
<%= link_to "remove", "#", class: "remove_keyword" %>
- remove link를 클릭하면 javascript로 hidden_field인
_destroy
의 값을1
로 세팅하고 해당 form을 보이지 않게한다.app/assets/javascripts/items.js
$(document).on("turbolinks:load", function(){
$("form").on("click", ".remove_keyword", function(event){
$(this).prev('input[type=hidden]').val('1');
$(this).closest('tr').hide();
event.preventDefault();
});
});
app/helpers/items_helper.rb
에link_to_add_log
메소드를 추가했다. Log는 해당 keyword에 대해 3rd party API를 통해 얻은 data를 기록한다.
module ItemsHelper
def link_to_add_log(f)
new_object = Log.new
id = new_object.object_id
fields = f.fields_for(:logs, new_object, child_index: id) do |builder|
render("log_field", f: builder)
end
link_to("Add Log", '#', class: "update_nsa_data", data: {id: id, fields: fields.gsub("\n", "")})
end
end
- 미리 Log form을
Add Log
링크의 data attribute에 추가시켜둔다. javscript로 해당 링크를 클릭했을 때, data attribute에 있는 form을 dom에 붙여준다. child_index: id
로 keyword의 id가 아닌 Log instance의 id를 사용하게 한다. id는 임의로 object_id를 사용하고, 실제Add Log
링크를 눌러 Log form이 추가될 때, javascript 를 이용, 시간에 의해 생성된 unique id로 바꿔준다.app/views/items/_log_field.html.erb
<%= f.text_field :nmc %>
<%= f.text_field :npc %>
<%= f.text_field :log_date %>
app/assests/javascripts/items.js
$('form').on('click', '.update_nsa_data', function(e){
var update_btn = $(this)
event.preventDefault();
// add new nsa_dat form
var time = new Date().getTime();
var regexp = new RegExp($(this).data('id'), 'g');
$(this).after($(this).data('fields').replace(regexp, time));
// get 3rd party data and fill in input fields
});
Rails github - nested_attributes.rb RailsCast 196-nested-model-form