Reputation: 4439
I'm using an ActiveModel-based Form Object to handle signup (registration) for an application. The signup
class abstracts away information for an account
and a user
(the primary user for the account).
However, I find that I'm duplicating the validation logic for the account
and user
inside the signup
class. As I was writing my specs (using rspec), I realized that this duplication is probably pointing to a problem with the way I'm handling this.
Is there a way to pass the validation in the signup
class off to the account
and user
models without duplicating it? That way the validation stays in those models and I can reference/call it in the signup
class.
Below is the signup
class I have that works, but seems to be duplicating code...
class Signup
include ActiveModel::Model
# Scopes
#----------------------------------------------------------------------
# NOOP
# Macros
#----------------------------------------------------------------------
attr_accessor :slug, :email, :password, :password_confirmation
# Associations
#----------------------------------------------------------------------
# NOOP
# Validations
#----------------------------------------------------------------------
validate :verify_unique_email
validate :verify_unique_slug
validates :email, presence: true, format: { with: /@/, message: "is invalid" }
validates :password, presence: true, length: { minimum: 8 }, confirmation: true
validates :password_confirmation, presence: true
validates :slug,
presence: true,
format: { with: /\A[\w-]+\z/, message: "is invalid" },
exclusion: { in: %w[signup signups login] }
# Methods
#----------------------------------------------------------------------
def account
@account ||= Account.new
end
def user
@user ||= account.build_primary_user
end
def save
account.active = true
account.slug = slug
user.email = email
user.password = password
user.password_confirmation = password_confirmation
if valid?
ActiveRecord::Base.transaction do
account.save!
user.save!
end
true
else
false
end
end
def save!
save
end
private
def verify_unique_email
if User.exists?(email: email)
errors.add :email, "is invalid"
end
end
def verify_unique_slug
if Account.exists?(slug: slug)
errors.add :slug, "has already been taken"
end
end
end
Here's the account
model, note the duplication to validations:
class Account < ActiveRecord::Base
has_one :primary_user, -> { where(primary: true) }, class_name: User
has_many :users, dependent: :destroy
validates :slug,
uniqueness: true,
presence: true,
format: { with: /\A[\w-]+\z/, message: "is invalid" },
exclusion: { in: %w[signup signups login] }
end
Upvotes: 2
Views: 743
Reputation: 7586
I like what you're doing using a form object. validates_associated :user, :account
might help, but the error messages might be kind of odd. Rather, I might use mixins for common validations:
class Account < ActiveRecord::Base
module Validations
extend ActiveSupport::Concern
included do
validates :slug, presence: true
end
end
include Validations
end
class Signup
include ActiveModel::Model
include Account::Validations
extend Forwardable
def_delegators :account, :slug
end
Upvotes: 1
Reputation: 3053
The simplest thing that would work would be:
def valid?
user.valid? && account.valid? && super
end
You can then get rid of the duplicated validations.
The super
is only needed if you need additional validations beyond those on User
and Account
. This will also add the errors onto the User
and Account
objects, which might be more useful than on the Signup
class.
Upvotes: 0