11

How can I make it so that at least two option records are required to submit a product?

class Product < ActiveRecord::Base
  belongs_to :user
  has_many :options, :dependent => :destroy
  accepts_nested_attributes_for :options, :allow_destroy => :true, :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
  validates_presence_of :user_id, :created_at
  validates :description, :presence => true, :length => {:minimum => 0, :maximum => 500}
end

class Option < ActiveRecord::Base
  belongs_to :product
  validates :name, :length => {:minimum => 0, :maximum => 60}                  
end
edgerunner
  • 14,873
  • 2
  • 57
  • 69
morcutt
  • 3,739
  • 8
  • 30
  • 47
  • It should be pretty simple to do with a custom validation. Something like `self.errors.add_to_base("Two options are required") unless self.options.length >= 2` – Todd Nov 30 '10 at 00:08
  • 1
    If you are using `accepts_nested_attributes_for` with `allow_destroy: true` then you must use `marked_for_destruction?` with children association to find exact length of children, because it may be possible while submitting from form some of objects have been marked `_destroy: true` for destruction after saving object. Length, size and count will not work perfect for that case. This link has perfect answer. [link](http://stackoverflow.com/a/28476834/4377172) – Azmat Rana Jan 20 '17 at 11:05

5 Answers5

17
class Product < ActiveRecord::Base
  #... all your other stuff
  validate :require_two_options

  private
    def require_two_options
      errors.add(:base, "You must provide at least two options") if options.size < 2
    end
end
deprecated
  • 5,142
  • 3
  • 41
  • 62
Keith Gaddis
  • 4,113
  • 23
  • 20
  • 1
    add_to_base(msg) has been deprecated, use Errors#add(:base, msg) instead – AnApprentice Jun 28 '11 at 22:23
  • 4
    options.count will generate an SQL COUNT query to find the number of options you have. If your options are in memory, and not saved in the database, this will give an unexpected answer as they won't be included in the count. [In cases like this consider using size.](http://stackoverflow.com/questions/6083219/activerecord-size-vs-count) – notapatch Jun 07 '13 at 12:28
  • Replaced in answer call to `.count` for `.size`. – deprecated Oct 16 '14 at 09:24
12

Just a consideration about karmajunkie answer: I would use size instead of count because if some built (and not saved) nested object has errors, it would not be considered (its not on database yet).

class Product < ActiveRecord::Base
  #... all your other stuff
  validate :require_two_options

  private
    def require_two_options
      errors.add(:base, "You must provide at least two options") if options.size < 2
    end
end
4

If your form allows records to be deleted then .size will not work as it includes the records marked for destruction.

My solution was:

validate :require_two_options

private
 def require_two_options
    i = 0
    product_options.each do |option|
      i += 1 unless option.marked_for_destruction?
    end
    errors.add(:base, "You must provide at least two option") if i < 2
 end
TrevTheDev
  • 2,616
  • 2
  • 18
  • 36
  • +1 Good point about minding records that are marked for destruction. However, a somewhat cleaner way to get `i` might be `i = product_options.reject { |option| option.marked_for_destruction? }.size`. – februaryInk Nov 23 '15 at 23:35
2

Tidier code, tested with Rails 5:

class Product < ActiveRecord::Base
  OPTIONS_SIZE_MIN = 2
  validate :require_two_options

  private

  def options_count_valid?
    options.reject(&:marked_for_destruction?).size >= OPTIONS_SIZE_MIN
  end

  def require_two_options
    errors.add(:base, 'You must provide at least two options') unless options_count_valid?
  end
end
Sebastian
  • 2,154
  • 1
  • 26
  • 42
0

I wonder why nobody mentioned this simple solution. Just add this to your parent class:

class Product < ActiveRecord::Base

  ...
  
  validates :options, :length => { :minimum => 2 }

end

Works like a charm to me in Rails 5.2.8 (and probably above as well).

Tintin81
  • 9,821
  • 20
  • 85
  • 178