Davigor
Davigor

Reputation: 403

Polymorphic association for an either/or association vs. two separate rows with one null

I'm new to rails and was trying to implement a polymorphic association. Basically, I have a device object which can belong to either a outlet or a port, but not both. From my understanding, a polymorphic association would be the way to do this, so I modeled it like this:

class Device < ApplicationRecord
    belongs_to :io, polymorphic: true
end

class Outlet < ApplicationRecord
    has_one :device, as: :io
end

class Port < ApplicationRecord
  has_one :device, as: :io
end


  create_table "devices", force: :cascade do |t|
        ...
        t.integer "io_id"
        t.index ["io_id"], name: "index_devices_on_io_id"
      end

Now, I ran into some trouble trying to assign an outlet or port to the device's io:

can't write unknown attribute io_type

  @outlet = Outlet.where(id: @outlet_id).first

  @device.io = @outlet

  if @device.save
  redirect_to @device

which is from

class DevicesController < ApplicationController
    def new
        @device = Device.new(io_id: params[:io_id])
    end
    
    def create
        @outlet_id = params[:device][:io_id]
        @device = Device.new(device_params)
        
        @outlet = Outlet.where(id: @outlet_id).first

        @device.io = @outlet

        if @device.save
        redirect_to @device
      else
        render 'new'
      end
        
  end

Now, I will gladly accept some help on why this error was thrown, but that's not my primary question. I had a thought while debugging this-- does it even matter if I don't use a polymorphic association here? Would it also be fine just to create two separate associations on my device table, one pointing to the port table and the other to the outlet table and make them both optional? One would remain null and the other would be assigned, and then I could check which field != null in my business logic. Will I run into problems down the road going this route?

Upvotes: 0

Views: 43

Answers (1)

jvillian
jvillian

Reputation: 20263

Device needs both an io_id and an io_type. Otherwise, how would Device know what kind of thing the io_id belongs to?

Currently, as shown by your migration, you only have io_id. And so, you're getting the unknown attribute error for io_type.

You should add io_type to your table and you should be good to go.

I suggest you modify your create action to be more like (with some comments):

class DevicesController < ApplicationController
  def new
    @device = Device.new(io_id: params[:io_id])
  end

  def create

    # how do you know the :io_id is for an Outlet and not a Port?
    # if you add a hidden field that includes io_type, so that
    # params[:device] = {io_id: x, io_type: 'Outlet'}, then you 
    # should be able to do something like:
    @io = params[:device][:io_type].constantize.find_by(id: params[:device][:io_id])

    # the has_one association provides the build_device method.
    # if you use this method, then io_id and io_type will 
    # automatically be set on @device
    @device = @io.build_device(device_params)

    if @device.save
      redirect_to @device
    else
      render 'new'
    end

end

Upvotes: 1

Related Questions