Lucas Wilson-Richter
Lucas Wilson-Richter

Reputation: 2324

How to specify polymorphic association with custom primary keys in Rails

I have a few ActiveRecord classes using paper_trail for version tracking. The AR classes have custom primary keys based on their table names (e.g. Item.ItemID instead of Item.id) in order to adhere to business DB conventions.

paper_trail specifies a polymorphic relationship on each of the tracked classes, thus:

class TrackedExample < ActiveRecord::Base
    set_table_name 'TrackedExample'
    set_primary_key 'TrackedExampleID'

    # simplified from https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb
    has_many :versions
        :class_name => 'Version'
        :as         => :item,
end

class AnotherTrackedExample
    set_table_name 'AnotherTrackedExample'
    set_primary_key 'AnotherTrackedExampleID'

    has_many :versions
        :class_name => 'Version'
        :as         => :item,
end

# from https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/version.rb
class Version
    belongs_to :item, :polymorphic => true
    ...
 end

If I were not using custom primary keys, the version object could refer to the tracked object (i.e. the object of which it is a version) using Version#item. When I try it, I get an error:

# This should give back `my_tracked_example`
my_tracked_example.version.first.item

=> TinyTds::Error: Invalid column name 'id'.: EXEC sp_executesql N'SELECT TOP (1) [TrackedExample].* FROM [TrackedExample] WHERE [TrackedExample].[id] = 1 ORDER BY TrackedExample.TrackedExampleID ASC'

Is there a way to get Version#item to perform the correct query? I would expect something like this:

EXEC sp_executesql N'SELECT TOP (1) [TrackedExample].* FROM [TrackedExample] WHERE [TrackedExample].[TrackedExampleID] = 1 ORDER BY TrackedExample.TrackedExampleID ASC'

I'm using Rails 3.1.0, paper_trail 2.6.4 and MS SQL Server through TinyTDS and activerecord-sqlserver-adapter.

EDIT: I've worked around the problem by adding computed columns TrackedExample.id and AnotherTrackedExample.id that refer to the primary key values. This isn't a proper solution (Rails is still making the wrong query), but it may be useful to others in a hurry.

MS SQL:

ALTER TABLE TrackedExample
    ADD COLUMN id AS TrackedExampleID

Upvotes: 4

Views: 3754

Answers (3)

ProfessorJigsaw
ProfessorJigsaw

Reputation: 101

Rather than specifying t.references :item, polymorphic: true in your migration file. You simply have to specify the item_id and item_type as follows:

t.string :item_id

t.string :item_type

Rails will automatically select the correct primary_key for the item_id and correct type.

Upvotes: 2

Joshua Pinter
Joshua Pinter

Reputation: 47471

After scouring the PaperTrail documents it doesn't look like you can override what column item_id references (i.e. the primary key of the Item's table), so I think you have two main options:

1. Create an id column that does not have the name of the class in it. So, id instead of TrackedExampleID.

You said you already did this as a quick fix.

2. Fork and patch PaperTrail to allow you to pass what column to use when querying for item_id.

This could either be the value set with set_primary_key 'TrackedExampleID' or it could be something set like has_paper_trail primary_key: 'TrakedExampleID'.

Let us know what you end up with.

Upvotes: 0

James Mason
James Mason

Reputation: 4296

I haven't tried it, but this might work

class Version
  belongs_to :item, :polymorphic => true, :primary_key => 'TrackedExampleID'
  ...
end

Upvotes: 1

Related Questions