3

There are models with has has_many through association:

class Event < ActiveRecord::Base
  has_many :event_categories
  has_many :categories, through: :event_categories

  validates :categories, presence: true
end

class EventCategory < ActiveRecord::Base
  belongs_to :event
  belongs_to :category

  validates_presence_of :event, :category
end

class Category < ActiveRecord::Base
   has_many :event_categories
   has_many :events, through: :event_categories
end

The issue is with assigning event.categories = [] - it immediately deletes rows from event_categories. Thus, previous associations are irreversibly destroyed and an event becomes invalid.

How to validate a presence of records in case of has_many, through:?

UPD: please carefully read sentence marked in bold before answering. Rails 4.2.1

y.bregey
  • 1,469
  • 1
  • 12
  • 21

3 Answers3

2

You have to create a custom validation, like so:

validate :has_categories

def has_categories
  unless categories.size > 0
    errors.add(:base, "There are no categories")
  end
end

This shows you the general idea, you can adapt this to your needs.

UPDATE

This post has come up once more, and I found a way to fill in the blanks.

The validations can remain as above. All I have to add to that, is the case of direct assignment of an empty set of categories. So, how do I do that?

The idea is simple: override the setter method to not accept the empty array:

def categories=(value)
  if value.empty?
    puts "Categories cannot be blank"
  else
    super(value)
  end
end

This will work for every assignment, except when assigning an empty set. Then, simply nothing will happen. No error will be recorded and no action will be performed.

If you want to also add an error message, you will have to improvise. Add an attribute to the class which will be populated when the bell rings. So, to cut a long story short, this model worked for me:

class Event < ActiveRecord::Base
  has_many :event_categories
  has_many :categories, through: :event_categories

  attr_accessor :categories_validator # The bell

  validates :categories, presence: true
  validate  :check_for_categories_validator # Has the bell rung?

  def categories=(value)
    if value.empty?
      self.categories_validator = true # Ring that bell!!!
    else
      super(value) # No bell, just do what you have to do
    end
  end

  private

  def check_for_categories_validator
    self.errors.add(:categories, "can't be blank") if self.categories_validator == true
  end
end

Having added this last validation, the instance will be invalid if you do:

event.categories = []

Although, no action will have been fulfilled (the update is skipped).

oxfist
  • 749
  • 6
  • 22
Ruby Racer
  • 5,690
  • 1
  • 26
  • 43
-2

use validates_associated, official documentaion is Here

hgsongra
  • 1,454
  • 15
  • 25
  • From docs - 'Validates whether the associated object or objects are all valid' and 'This validation will not fail if the association hasn’t been assigned. If you want to ensure that the association is both present and guaranteed to be valid, you also need to use validates_presence_of.'. But even combination of `validates_presence_of :categories` and `validates_associated :categories` DOES NOT prevent of immediate delete from `event_categories` table when assigning `event.categories = []` – y.bregey Jul 27 '16 at 15:07
-3

If you are using RSpec as your testing framework, take a look at Shoulda Matcher. Here is an example:

describe Event do
  it { should have_many(:categories).through(:event_categories) }
end
Greg Answer
  • 717
  • 5
  • 15