#706 ✓resolved
Balint Erdi

[PATCH] all_valid? goes into infinite loop on self-referencing many-to-many associations

Reported by Balint Erdi | December 10th, 2008 @ 11:30 AM | in 1.0.0

I have a User resource and a Recommendation resource. A user can recommend many users and can be recommended by many users; a self-referential many-to-many relationship. See here in more detail:

http://www.pastie.org/335894

If I am adding a recommendation that is not valid (e.g the user recommends himself), then calling all_valid? on a user object goes into an infinite loop. (also see the pastie)

I investigated the case and that's because when the recommendation of the user object is checked for validity, it has a @collection instance variable whose value is the recommendation itself, contained in a list, so it will recursively call the recursive_valid? function with the same values.

I am trying to put together a failing spec and eventually I may come up with a fix.

Comments and changes to this ticket

  • Balint Erdi

    Balint Erdi December 12th, 2008 @ 09:36 AM

    • Tag set to dm-validations, validations

    Here goes the failing spec. If anyone could take a quick look at it and confirm that all_valid? was meant to be used this way and this is actually a bug it would be fine.

    Otherwise I'll proceed and try to implement the actual fix.

  • Balint Erdi

    Balint Erdi December 12th, 2008 @ 10:15 AM

    • Title changed from “all_valid? goes into infinite loop on self-referencing many-to-many associations” to “[PATCH] all_valid? goes into infinite loop on self-referencing many-to-many associations”

    See my patch that fixes the issue attached.

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) January 8th, 2009 @ 05:13 AM

    • State changed from “unconfirmed” to “accepted”
    • Assigned user set to “Dan Kubb (dkubb)”
  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) June 7th, 2009 @ 04:01 AM

    • State changed from “accepted” to “unconfirmed”
    • Assigned user changed from “Dan Kubb (dkubb)” to “Michael Klishin (antares)”
  • Martin Gamsjaeger (snusnu)

    Martin Gamsjaeger (snusnu) July 9th, 2009 @ 04:39 PM

    • State changed from “unconfirmed” to “confirmed”

    I extracted a standalone script from the pastie and adapted it to work with current next branch (I also had to put the validates_with_block call into Recommendation instead of User as shown in the pastie). As can be seen below, I can confirm that this is still an issue with 0.10.

    require "rubygems"
    require "dm-core"
    require "dm-validations"
    
    DataMapper::Logger.new(STDOUT, :debug)
    DataMapper.setup(:default, "sqlite3::memory:")
    
    
    class User
      include DataMapper::Resource
    
      property :id, Serial
    
      has 0..n, :recommendations
      has 0..n, :recommendees,   :through => :recommendations, :model => "User", :target_key => [:user_id]
      has 0..n, :recommended_by, :through => :recommendations, :model => "User", :target_key => [:recommendee_id], :via => :user
    
    end
    
    class Recommendation
      include DataMapper::Resource
    
      property  :user_id,        Integer, :key => true
      property  :recommendee_id, Integer, :key => true
    
      belongs_to :user
      belongs_to :recommendee, 'User', :source_key => [:recommendee_id]
      
      validates_with_block :recommendee_id do
        if @recommendee_id == @user_id
          [false, "A user can not recommend himself"]
        else
          true
        end
      end
    end
    
    DataMapper.auto_migrate!
    
    u = User.create
    
    u = User.get(u.id)
    u.recommendations
    u.recommendations.create(:recommendee => u)
    u.valid?
    u.all_valid?
    rec = u.recommendations.first
    
    puts rec.instance_variables.map { |ivar| "ivar name: #{ivar} ivar: #{rec.t(ivar).inspect}" }.join("\n")
    
    
    # mungo:snippets snusnu$ ruby balint.rb 
    #  ~ (0.000349) SELECT sqlite_version(*)
    #  ~ (0.000393) DROP TABLE IF EXISTS "users"
    #  ~ (0.000066) DROP TABLE IF EXISTS "recommendations"
    #  ~ (0.000061) PRAGMA table_info("users")
    #  ~ (0.001083) CREATE TABLE "users" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT)
    #  ~ (0.000027) PRAGMA table_info("recommendations")
    #  ~ (0.000601) CREATE TABLE "recommendations" ("user_id" INTEGER NOT NULL, "recommendee_id" INTEGER NOT NULL, PRIMARY KEY("user_id", "recommendee_id"))
    #  ~ (0.000096) INSERT INTO "users" DEFAULT VALUES
    #  ~ (0.000065) SELECT "id" FROM "users" WHERE "id" = 1
    # /opt/local/lib/ruby/gems/1.8/gems/extlib-0.9.13/lib/extlib/lazy_array.rb:293:in `respond_to?': stack level too deep (SystemStackError)
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-core-0.10.0/lib/dm-core/collection.rb:908:in `respond_to?'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:108:in `recursive_valid?'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:104:in `each'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:104:in `recursive_valid?'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:111:in `recursive_valid?'
    #   from /opt/local/lib/ruby/gems/1.8/gems/extlib-0.9.13/lib/extlib/lazy_array.rb:458:in `each'
    #   from /opt/local/lib/ruby/gems/1.8/gems/extlib-0.9.13/lib/extlib/lazy_array.rb:458:in `each'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:109:in `recursive_valid?'
    #    ... 4980 levels...
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:104:in `each'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:104:in `recursive_valid?'
    #   from /opt/local/lib/ruby/gems/1.8/gems/dm-validations-0.10.0/lib/dm-validations.rb:97:in `all_valid?'
    #   from balint.rb:46
    # mungo:snippets snusnu$
    
  • Martin Gamsjaeger (snusnu)

    Martin Gamsjaeger (snusnu) July 9th, 2009 @ 07:45 PM

    • Assigned user changed from “Michael Klishin (antares)” to “Martin Gamsjaeger (snusnu)”
    • State changed from “confirmed” to “accepted”
    • Milestone set to 0.10.0
  • Martin Gamsjaeger (snusnu)

    Martin Gamsjaeger (snusnu) July 11th, 2009 @ 12:04 PM

    I think we should look into this once the next branch supports self referential m:m. Since that is currently not possible, it makes more sense to wait till that's known to work?

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) October 4th, 2009 @ 09:33 PM

    • Milestone changed from 0.10.0 to 0.10.2

    [project:id#20609 not-tagged:"0.10.0" milestone:id#51895 bulk edit command]

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) October 4th, 2009 @ 09:39 PM

    Resource#all_valid? is no longer part of dm-validations, since it duplicates functionality in Resource#valid? now, and the implementation is not as good.

    I'm going to leave this ticket open so that we can verify that Resource#valid? does not have a similar issue with m:m.

  • Dan Kubb (dkubb)

    Dan Kubb (dkubb) February 1st, 2010 @ 04:33 PM

    • Milestone changed from 0.10.2 to 1.0.0
  • Martin Gamsjaeger (snusnu)

    Martin Gamsjaeger (snusnu) March 30th, 2010 @ 05:35 PM

    • State changed from “accepted” to “resolved”
    • Assigned user cleared.

    I think it's confusing to keep this ticket around while we don't even have the #all_valid? method anymore. I'm marking this resolved for now. If a problem crops up again, I'm almost sure that the potential reporter wouldn't "find" this ticket and instead would open up a new one anyway.

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Pages