Search code examples
ruby-on-railsrubyindexingpolymorphic-associations

Rails - Polymorphic Associations with has many relationship - index values


I have models for Organisation and Package_ip.

The associations are:

Organisation

has_many :ips, as: :ipable, class_name: Package::Ip
    accepts_nested_attributes_for :ips,  reject_if: :all_blank, allow_destroy: true

Package::Ip

belongs_to :ipable, :polymorphic => true, optional: true, inverse_of: :ip

The Package_ips table has:

create_table "package_ips", force: :cascade do |t|
    t.string   "identifier"
    t.text     "description"
    t.text     "conditions"
    t.integer  "ipable_id"
    t.string   "ipable_type"
    t.datetime "created_at",     null: false
    t.datetime "updated_at",     null: false
    t.string   "title"
    t.string   "status"
    t.string   "classification"
    t.index ["ipable_type", "ipable_id"], name: "index_package_ips_on_ipable_type_and_ipable_id", unique: true, using: :btree
  end

My objective is for organisations to create many instances of package_ip.

I have a problem however with the way the index works. If I want to create a second instance of package_ip, I get an error that says the index values are no longer unique.

Am I alright to remove the unique constraint from my index? I realise that the ipable_ip is going to be the organisation.id and the ipable_type is going to be the parent class (organisation). I don't see why that needs to be unique. But maybe I've missed something about polymorphic associations and the reliance on the index.


Solution

  • Yes, this limitation is from your business logic, and not built into rails. Rails should allow you to drop the unique constraint, it would be wise to ensure your business logic is safe to handle this.

    You may have code like:

    foo_ip org.ips.where(type: "foo").first

    This would hopefully live in a named scope if it existed.

    Update after comment: Presumably you are using IPs as "Intellectual Properties".

    Let's suppose you have

    class Song < Package::Ip
    end
    
    class ThemeSong < Song
    end
    
    class Logo < Package::IP
    end
    

    You may have business logic that expects an organization to have exactly one theme song or exactly one Logo.

    So you may have code like:

    theme = org.theme_songs.first
    

    or:

    logo = org.logos.first
    
    # Or worse yet
    logo = org.ips.where(:type = "Logo").first
    

    So while Rails supports having multiple Songs or Logos, make sure your business logic doesn't just take the first one in some case.