33

I know I can require a field by adding validates_presence_of :field to the model. However, how do I require at least one field to be mandatory, while not requiring any particular field?

thanks in advance

-- Deb

deb
  • 12,326
  • 21
  • 67
  • 86

6 Answers6

36

You can use:

validate :any_present?

def any_present?
  if %w(field1 field2 field3).all?{|attr| self[attr].blank?}
    errors.add :base, "Error message"
  end
end

EDIT: updated from original answer for Rails 3+ as per comment.

But you have to provide field names manually. You could get all content columns of a model with Model.content_columns.map(&:name), but it will include created_at and updated_at columns too, and that is probably not what you want.

Tom Harrison
  • 13,533
  • 3
  • 49
  • 77
Tatjana N.
  • 6,215
  • 32
  • 29
9

Here's a reusable version:

class AnyPresenceValidator < ActiveModel::Validator
  def validate(record)
    unless options[:fields].any?{|attr| record[attr].present?}
      record.errors.add(:base, :blank)
    end
  end
end

You can use it in your model with:

validates_with AnyPresenceValidator, fields: %w(field1 field2 field3)
Shai Coleman
  • 868
  • 15
  • 17
  • 1
    Instead of pairing `all?` with `blank?` here, you could also pair `any?` with `present?` so that the code itself follows the semantics of the validator name – user456584 Nov 14 '14 at 22:08
8

Add a validate method to your model:

def validate
  if field1.blank? and field2.blank? and field3.blank? # ...
    errors.add_to_base("You must fill in at least one field")
  end
end
Robert Speicher
  • 15,382
  • 6
  • 40
  • 45
  • I can't imagine he would want to check if one field _in his entire model_ is present. I assumed it was a particular set of fields. – Robert Speicher May 13 '10 at 02:40
  • your answer is also correct, so I voted it up as well. However, I ended up using Voyta's answer. Thanks for the reply! – deb May 17 '10 at 21:00
  • @RobertSpeicher I do agree with you since, usually, we would like to check if one-of-the-two are present. Such: (phone or email) or (deadline or date in arrears) not requiring only one field out of the 10 or... So, I voted for your answer for it's simplicity. – bir_ham May 26 '16 at 10:20
2

I believe something like the following may work

class MyModel < ActiveRecord::Base
   validate do |my_model|
      my_model.my_validation
   end

   def my_validation      
      errors.add_to_base("Your error message") if self.blank? 
      #or self.attributes.blank? - not sure
   end
end
Ju Nogueira
  • 8,435
  • 2
  • 29
  • 33
1

Going further with @Votya's correct answer, here is a way to retrieve all columns besides created_at and updated_at (and optionally, any others you want to throw out):

# Get all column names as an array and reject the ones we don't want
Model.content_columns.map(&:name).reject {|i| i =~ /(created|updated)_at/}

For example:

 1.9.3p327 :012 > Client.content_columns.map(&:name).reject {|i| i =~ /(created|updated)_at/}
 => ["primary_email", "name"]
d3vkit
  • 1,942
  • 1
  • 24
  • 36
0

If you only have two fields, this will get the job done:

validates :first_name, presence: true, if: :nick_name.blank?
validates :nick_name, presence: true, if: :first_name.blank?

This does not scale up well with more fields, but when you only have two, this is perhaps clearer than a custom validation method.

n.b. If they omit both, the error message will appear more restrictive than you intend. (e.g. First Name is required. Nick Name is required.) ¯\(ツ)

David Hempy
  • 5,373
  • 2
  • 40
  • 68