title: What is causing the rollback when I try to update a record
body: <p>Not understanding why my record is not being updated. Where do I look? How do I debug?</p>
  <p>Here is the rollback message I see...</p>
  <pre><code>    66: def update #this is NOT working...use imported older version below
   =&gt; 67:   binding.pry
      68:   
      69:   @assignment.update(assignment_params)
      70: end
  
  [1] pry(#&lt;AssignmentsController&gt;)&gt; @assignment.update(assignment_params)
     (0.2ms)  BEGIN
    VolunteerShift Load (0.2ms)  SELECT  &quot;volunteer_shifts&quot;.* FROM &quot;volunteer_shifts&quot; WHERE &quot;volunteer_shifts&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 545496], [&quot;LIMIT&quot;, 1]]
    Contact Load (0.3ms)  SELECT  &quot;contacts&quot;.* FROM &quot;contacts&quot; WHERE &quot;contacts&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 137424], [&quot;LIMIT&quot;, 1]]
    VolunteerTaskType Load (0.3ms)  SELECT  &quot;volunteer_task_types&quot;.* FROM &quot;volunteer_task_types&quot; WHERE &quot;volunteer_task_types&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 41], [&quot;LIMIT&quot;, 1]]
    Roster Load (0.2ms)  SELECT  &quot;rosters&quot;.* FROM &quot;rosters&quot; WHERE &quot;rosters&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 7], [&quot;LIMIT&quot;, 1]]
    VolunteerEvent Load (0.2ms)  SELECT  &quot;volunteer_events&quot;.* FROM &quot;volunteer_events&quot; WHERE &quot;volunteer_events&quot;.&quot;id&quot; = $1 LIMIT $2  [[&quot;id&quot;, 52679], [&quot;LIMIT&quot;, 1]]
     (0.2ms)  ROLLBACK
  =&gt; false
  [2] pry(#&lt;AssignmentsController&gt;)&gt; exit
  
  </code></pre>
  <p>Here is the model...</p>
  <pre><code>class Assignment &lt; ApplicationRecord
    # attr_accessor :volunteer_event ,:contact_id #why is this here???
    # attr_accessor :redirect_to
    belongs_to :volunteer_shift, optional: true
    has_one :volunteer_task_type, :through =&gt; :volunteer_shift, :source =&gt; :volunteer_task_type
    belongs_to :contact ,optional: true
    #validates_presence_of :volunteer_shift #belongs_to takes care of this now
    validates_associated :volunteer_shift
    belongs_to :attendance_type
    belongs_to :call_status_type
    validates_presence_of :set_date, :if =&gt; :volshift_stuck #belongs_to takes care of this now??
  
    delegate :set_date, :set_date=, :to =&gt; :volunteer_shift
    delegate :set_description, :set_description=, :to =&gt; :volunteer_shift
  
    has_one :contact_volunteer_task_type_count, lambda{||
      {:conditions =&gt; 'contact_volunteer_task_type_counts.contact_id = #{defined?(attributes) ? contact_id : &quot;assignments.contact_id&quot;}', :through =&gt; :volunteer_shift, :source =&gt; :contact_volunteer_task_type_counts}
    }
    scope :date_range, lambda { |range|
      joins(volunteer_shift: :volunteer_event)
          .where(volunteer_shifts: { volunteer_events: { date: range } })
    }
    scope :is_after_today, lambda {||
      { :conditions =&gt; ['(SELECT date FROM volunteer_events WHERE id = (SELECT volunteer_event_id FROM volunteer_shifts WHERE id = assignments.volunteer_shift_id)) &gt; ?', Date.today] }
    }
    scope :on_or_after_today, lambda {||
      { :conditions =&gt; ['(SELECT date FROM volunteer_events WHERE id = (SELECT volunteer_event_id FROM volunteer_shifts WHERE id = assignments.volunteer_shift_id)) &gt;= ?', Date.today] }
    }
    scope :not_cancelled, -&gt; { where('(attendance_type_id IS NULL OR attendance_type_id NOT IN (SELECT id FROM attendance_types WHERE cancelled = \'t\'))')}
    scope :roster_is_limited_by_program, -&gt; {where(&quot;roster_id IN (SELECT id FROM rosters WHERE limit_shift_signup_by_program = 't')&quot;).joins(:volunteer_shift)}
  
    def real_programs
      return [] unless self.volunteer_shift&amp;.roster
      return [] unless self.volunteer_shift.roster.limit_shift_signup_by_program
      self.volunteer_shift.roster.skeds.select{|x| x.category_type == &quot;Program&quot;}.map{|x| x.name}
    end
  
    def contact_id=(newval)
      self.write_attribute(:contact_id, newval)
      self.contact = Contact.find_by_id(newval.to_i)
    end
  
    def contact_id_and_by_today
      # Unless the contact id is empty, or the event date is after today.
      !(contact_id.nil? || self.volunteer_shift.volunteer_event.date &gt; Date.today)
    end
  
  # TODO: find all time_range_s methods and either pull out to DRY or give unique names
    def time_range_s
      return &quot;&quot; unless start_time and end_time
      (start_time.strftime(&quot;%I:%M&quot;) + ' - ' + end_time.strftime(&quot;%I:%M&quot;)).gsub( ':00', '' ).gsub( ' 0', ' ').gsub( ' - ', '-' ).gsub(/^0/, &quot;&quot;)
    end
  
    def description
      return unless volunteer_shift
      self.volunteer_shift.volunteer_event.date.strftime(&quot;%D&quot;) + &quot; &quot; + self.time_range_s + &quot; &quot; + self.slot_type_desc
    end
  
    def roster_title
      return unless volunteer_shift
      self.volunteer_shift.roster.name
    end
  
    def date
       return unless volunteer_shift
       volunteer_shift.date
    end
  
    #full calendar uses this method name....see the assignment.json.jbuilder
    def event_date
      return unless volunteer_shift
      self.date
    end
  
    def first_time_in_area?
      if self.contact and self.volunteer_shift and self.volunteer_shift.volunteer_task_type
        return !ContactVolunteerTaskTypeCount.has_volunteered?(self.contact_id, self.volunteer_shift.volunteer_task_type_id)
      else
        return false
      end #  and self.contact_id_changed? moved outside because we use update_attributes
    end
  
    def slot_type_desc
      b = (self.volunteer_shift.volunteer_task_type_id.nil? ? self.volunteer_shift.volunteer_event.description : self.volunteer_shift.volunteer_task_type.description)
      b = b + &quot; (#{self.volunteer_shift.description})&quot; if self.volunteer_shift.description and self.volunteer_shift.description.length &gt; 0
      b
    end
  
    def display_name
      ((!(self.volunteer_shift.description.nil? or self.volunteer_shift.description.blank?)) ? self.volunteer_shift.description + &quot;: &quot; : &quot;&quot;) + self.contact_display
    end
  
    def cancelled?
      (self.attendance_type&amp;.cancelled)
    end
  
    def attended?
      (self.attendance_type and !self.attendance_type.cancelled)
    end
  
    def contact_display
      if self.closed
        &quot;(closed)&quot;
      elsif contact_id.nil?
        return &quot;(available)&quot;
      else
        self.contact.display_name + &quot;(#{self.voltask_count})&quot;
      end
    end
  
    before_validation :set_values_if_stuck
    def set_values_if_stuck
      return unless (volshift_stuck || volunteer_shift)
      volunteer_shift.set_values_if_stuck(self)
    end
  
    after_destroy { |record| if record.volunteer_shift&amp;.stuck_to_assignment; record.volunteer_shift.destroy; else VolunteerShift.find_by_id(record.volunteer_shift_id).fill_in_available; end}
    after_save {|record| if record.volunteer_shift&amp;.stuck_to_assignment; record.volunteer_shift.save; end}
    after_save { |record| VolunteerShift.find_by_id(record.volunteer_shift_id).fill_in_available }
  
    scope :for_contact, lambda{|assignment|
      tcid = assignment.contact.id
      { :conditions =&gt; ['contact_id = ?', tcid] }
    }
  
    def volunteer_shift_attributes=(attrs)
      return unless volunteer_shift
      self.volunteer_shift.attributes=(attrs) # just pass it up
    end
  
    def volshift_stuck
      return unless volunteer_shift
      self.volunteer_shift&amp;.stuck_to_assignment
    end
  
  #for fullcalendar
    def all_day_event?
      self.start_time == self.start_time.midnight &amp;&amp; self.end_time == self.end_time.midnight ? true : false
    end
  end
  
  </code></pre>
  <p>Here is the entire controller</p>
  <pre><code>class AssignmentsController &lt; ApplicationController
    before_action :set_assignment, only: [:show, :edit, :update, :destroy]
    skip_before_action :verify_authenticity_token #TODO refactor this line to be very specific
  
    # GET /assignments or /assignments.json
    def index
      # @assignments = Assignment.limit(20)
      # @assignments = Assignment.where(start: params[:start]..params[:end])
      @assignments = Assignment.date_range(params[:start]..params[:end])
  
    end
  
    # GET /assignments/1 or /assignments/1.json
    def show
    end
  
    # GET /assignments/new
    def new
      # @assignment = Assignment.new
      add_shift
      # @assignment.volunteer_shift.build
    end
  
    # GET /assignments/1/edit &lt;-- ORIGINAL
    # def edit
    # end
  
    def edit #dumb &quot;stuff&quot; from older app
      logger.debug &quot;assignments_controller EDIT called&quot;
      if @assignment
        @assignments = [@assignment]
      else
        begin
          @assignments = params[:id].split(&quot;,&quot;).map{|x| Assignment.find(x)}
          @assignment = @assignments.first
        rescue
          flash[:error] = $!.to_s
          redirect_skedj(request.env[&quot;HTTP_REFERER&quot;], &quot;&quot;)
          return
        end
      end
      @referer = request.env[&quot;HTTP_REFERER&quot;]
      @my_url ||= {:action =&gt; &quot;update&quot;, :id =&gt; params[:id]}
      render :action =&gt; 'edit'
    end
  
    # POST /assignments or /assignments.json
    def create
      create_shift
      # @assignment = Assignment.new(assignment_params)
      #
      # # error wants contact.id not contact_id ???
      #
      # respond_to do |format|
      #   if @assignment.save
      #     format.html { redirect_to @assignment, notice: &quot;Assignment was successfully created.&quot; }
      #     format.json { render :show, status: :created, location: @assignment }
      #   else
      #     format.html { render :new, status: :unprocessable_entity }
      #     format.json { render json: @assignment.errors, status: :unprocessable_entity }
      #   end
      # end
    end
  
    # PATCH/PUT /assignments/1 or /assignments/1.json
    def update #this is NOT working...use imported older version below
      binding.pry
      @assignment.update(assignment_params)
    end
  
  
    def update_from_old_code #from original garbage code
      logger.debug &quot;assignments_controller UPDATE called&quot;
      unless params[:assignment]
        redirect_to :action =&gt; &quot;index&quot;
        return
      end
      @my_url = {:action =&gt; &quot;update&quot;, :id =&gt; params[:id]}
      last_id = nil
      begin
        # is @assignments broken here??
        @assignments = params[:id].split(&quot;,&quot;).map{|x| last_id = x; Assignment.find(x)}
        # binding.pry
      rescue ActiveRecord::RecordNotFound
        flash[:jsalert] = &quot;The assignment (##{last_id.to_i.inspect}) seems to have disappeared or never existed. It is possible somebody else has modified or deleted it.&quot;
        rt = params[:assignment].delete(:redirect_to)
        redirect_skedj(rt, &quot;&quot;)
        return
      end
      # binding.pry
      lv = params[&quot;lock_versions&quot;]
      ac = params[&quot;assigned_contacts&quot;] || {}
      @assigned_contacts = []
      @replaced_contacts = []
      ret = true #WTF why???
      #500 error caused by ??
      @assignments.each do |as|
        as.lock_version = lv[as.id.to_s]
        # binding.pry
        if as.lock_version_changed?
          binding.pry
          as.errors.add(&quot;lock_version&quot;, &quot;is stale for this assignment, which means it has been edited by somebody else since you opened it, please try again&quot;)
          ret = false
        end
        if as.contact_id &amp;&amp; as.contact_id.to_s != params[:assignment][:contact_id].to_s
          binding.pry
          @assigned_contacts &lt;&lt; as.contact
          unless ac[as.contact_id.to_s] &amp;&amp; ac[as.contact_id.to_s] == &quot;replace&quot;
            as.errors.add(&quot;contact_id&quot;, &quot;has been changed, please confirm below that the volunteer who is already assigned to the shift should be removed&quot;)
            ret = false
          else
            @replaced_contacts &lt;&lt; as.contact_id
          end
        end
      end
      rt = params[:assignment].delete(:redirect_to)
  
      js_alert = nil
      # binding.pry
      if ! ret
        @assignment = Assignment.new
        @assignment.volunteer_shift = @assignments.first.volunteer_shift
        @assignment.attributes=(params[:assignment]) # .. ? .delete(&quot;volunteer_shift_attributes&quot;)
      end
      # binding.pry
      @assignments.each{|x|
        # binding.pry
        if ret #what does ret mean???
          @assignment = x
          bc = x.contact_id
          binding.pry
          ret = !!(x.update(params[:assignment])) #TODO not working...what is &quot;!!&quot; here? THIS IS THE BREAKING LINE
          binding.pry
          # how to say if updated then true ??
          if ret
            binding.pry
            flash[:notice] = 'Assignment was successfully updated.'
          else
            binding.pry
            render :action =&gt; &quot;edit&quot;
          end
          # when are the values x updated?
        end
      }
    end
  
    # DELETE /assignments/1 or /assignments/1.json
    def destroy #this is working
      @assignment.destroy
      # NOTE: comment original out 4 now
      # respond_to do |format|
      #   format.html { redirect_to assignments_url, notice: &quot;Assignment was successfully destroyed.&quot; }
      #   format.json { head :no_content }
      # end
    end
  
    private
      # Use callbacks to share common setup or constraints between actions.
      def set_assignment
        @assignment = Assignment.find(params[:id])
      end
  
      # Only allow a list of trusted parameters through.
      def assignment_params
        #fixme: ,volunteer_shift_attributes: [:???, :???, :???] &lt;--- insert this below?
              params.require(:assignment).permit(:title, :set_date, :date, :date_range, :volunteer_shift_id, :contact_id, :start_time, :end_time, :start, :end, :attendance_type_id, :notes, :call_status_type_id, :closed, :lock_version, :color, :description, volunteer_shift_attributes: [:volunteer_task_type_id,:roster_id,:program_id,:set_description,:set_date,:id,:destroy])
        # params.require(:assignment).permit(:title, :set_date, :date_range, :contact_id, :start_time, :end_time, :start, :end, :attendance_type_id, :notes, :call_status_type_id, :closed, :lock_version, :color, volunteer_shift_attributes: [:volunteer_task_type_id,:roster_id,:program_id,:set_description,:set_date,:id,:destroy])
      end
  end
  
  </code></pre>
  <p>Any help is appreciated</p>
  <p>Thank you for your time.</p>