Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maximum number of associated records

I'd like to have a maximum number of associated records on a model. E.g. a project has_many tasks, but not more then twenty.

How can I enforce this rule?

The only solution that I've been able to come up with so far is an INSERT INTO...SELECT query like this:

INSERT INTO
  tasks (`id`,`project_id`,`title`,`body`)
SELECT
  NULL, ?, ?, ?
FROM
  tasks
HAVING
  count(id) < MAX_NUMBER_OF_TASKS
LIMIT 1;
  1. As far as I can tell, this will guarantee a maximum number of tasks being inserted. Am I correct in this?
  2. Is there a 'Rails way' to do this?
  3. Is it possible to override ActiveRecord/the Task model so that it uses the query above to insert a new record?

I'm currently using a custom method with ActiveRecord::Base.connection and calling that instead of .create or .save when new_record? == true.

like image 253
Justin Case Avatar asked Oct 27 '25 18:10

Justin Case


1 Answers

I haven't been able to try this, but I can't see why it shouldn't work.

Step one: Define a validator on the parent object (this is a simple implementation - could/should be made more generic):

class Project < ActiveRecord::Base
  validate :max_tasks

  def max_tasks
    if tasks.count > 20
      errors.add_to_base("Should not have more than 20 tasks")
    end
  end
end

Step two: Turn on validation of project from tasks:

class Task < ActiveRecord::Base
  validates_associated :project
end

And I think you should be in business. When you try and save a new task, it'll validate the associated project, and the validation will fail if there are (now) more than 20 tasks associated.

Just in case you fancy making this more generic, you could do something like:

class NumberOfAssociatedValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if options[:maximum] && record.send(attribute).count > options[:maximum]
      record.errors[attribute] << "must not have more than #{options[:maximum]}"
    end
    if options[:minimum] && record.send(attribute).count < options[:minimum]
      record.errors[attribute] << "must not have less than #{options[:minimum]}"
    end
  end
end

class MyModel < ActiveRecord::Base
  validates :my_association, :number_of_associated => {:maxiumum => 20}
end
like image 184
Paul Russell Avatar answered Oct 29 '25 08:10

Paul Russell



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!