Witam.
Mam problem z utworzeniem dobrego sposobu na dynamicznie tworzony formularz gdzie wraz z obiektem tworzę jego relacje many_to_many. Mianowicie jeśli dane nie przejdą walidacji formularz jest wyświetlany bez danych dla relacji.
Zapoznałem się z http://railscasts.com/episodes/196-nested-model-form-part-1?autoplay=true i drugą cześcią. Ale z tego co wyczytałem jest to przestarzałe. Doszedłem więc do rozwiązana z link_to: remote: true ale kod przy takim rozwiązaniu jest po prostu brzydki (swoją drogą, że najpierw chce zmusić go do poprawnego działania a potem zająłbym sie poprawianiem aby to wyglądało). Czyli tak. najpierw metoda get_data_sets_params musi zwracać mi poprawnie czego nie robi, a następnie metoda create w przypadku gdy dane nie przejdą walidacji ma mi poprawnie wyświetlic formularz. Dodatkowo przydałyby się porada jak poradzić sobie z usuwaniem relacji (wnioskuję, że pomysł z polem _destroy jest do bani bo powoduje powtarzanie sie ID dla pól.)
Moje modele
class DataSet < ActiveRecord::Base
self.table_name = :data_sets
has_many :data_set_synch_agents, inverse_of: :data_set, dependent: :destroy
has_many :data_set_users, inverse_of: :data_set, dependent: :destroy
has_many :users, inverse_of: :data_sets, through: :data_set_users
has_many :synch_agents, inverse_of: :data_sets, through: :data_set_synch_agents
validates_presence_of :configuration_id
validates_length_of :configuration_id, in: 3..100
validates_numericality_of :reports_synch_interval_min, greater_than_or_equal_to: 0, only_integer: true, allow_nil: true
validates_numericality_of :max_packages_on_server, greater_than: 0, only_integer: true, allow_nil: true
accepts_nested_attributes_for :data_set_synch_agents, :reject_if => lambda { |a| a[:synch_agent_id].blank? }, :allow_destroy => true
accepts_nested_attributes_for :data_set_users, :reject_if => lambda { |a| a[:user_id].blank? }, :allow_destroy => true
...
end
class DataSetSynchAgent < ActiveRecord::Base
self.table_name = :data_set_synch_agent
self.primary_keys = :data_set_id, :synch_agent_id, :max_idle_on_data_set
belongs_to :synch_agent
belongs_to :data_set, inverse_of: :data_set_synch_agents, foreign_key: :data_set_id
validates_numericality_of :max_idle_on_data_set, greater_than_or_equal_to: 0, only_integer: true
validates_numericality_of :synch_agent_id, greater_than: 0, only_integer: true
end
class SynchAgent < ActiveRecord::Base
belongs_to :exchange_server, inverse_of: :synch_agents
has_many :data_set_synch_agents, inverse_of: :synch_agent, dependent: :restrict_with_error
has_many :synchronization_reports, dependent: :nullify
has_many :data_sets, inverse_of: :synch_agents, through: :data_set_synch_agents, dependent: :restrict_with_error
...
end
class DataSetUser < ActiveRecord::Base
belongs_to :data_set, inverse_of: :data_set_users
belongs_to :user, inverse_of: :data_set_users
validates_inclusion_of :send_alerts, in: [true, false], default: true
end
class User < ActiveRecord::Base
include User::Roles
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :data_set_users, inverse_of: :user, dependent: :delete_all
has_many :data_sets, inverse_of: :users, through: :data_set_users
validates_length_of :name, in: 3..32
end
Mój formularz, ktory chcę rozszerzać
.row
= bootstrap_nested_form_for @data_set do |f|
= f.text_field :configuration_id
= f.number_field :max_packages_on_server
= f.number_field :reports_synch_interval_min
.row
#users.col-xs-12
= link_to t('general.add'), data_set_add_field_path(name: :users), class: 'btn btn-link', remote: true
.row
= f.fields_for :data_set_users do |ff|
.col-xs-8
= ff.select :user_id, User::FormParameters.get_users_select_list_values
.col-xs-2
= ff.check_box :send_alerts
.col-xs-2
= f.hidden_field(:_destroy)
= link_to data_set_remove_field_path, class: 'btn btn-link', remote: true, alt: t('general.remove'), class: "btn btn-lg" do
= fa_icon :remove
#synch_agents.col-xs-12
= link_to t('general.add'), data_set_add_field_path(name: :synch_agents), class: 'btn btn-link', remote: true
.row
= f.fields_for :data_set_synch_agents do |ff|
.col-xs-8
= ff.select :synch_agent_id, SynchAgent::FormParameters.get_synch_agents_select_list_values, { include_blank: true }
.col-xs-2
= ff.number_field :max_idle_on_data_set
.col-xs-2
= ff.hidden_field :_destroy
= link_to data_set_remove_field_path, class: 'btn btn-link', remote: true, alt: t('general.remove'), class: "btn btn-lg" do
= fa_icon :remove
= f.submit t("general.save"), class: "btn btn-primary i4b-btn-block-xs"
= link_to t("general.cancel"), data_sets_path( data_set: params[:params], sort: params[:sort], page: params[:page] ), class: "btn btn-default i4b-btn-block-xs"
Kontroler, w którym a się wszystko odbywać
class DataSetsController < ApplicationController
expose(:search_data_set) { DataSet.new_search(params) }
expose(:data_sets) { DataSet::Search.search(params) }
def new
@data_set = DataSet.new
@data_set.data_set_users.build
@data_set.data_set_synch_agents.build
end
def create
respond_to do |format|
@data_set = DataSet.new(get_data_set_params)
byebug
if @data_set.save
format.html {
redirect_to data_sets_path,
notice: t(
"general.models.create_object_message",
name: DataSet.model_name.human.downcase
)
}
else
format.html { render action: :new }
end
end
end
def destroy
data_set = DataSet.find(params.require(:id))
data_set.destroy
attrs = { flash: {} }
if data_set.errors.any?
attrs[:flash][:error] = data_set.errors.full_messages().join(". ") + "."
else
attrs[:notice] = I18n.t(
"general.models.destroy_success_message",
removed_object_name: DataSet.model_name.human
)
end
respond_to do |format|
format.html {
redirect_to(
data_sets_path,
attrs
)
}
end
end
def remove_field; end
def add_field
@select_id = "data_set_data_set"
@value = Time.now().strftime("%Y_%m_%d_%H_%M_%S")
@name = "data_set"
if params[:name] == "synch_agents"
@label = SynchAgent.human_attribute_name('send_alert')
@select_values = SynchAgent::FormParameters.get_synch_agents_select_list_values
@include_blank = true
@synch_agent = true
@select_id += "_synch_agents_attributes_#{@value}_synch_agent_id"
@name += "[data_set_synch_agents_attributes][#{@value}][synch_agent_id]"
else
@label = DataSet.human_attribute_name('user_id')
@select_values = User::FormParameters.get_users_select_list_values
@include_blank = false
@synch_agent = false
@select_id += "_users_attributes_#{@value}_user_id"
@name += "[data_set_users_attributes][#{@value}][user_id]"
end
respond_to do |format|
format.js {}
end
end
private
def get_data_set_params
params.require(:data_set)
.permit(
:configuration_id,
:max_packages_on_server,
:reports_synch_interval_min,
data_set_users_attributes: [:user_id, :send_alerts, :_destroy],
data_set_synch_agents_attributes: [:synch_agent_id, :max_idle_on_data_set, :_destroy]
)
end
end
Widoki związane z metodą add_field
add_field.js.erb
$('#<%=j params[:name] %>').append('<%=j render partial: "data_sets/partials/add_field", locals: { _label: @label, select_values: @select_values , include_blank: @include_blank, synch_agent: @synch_agent, _value: @value, _select_id: @select_id, _name: @name } %>')
_add_field_html.haml
.row
.fields
.col-xs-8
.form-group
= label_tag :label, _label, for: _select_id
= select_tag _select_id, options_for_select(select_values), include_blank: include_blank, class: 'form-control', name: _name
.col-xs-2
- if synch_agent
= label_tag DataSetSynchAgent.human_attribute_name('max_idle_on_data_set')
= number_field_tag :max_idle_on_data_set, 0, class: 'form-control', id: "data_set_data_set_synch_agents_attributes_#{_value}_max_idle_on_data_set", name: "data_set[data_set_synch_agents_attributes][#{_value}][max_idle_on_data_set]"
- else
.checkbox
%label{ for: "data_set_data_set_users_attributes_#{_value}_send_alerts" }
%input{ type: "hidden", value: _value, name: "data_set[data_set_users_attributes][#{_value}][send_alerts]" }
%input{ type: "checkbox", value: "1", checked: "checked", id: "data_set_data_set_users_attributes_#{_value}_send_alerts", name: "data_set[data_set_users_attributes][#{_value}][send_alerts]" }
= DataSetUser.human_attribute_name('send_alerts')
.col-xs-2
= hidden_field_tag("data_set[_destroy]")
= link_to data_set_remove_field_path, class: 'btn btn-link', remote: true, alt: t('general.remove'), class: "btn btn-lg" do
= fa_icon :remove
Zawartość params, które jest poprawne ale nie przechodzi przez metodę get_data_set_params
{"utf8"=>"✓", "authenticity_token"=>"Jji9Y/xLRMgGcPzjfLnbPPeH51uP9yqna3ZC/1r9+GsVsSV5hsslxR0sh4RkFpU0wBBdW38GJf1L77l9FkP3Og==", "data_set"=>{"configuration_id"=>"", "max_packages_on_server"=>"", "reports_synch_interval_min"=>"", "data_set_users_attributes"=>{"0"=>{"user_id"=>"1", "send_alerts"=>"1"}, "2015_09_09_11_43_36"=>{"user_id"=>"2", "send_alerts"=>"1"}}, "_destroy"=>"", "data_set_synch_agents_attributes"=>{"0"=>{"synch_agent_id"=>"1", "max_idle_on_data_set"=>"0", "_destroy"=>"false"}, "2015_09_09_11_43_39"=>{"synch_agent_id"=>"2", "max_idle_on_data_set"=>"0"}}}, "commit"=>"Save", "controller"=>"data_sets", "action"=>"create"}
Będę wdzięczny za wszelką pomoc.
Pozdrawiam.