ruby-on-railsactiverecordsti

Implementing STI in Ruby on Rails, how can I duplicate an object from one class to the other?


I implemented STI to separate 2 types of Skill objects: DefinedSkill and DeployedSkill. They are physically very close, but are managed different ways.

One method of the DefinedSkill is the deploy method, which creates a nearly identical DeployedSkill.

Initially, I wrote this:

def deploy
  @template_skill = DefinedSkill.find(params[:id])
  if @template_skill.status.code == "ACCEPTED"
    @deployed_skill = @template_skill.deep_clone include: [:translations]
    @deployed_skill.type = 'DeployedSkill'
---
    @deployed_skill.save
  end
end

But this produces an object of DefinedSkill class, even though I try to assign the type attribute.

Then I tried to work at attributes level, and wrote this:

def deploy
  @template_skill = DefinedSkill.find(params[:id])
  if @template_skill.status.code == "ACCEPTED"
    @deployed_skill = DeployedSkill.new(@template_skill.attributes.except(:id, :type))
    # @deployed_skill.type = 'DeployedSkill' (useless as type is managed by STI feature)
---
    @deployed_skill.save
  end
end

But this produces the following error:

ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: DefinedSkill is not a subclass of DeployedSkill)

So this is my question: how can I create an object of a sibling class in the context of STI?


Solution

  • Many thanks to msencenb and felipeecst for putting me on the way. After reading the doc and trying, I came to the conclusion that the existing object could not be converted, but class conversion should be done when creating the new instance.

    The solution I applied is:

    @deployed_skill = @template_skill.becomes!(DeployedSkill).deep_clone include: [:translations]
    

    which solved my issue.