Josh
Josh

Reputation: 676

Rails changing a proc to a method to validate if present in model

I have a model that has a long list of attributes. Not all attributes need to be present to save, but, if they are present I want to validate them.

Currently I am adding an if: proc to the end of each validation as such:

# app/models/bio.rb
class Bio < ApplicationRecord
  ...
  validates :age, numericality: { only_integer: true }, if: Proc.new { |a| a.age.present? }
  validates :height, numericality: { only_integer: true }, if: Proc.new { |a| a.height.present? }
  validates :weight, numericality: { only_integer: true }, if: Proc.new { |a| a.weight.present? }
  ...
end

This doesn't seem very DRY, so, I wanted to move this proc to a class method and call it via it's symbol as:

...
validates :age, numericality: { only_integer: true }, if: :has_attr(:age)
...
def has_attr(a)
  #some_code.a.present?
end

I'm just unsure what the a in the proc refers to. I am assuming it's self or something similar.

How do I need to modify the method to get it to work the same as the proc? And do I need to add a ? to the method name, such as def has_attr?(a) or def has_attr(a)?? And, honestly, I'm not even sure if you can pass an argument to a symbol. Any light you can shed on this is greatly appreciated.

Upvotes: 0

Views: 764

Answers (1)

mu is too short
mu is too short

Reputation: 434785

You can't do it that way, the :if option wants something callable (such as a Proc or lambda) or a symbol which names an instance method. There's no way to tunnel arguments down to the instance method unless you want to wrap it as in your first example.

However, in this case, you can say allow_nil: true which will have the same effect:

validates :age, numericality: { only_integer: true }, allow_nil: true
# ----------------------------------------------------^^^^^^^^^^^^^^^

Adding that will skip the validation if the age is nil and that should have the same effect as your current logic.


You could do something close to what you were trying to do with a class method that returns lambdas. Something like:

def self.has_attr(a)
  ->(obj) { obj.public_send(a).present? }
end

and then:

validates :age, ..., if: has_attr(:age)

You'd need to arrange for self.has_attr to be defined before your validates calls but that's not too hard, you'd probably put has_attr in a concern and include it at the top of your model class to deal with that problem and make has_attr easily available in other models as well.

Upvotes: 2

Related Questions