Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

accepts_nested_attributes_for and attr_accessor

#4944

I have a model that has one attr_accessor, When that gets updated through a nested_attributes form, it does not pick up the change to the attr_accessor, and does not run any of the callbacks.

I have to edit another attribute that is actually part of the model, for the callbacks to occur.

For example:

class Resource < ActiveRecord::Base
  # ...
  has_many :items
  
  accepts_nested_attributes_for :items, :allow_destroy => true
end
class Item < ActiveRecord::Base
  belongs_to :resource
  
  before_validation :clear_if_available
  
  attr_accessor :unavailable

  def clear_if_available
    if @unavailable == 'yes'
      # ...
    end
  end
end

# Resource.rb

=> r = Resource.new
#<Resource id: 1, quantity: 1, maximum_capacity: 10, name: "Lolcat", supplier_id: 1, created_at: "2010-06-22 15:09:57", updated_at: "2010-06-22 15:09:57", category: "other">

=> Item.first
#<Item id: 1, name: "Lolcat Item", resource_id: 1, out_from: nil, out_to: nil, created_at: "2010-06-22 15:09:57", updated_at: "2010-06-23 09:30:36", mobile_number: nil, email: nil, link_id: 1, points: 0, unavailable: false>

=> r.update_attributes(:items_attributes => { :id => 1, :name => "Lolcat changed, :unavailable => 'yes' })
true # This does the callbacks and does my update

=> r.update_attributes(:items_attributes => { :id => 1, :unavailable => 'yes' })
true # This ignores my update to unavailable and does nothing

This used to work in Rails 2.3.5 now not working in Rails 2.3.8?

Reported by ryanza · June 23rd, 2010 @ 11:18 AM

State: resolved
Milestone: none
Assigned to: nobody
Importance: Low

Activity

  1. Neeraj Singh
    Neeraj Singh

    If I understand you correctly then attribute "unavailable" is not a column on Item table. In that case r.update_attributes(:items_attributes => { :id => 1, :unavailable => 'yes' }) has nothing to do.

    Am I missing something here?

    June 26th, 2010 @ 05:22 AM

  2. Brad Crawford
    Brad Crawford
    • Importance changed from to Low

    You are missing the fact that in 2.3.5, this worked. :)

    July 7th, 2010 @ 08:21 AM

  3. Brad Crawford
    Brad Crawford

    Ryanza, I am having the same issue but after creating a blank 2.3.8 project it no longer occurs. I am thinking that it is probably a gem/plugin issue. I will try and track it down and get back to you.

    July 7th, 2010 @ 08:31 AM

  4. ryanza
    ryanza

    Neeraj Singh,

    Its not firing off any of the hooks in the Item model, where it used to in 2.3.5 (on update_attributes and saves). I do understand that the way it is now (2.3.8) should actually be the correct response (do nothing as nothing is being modified), but it is breaking my old functionality, is there a workaround? :)

    Brad Crawford,

    I saw alot of code was changed/refactored in the accepts_nested_attributes_for method, I haven't had enough time to actually dig through it, I'll pull out a diff tonight and see if i can spot anything.

    Ill try test this in a new Rails 2.3.8 project; makes no sense that it would work?

    Thanks :)

    July 7th, 2010 @ 08:45 AM

  5. Neeraj Singh
    Neeraj Singh

    @ryanza can you show us full implementation of method clear_if_available?

    July 7th, 2010 @ 11:18 AM

  6. ryanza
    ryanza

    Sure:

      def clear_dates_if_available
        if self.unavailable == false || !self.book_out_expired?
          self.out_from, self.out_to = nil, nil
        end
      end
    

    July 7th, 2010 @ 11:21 AM

  7. Brad Crawford
    Brad Crawford

    Ok I might have spoken too soon, it doesn't actually work for a blank 2.3.8 app. I haven't looked into the code but as you said, none of the callbacks are being executed on the nested model when update_attributes is called on the parent after setting an attr_accessor attribute on the nested model. It is not dirty I guess. Any ideas for a workaround? 2.3.8 is a no go until I find one.

    July 7th, 2010 @ 11:42 AM

  8. ryanza
    ryanza

    I thought it had to do with dirty attributes, so i tried to fake it by writing my own dirty methods for the attr_accessor, and had no luck there either.. Hopefully some sort of solution is figured out for this, as its not REALLY a bug..

    July 7th, 2010 @ 12:03 PM

  9. ryanza
    ryanza

    Maybe "accepts_nested_attributes_for :items, :ignore_dirty => true" or something similar?

    July 7th, 2010 @ 12:37 PM

  10. Neeraj Singh
    Neeraj Singh

    Is it true that in the example that is mentioned at the very top you are changing :unavailable from false to 'yes'; from boolean to string?

    July 7th, 2010 @ 12:48 PM

  11. ryanza
    ryanza

    Neeraj, Yes I changed it from 'yes'/'no' to true/false

    July 7th, 2010 @ 12:52 PM

  12. Neeraj Singh
    Neeraj Singh

    Not able to reproduce the problem in rails edge. I am getting the callbacks.

    Here is what I did

    ActiveRecord::Schema.define(:version => 20100716030954) do
      create_table "questions", :force => true do |t|
        t.text    "name"
        t.integer "survey_id"
      end
      create_table "surveys", :force => true do |t|
        t.string "name"
      end
    end
    class Question < ActiveRecord::Base
      belongs_to :survey
    
      before_validation :doit
    
      def doit
        puts 'done' * 20
      end
    
      attr_accessor :sex
    end
    class Survey < ActiveRecord::Base
    
      has_many :questions, :dependent => :destroy
    
      accepts_nested_attributes_for :questions, :allow_destroy => true
    
      validates_presence_of :name
    
      def self.lab
        survey = Survey.create!(:name => "Current Trend in Programming")
        question = survey.questions.create!(:name => "Java", :sex => 'a')
        survey.update_attributes({:name => 'hello', :questions_attributes => [{:id => question.id, :sex => 'b' }]})
      end
    end
    

    July 16th, 2010 @ 04:19 AM

  13. Andreas Mayer
    Andreas Mayer

    Can confirm this for Rails 3 Beta 4

    July 25th, 2010 @ 04:09 PM

  14. Andreas Mayer
    Andreas Mayer

    Solved it using

    class TutorialStep < ActiveRecord::Base
      belongs_to :tutorial
      attr_accessor :download_remove
    
    ...
    
      def download_remove=(remove)
        self.download = nil if remove == '1'
      end
    end
    

    which doesn't require a callback and solves the dirty issue because setting download_remove changes download and makes the record dirty (because the download shall be removed)

    July 26th, 2010 @ 05:17 PM

  15. Neeraj Singh
    Neeraj Singh
    • State changed from new to resolved

    awesome. closing it.

    July 26th, 2010 @ 05:28 PM

  16. matthew.willhite (at gmail)
    matthew.willhite (at gmail)

    I am having the same issue as the original post.

    # page.rb
    class Page < ActiveRecord::Base
      has_many :page_elements, :dependent => :destroy
      accepts_nested_attributes_for :page_elements, :allow_destroy => true
    end
    
    # page_element.rb
    class PageElement < ActiveRecord::Base
      belongs_to :page
      belongs_to :media, :polymorphic => true
        
      attr_accessor :media_attributes
    
      after_update :update_media  
        
      def update_media
        self.media.update_attributes(media_attributes)
      end
      
    end
    
    Page.find(2).update_attributes(:page_elements_attributes => [{ :id=>3, :scale => 2, :media_attributes => { :source => "iphone"} }])
    

    (This will trigger the page element callbacks because an actual (non-virtual) attribute is being modified)

    Page.find(2).update_attributes(:page_elements_attributes => [{ :id=>3, :media_attributes => { :source => "test15"} }])
    

    (This will not trigger the page element callbacks because only the virtual attribute is being modified)

    Is this the intended behavior? If not, I think this ticket needs to be reopened.

    Using Rails 3.0.1, Ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]

    October 29th, 2010 @ 07:02 PM