Reputation: 13
I was searching about building Rails APIs and how to right apply some versioning on model validations.
Suppose there is a model and a route like these ones:
class Person < ApplicationRecord
validates :name, presence: true
end
namespace :v1
resources :people
end
So, my model is validating if name
is present.
But, if I want to release a new version of my API with a new validation on Person
to make, for example, a job
field mandatory, as this one:
class Person < ApplicationRecord
validates :name, presence: true
validates :job, presence: true
end
If I make it this way, I would break my V1 endpoint. So, is there a good practice to make it without breaking my previous V1 endpoint? Or should I drop this validations out of model and make it on request parameter level?
I've saw some gems that makes it at model level but I would like to know if there is some pattern for it without using any additional gem.
Upvotes: 1
Views: 345
Reputation: 907
The simplest way to achieve this is to use :on
option on your validations and performing contextual validations, e.g.
class Person < ApplicationRecord
validates :name, presence: true
validates :job, presence: true, on: :v2
end
Then you can use person.valid?
in your v1 endpoint and person.valid?(:v2)
in the v2 endpoint.
But this solution is good enough only for simple scenarios, it's not scalable in long term or more complex apps.
Better solution would be to extract the validation logic to form objects created from your params and have separate form objects for each version of your endpoints that contain some breaking changes, e.g.:
class PersonV1Form
include ActiveModel::Validations
attr_accessor :name, :job
validates :name, presence: true
def initialize(params = {})
@name = params[:name]
@job = params[:job]
end
end
class PersonV2Form
include ActiveModel::Validations
attr_accessor :name, :job
validates :name, :job, presence: true
def initialize(params = {})
@name = params[:name]
@job = params[:job]
end
end
Then in your controllers you can just validate form objects before you use them to feed your model:
module V1
class PeopleController < ApplicationController
def create
person = PersonV1Form.new(params[:person])
if person.valid?
# ...
end
end
end
end
module V2
class PeopleController < ApplicationController
def create
person = PersonV2Form.new(params[:person])
if person.valid?
# ...
end
end
end
end
Upvotes: 3