Forum: Ruby on Rails nested forms - how to use before/after destroy callback ?

Posted by Serguei Cambour (javix)
on 2013-03-14 20:14
(Received via mailing list)
How is it possible to use a callback and which one to avoid the 
destroying
of the last association.
For example, there are 3 following models:

#timesheet.rb
class Timesheet < ActiveRecord::Base
  has_many :activities, dependent: :destroy
  has_many :time_entries, through: :activities
  accepts_nested_attributes_for :activities, allow_destroy: true
end

#activity.rb
class Activity < ActiveRecord::Base
  has_many :time_entries, order: :workdate, dependent: :destroy
  accepts_nested_attributes_for :time_entries, allow_destroy: true,
reject_if: proc { |a| a[:worktime].blank? }
end

#time_entry.rb
class TimeEntry < ActiveRecord::Base
  belongs_to :activity
  validates :worktime, presence: true, inclusion: { in: [0.5, 1] }
end

Every timesheet is created for 7 days (tile_entries) for one activity in
the the same form. To delete I use the technic with jQuery explained at
Railscats:
#add_remove_fields.js

function remove_fields(link) {
  $(link).prev("input[type=hidden]").val("1");
  $(link).closest(".fields").hide();
}

function add_fields(link, association, content) {
  var new_id = new Date().getTime();
  var regexp = new RegExp("new_" + association, "g");
  $(link).parent().before(content.replace(regexp, new_id));
}

#application_helper.rb

def link_to_remove_fields(name, f)
    f.hidden_field(:_destroy) + link_to_function(name,
"remove_fields(this)")
  end

  def link_to_add_fields(name, f, association, timesheet)
...
end

I tried several ways with after_destroy hook, - it didn't work.
Any idea ? Thanks and regards.
Posted by Serguei Cambour (javix)
on 2013-03-19 15:31
(Received via mailing list)
So still no idea how to avoid the destroy of the last association. I do 
all
the processing in the 'update ' action of TimesheetsController. I tried
with 'after_update' hook in the Timsheet model as follows:

#timesheet.rb
class Timesheet < ActiveRecord::Base
  accepts_nested_attributes_for :activities, allow_destroy: true
  after_update :check_an_activity_present

  private
    def check_an_activity_present
      raise "You should have at least ONE activity present" if
activities.empty?
    end
end

An I put the update_attributes in the begin/rescue block in the 
controller:
#timesheets_controller.rb
class TimesheetsController < ApplicationController
def update
    begin
      @timesheet = current_user.timesheets.find(params[:id])
      if @timesheet.update_attributes(params[:timesheet])
        flash[:success] = 'Timesheet updated sucessfully'
        redirect_to @timesheet
      else
        load_entries
        render 'edit'
      end
    rescue Exception => e
      flash.now[:error] = e.message
      @entries = @timesheet.time_entries
      render 'edit'
    end

private
  def load_entries
      @entries = @timesheet.build_and_sort_time_entries
    end
  end
end

The problem is that the activity with corresponding time entries 
collection
is not destroyed even if I added a new activity with corresponding time
entries. So after the update executed, I have 2 activities saved with
corresponding time entries instad of having just the ONLY one, entered 
just
after the destroying the previous one. Any idea? Thank you.
Posted by Serguei Cambour (javix)
on 2013-03-19 21:52
(Received via mailing list)
Finally, after_update callback works as needed, te problem was in the 
new
fields_for generation. The below is the correct and updated version:

#timesheet.rb
> reject_if: proc { |a| a[:worktime].blank? }
>
    validate :one_time_entry_present
private
    def one_time_entry_present
      errors[:base] << "At least one entry should be entered" if
time_entries.empty?
    end

> Railscats:
>   var regexp = new RegExp(association_id, "g");
>  def link_to_add_fields(name, f, association, timesheet)
>     link_to_function(name, "add_fields(this, \"#{id}\",
> \"#{escape_javascript(fields)}\")")
>   end
>

#timesheets_controller.rb

def update
    begin
      @timesheet = current_user.timesheets.find(params[:id])
      if @timesheet.update_attributes(params[:timesheet])
        flash[:success] = 'Timesheet updated sucessfully'
        redirect_to @timesheet
      else
        load_entries
        render 'edit'
      end
    rescue Exception => e
      flash[:error] = e.message
      redirect_to edit_timesheet_path(@timesheet)
    end

  end

Hope this helps.
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.