Polymorphic has_many through association causes nil in join table #403

Closed
larsar opened this Issue May 5, 2011 · 6 comments

3 participants

@larsar

I have stumbled upon a problem with Rails 3.0.7 that I have not found a solution for, and to me it seems like a bug. If it's not, please be gentle. :)

My models contains of a Notification class that are connected to Group and Member via a polymorphic join model called Delivery. The problem is that when I do the following, the Delivery object is saved, but it fails to add the key to the new notification object.

The flow in the application, for this example is:

  1. /members/new Creating a new member
  2. /members/1/notifications/new Creating a new notification object by creating a notification object, adding the parent member to the members association and then saving the notification object. As I understand the documentation, the Delivery join object should automatically be saved after the notification object has been saved to the database (and an ID is given to the notification object).

After saving the notification, the Delivery join object is also saved, but the Delivery table in the database does not contain the id of the newly created notification object:

select id, notification_id, notifiable_type, notifiable_id from deliveries; 
1|<NULL>|Member|1

Note: I worked around the problem by calling notification.save before notification.members << member, but this causes my after_save callbacks to run prematurely, before the notification object is in a valid state.

Example code from my application. Using this code results in the null value in the delivery table.

class Notification < ActiveRecord::Base
  has_many :deliveries, :as => :notifiable
  has_many :members, :through => :deliveries, :source => :notifiable, :source_type => "Member"
  has_many :groups, :through => :deliveries, :source => :notifiable, :source_type => "Group"
end

 class Member < ActiveRecord::Base
  has_many :deliveries, :as => :notifiable
  has_many :notifications, :through => :deliveries
end

class Delivery < ActiveRecord::Base
  belongs_to :notification
  belongs_to :notifiable, :polymorphic => true
end

# Group is not really relevant in this example.
class Group < ActiveRecord::Base 
  has_many :deliveries, :as => :notifiable
  has_many :notifications, :through => :deliveries
end

class NotificationsController < ApplicationController
  def create
    @notification = Notification.new(params[:notification])
    @member = Member.find(params[:member_id])
    @notification.members << @member

    respond_to do |format|
      if @notification.save
        ...
      end
    end
  end
end

Using non-polymorphic join model worked as expected

Trying to identify if the polymorphic association was the origin of the problem, I simplified my model using only a simple non-polymorphic join model. This worked as expected, and the notification_id was set correctly in the delivery table without the need for "double saving" the notification object.

select id, notification_id, member_id from deliveries; 
1|1|1

Working code, without the many-to-many polymorphic join model:

class Notification < ActiveRecord::Base
  has_many :deliveries
  has_many :members, :through => :deliveries
end

class Member < ActiveRecord::Base
  has_many :deliveries, :as => :notifiable
  has_many :notifications, :through => :deliveries
end

class Delivery < ActiveRecord::Base
  belongs_to :notification
  belongs_to :member
end

class NotificationsController < ApplicationController
  def create
    @notification = Notification.new(params[:notification])
    @member = Member.find(params[:member_id])
    @notification.members << @member

    respond_to do |format|
      if @notification.save
        ...
    end
  end
end
@gnufied

I think the only hope of getting this fixed is, if you can attach a some minimum code that we can run locally and reproduce the problem.

@larsar

I created a minimal rails example project with a readme file explaining how to reproduce the problem:

git://github.com/larsar/rails-issue-403.git

@josevalim
Ruby on Rails member

Thanks @larsar and @gnufied. Can any of the several contributors now isolate the issue from the minimal rails app to a test case in Rails? This would help us a lot in moving on and fixing the issue. ❤️

@gnufied

On having a look at the sample code, this is not a bug. The problems with code:

  1. Following code is wrong and will not work
has_many :deliveries, :as => :notifiable
has_many :members, :through => :deliveries, :source => :notifiable, :source_type => "Member"

Because when you will say notification.members it will create the join with where condition deliveries.notifiable_id=1 AND deliveries.notifiable_type = 'Notification' . Clearly thats not what you want.

  1. When you are creating deliveries indirectly via notification.members << member , it WILL set notifiable_id and notifiable_type correctly in deliveries table, but AR has no way of knowing that you want to set notification_id in deliveries table as well. It is impossible, you will have to set that yourself.

    In other words, you are expecting too much from Rails here. :)

@josevalim
Ruby on Rails member

Ok, closing. Thanks @gnufied.

@josevalim josevalim closed this May 7, 2011
@larsar

Thanks for taking the time to look at this. I'll have to let it sink in and try another solution then. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment