Search code examples
ruby-on-railsactiverecordbelongs-to

Using ActiveRecord belongs_to with two keys


I have two ActiveRecord models with a hasMany / belongsTo association:

class User < ActiveRecord::Base
  has_many :letters
end

class Letter < ActiveRecord::Base
  belongs_to :user
end

The User model has a revision_number attribute, to which I would like to scope the belongs_to association, so the letter is associated to a User by both user.id and user.revision_number.

I tried using the :conditions key as documented in the API docs:

class Letter < ActiveRecord::Base
  belongs_to :user, :conditions => "revision_number = #{client_revision}"
end

but this attempts to call client-revision on the Letter class, not the instance of Letter. Could anyone point me in the right direction for scoping the belongs_to association correctly?

I'm using the acts-as-revisable plugin to version the User model.


Solution

  • Finally figured out what I needed was something like composite keys, which Rails ActiveRecord doesn't support. The solution (for now at least) was to write custom client accessors on the Letter to support the composite keys (id and revision_number):

    class Letter < ActiveRecord::Base
      def client
        Client.find_by_id(self.client_id).try(:find_revision, self.client_revision)
      end
    
      def client=(c)
        self.client_id = c.id
        self.client_revision = c.revision_number
      end
    end
    
    class Client < ActiveRecord::Base
      acts_as_revisable
    
      has_many :letters
    end
    

    With this setup, Client#1.letters will retrieve an array of both letters, but Letter#2.client will retrieve Client#1r2, whilst Letter#2.client will retrieve Client#1r4:

    Client         id:    1    1    1    1    1    1
           rev_number:    1    2    3    4    5    6
    
    Letter         id:         1              2
            client_id:         1              1
      client_revision:         2              5
    

    Still not sure if this is the best approach to this problem, but it seems to work for now.