Martin Westin
Martin Westin

Reputation: 1429

Replacing a legacy model with a new one of the same name

Re-structuring a legacy app I wanted to re-use one of the model names because it is too perfect not to.

I want to make it totally invisible to the outside, JSON API, preferably even the controller layer, that these models do not have the default table name. But I cannot figure out the final puzzle piece. aliasing the foreign keys without making a chain and breaking it all.

So I have two tables. The old table perfections which I will be migrating away from, but not in one go unfortunately. Then the new table imperfections which will be taking over.

I can easily mask the table names:

class Perfection < ApplicationRecord
  self.table_name = "imperfections"
end
class LegacyPerfection < ApplicationRecord
  self.table_name = "perfections"
end

Models holding a foreign key to these though. That is my problem. In fact I have one problem model. It belongs to both these models. I can alias only one of the foreign keys to look like what I want from the outside but aliasing both I get an alias chain because of the shared name.

class OtherThing < ApplicationRecord
  belongs_to :perfection, foreign_key: :imperfection_id
  belongs_to :legacy_perfection, foreign_key: :perfection_id, optional: true
  alias_attribute :legacy_perfection_id, 'perfection_id'
  alias_attribute :perfection_id, 'imperfection_id'
end

I totally see why this makes an alias chain. The aliases are effectively doing the equivalent to this (and more for the getters snd stuff)

class OtherThing < ApplicationRecord
  belongs_to :perfection, foreign_key: :imperfection_id
  belongs_to :legacy_perfection, foreign_key: :perfection_id, optional: true

  def legacy_perfection_id=(value)
    self.perfection_id = value
  end
  def perfection_id=(value)
    self.imperfection_id = value
  end
end

If I call other_thing.legacy_perfection_id = 1 this number will be saved into imperfection_id.

I wonder if I can do something else... like actually change the attribute names somehow, and end up with a model where the parameters you pass to create or update are not revealing the internals.

For now I will do the transformation in the controller but I would like to have it be even cleaner.

Upvotes: 1

Views: 61

Answers (1)

max
max

Reputation: 102036

The input parameters you pass to your model are not really linked to its attributes or columns - they are linked to setter methods. assign_attributes basically just does:

attributes.each do |k, v|
  send("#{k}=", v)
end

So if you want the model to accept new_attribute as input but still assign the old attribute you can do it with:

def new_attribute=(value)
  self.old_attribute = value
end

In some cases though it can be useful to have adapters. Like if API v1 accepts old_attribute and API v2 accepts new_attribute. To keep the difference from leaking into the model layer you add an adapter that transforms the params before you assign them. This is actually a controller concern in MVC as the controller is responsible for accepting user input and passing it to models.

Upvotes: 1

Related Questions