This scenario is quite common in Rails projects, and I am surprised that there are still not as many practical solutions as its simple evolution of data, but it requires some delicacy when working with already deployed systems.
I'm not sure that you are interested in many-to-many polymorphic behavior, but I quit because I find it useful for many many-to-many scenarios (pun intended!) :-).
I had this before I started:
class Tag < ActiveRecord::Base has_many :posts, inverse_of: :tag class Post < ActiveRecord::Base belongs_to :tag, inverse_of: :posts
I know, I know, why only one tag for a message? Turns out I wanted my posts to have multiple tags. And then I thought, wait a minute, I want other things to have tags, such as some Thing.
You can use: has_and_belongs_to_many for each of the Posts-Tags and Things-Tags, but then this makes 2 join tables, and we probably want Tag more objects, because they are added correctly? has_many :through is a great option for one side of our associations and avoids multiple join tables.
We are going to do this in 2 STEPS with 2 deployments :
Step 1 No changes to existing associations. A new Taggable model / migration that will be polymorphic with respect to messages and things. DEPLOY.
Step 2 Update associations. New migration to remove old :tag_id foreign_key from Posts. DEPLOY.
These two steps are necessary to complete the transfer in step 1 using your previous link definitions, otherwise your new associations will not work.
I think that two steps is the easiest approach, it is recommended if your traffic is small enough so that the risk of creating additional tags in messages / things between these two steps is low enough. If your traffic is very high, you can combine these two steps into one, but you will need to use different association names and then come back to remove the old unused ones after a working deployment. I will leave 1 step as an exercise for the reader :-)
Step 1
Create a model migration for the new polymorphic join table.
rails g model Taggable tag_id:integer tagged_id:integer tagged_type:string --timestamps=false
Edit the resulting migration to revert to using #up and #down (instead of #change ) and add data migration:
class CreateTaggables < ActiveRecord::Migration def up create_table :taggables do |t| t.integer :tag_id t.integer :tagged_id t.string :tagged_type end
Edit the new model:
class Taggable < ActiveRecord::Base belongs_to :tag belongs_to :tagged, polymorphic: true end
At this point, DEPLOY is your new model and migration. Excellent.
Step 2
Now we are going to update our class definitions:
class Tag < ActiveRecord::Base has_many :taggables has_many :posts, through: :taggables, source: :tagged, source_type: "Post" has_many :things, through: :taggables, source: :tagged, source_type: "Thing" class Post < ActiveRecord::Base has_and_belongs_to_many :tags, join_table: 'taggables', foreign_key: :tagged_id class Thing < ActiveRecord::Base has_and_belongs_to_many :tags, join_table: 'taggables', foreign_key: :tagged_id
You can add dependent: :destroy to has_many :posts and has_many :things , because :tag is belongs_to in Taggable.
Remember to leave your old foreign_key:
class RemoveTagIdFromPosts < ActiveRecord::Migration def up remove_column :posts, :tag_id end def down add_column :posts, :tag_id, :integer end end
Update your specifications!
DEPLOYMENT!