I'm revising this question again because I solved a majority of the issues. The current bug that I could use some help with is that when I remove an object using the technique described below, I end up with orphaned database rows that should be getting deleted. Details below.
I'm using the Ryan Bates technique from Advanced Rails recipes to dynamically add and remove elements on a multi-model form. http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf
.
I have 3 models: users, schedules and markets.
Users
has_many :schedules
has_many :markets, :through => :schedules
Markets
has_many :schedules
has_many :users, :through => :schedules
Schedules
belongs_to :users
belongs_to :markets
Schedules has columns user_id
and market_id
, but also has additional columns: monday, tuesday, wednesday, thursday, friday, saturday, sunday. These are booleans.
All the editing in my app happens from the user model. On the user#edit view, I'm showing the user a dropdown for each of his existing markets and for each market, I'm rendering 7 select boxes with options 'true' and 'false' representing a boolean for each day of the week. Each market selected and its associated selected dates are saved as a "schedule" record in the schedules table. The row in that table contains a user_id
, a market_id
, and each selected day of the week as true. The problem is that when a user removes a market from the edit page, then that schedule row should be destroyed. But this is not happening.
Instead, when I click the "remove" link for a given schedule, and I hit update, the user_id
and market_id
are properly deleted from the schedules table. However, the record itself is not deleting and the boolean fields representing the days of the week remain as well.
The following is my setup. You'll see that I'm following Ryan's tutorial very closely.
User#Edit view:
<%= error_messages_for :user %>
<% form_for @user do |f| %>
<%= add_schedule_link "+ Add another market" %>
<div id="schedules">
<%= render :partial => 'schedule', :collection => @user.schedules %>
</div>
<%= f.submit 'Update My Profile' %>
<% end %>
User#_schedule.html.erb
<div class="schedule">
<% new_or_existing = schedule.new_record? ? 'new' : 'existing' %>
<% prefix = "user[#{new_or_existing}_schedule_attributes][]" %>
<% fields_for prefix, schedule do |schedule_form| -%>
<%= error_messages_for :schedule, :object => schedule %>
<p><%= schedule_form.collection_select :market_id, Market.all, :id, :name, {:prompt => true} %></p>
<p><%= link_to_function "- Remove Market", "$(this).up('.schedule').remove()" %></p>
<%= schedule_form.select :monday, [['No', false], ['Yes', true]] %>
<%= schedule_form.select :tuesday, [['No', false], ['Yes', true]] %>
<%= schedule_form.select :wednesday, [['No', false], ['Yes', true]] %>
<%= schedule_form.select :thursday, [['No', false], ['Yes', true]] %>
<%= schedule_form.select :friday, [['No', false], ['Yes', true]] %>
<%= schedule_form.select :saturday, [['No', false], ['Yes', true]] %>
<%= schedule_form.select :sunday, [['No', false], ['Yes', true]] %>
<% end -%>
</div>
User Model:
validates_associated :schedules, :on => :update
after_update :save_schedules
accepts_nested_attributes_for :schedules, :allow_destroy => :true,
:reject_if => :all_blank
def new_schedule_attributes=(schedule_attributes)
schedule_attributes.each do |attributes|
schedules.build(attributes)
end
end
def existing_schedule_attributes=(schedule_attributes)
schedules.reject(&:new_record?).each do |schedule|
attributes = schedule_attributes[schedule.id.to_s]
if attributes
schedule.attributes = attributes
else
schedules.delete(schedule)
end
end
end
def save_schedules
schedules.each do |schedule|
schedule.save(false)
end
end
User Controller
def new
@user = User.new
end
def create
cookies.delete :auth_token
@user = User.new(params[:user])
@user.save!
flash[:notice] = "Thanks for signing up! Please check your email to activate your account before logging in."
redirect_to login_path
rescue ActiveRecord::RecordInvalid
flash[:error] = "There was a problem creating your account."
render :action => 'new'
end
def edit
@user = current_user
end
def update
params[:user][:existing_schedule_attributes] ||= {}
params[:user][:existing_season_attributes] ||= {}
@user = User.find(current_user)
if @user.update_attributes(params[:user])
flash[:notice] = "Your information has been updated."
redirect_to :action => 'show', :id => current_user
else
render :action => 'edit'
end
end
Helpers: users_helper.rb
module UsersHelper
def add_schedule_link(name)
link_to_function name do |page|
page.insert_html :bottom, :schedules, :partial => 'schedule', :object => Schedule.new
end
end
end