Reputation: 1429
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
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