13

I have a student and a course model. Student belongs to course, and course has many students.

class Student < ActiveRecord::Base
  attr_accessible :course_id, :name, :password, :status, :studentID, :year
  belongs_to :course

  validates :name, :password, :status, :studentID, :year, :presence =>true
  validates_associated :course
end

class Course < ActiveRecord::Base
  attr_accessible :courseCode, :courseName, :courseYr
  validates :courseCode,:courseName,:courseYr, :presence => true
  validates :courseCode,:courseYr, :uniqueness=>{:message=>"Cannot repeat the code"}

  has_many :students 
end

In the form used to create student record, I let the user enter the course ID.

<div class="field">
  <%= f.label :course_id %><br />
  <%= f.text_field :course_id %>
</div>

But I don't know how to validate the course_id input by the user. The student model validation will not generate an error, even when I type a course ID that does not exist. How do I get it to show the error?

Chris Peters
  • 17,918
  • 6
  • 49
  • 65
code4j
  • 4,208
  • 5
  • 34
  • 51

3 Answers3

18

You should look into creating a custom validation method:

class Student < ActiveRecord::Base
  validates :course_id, presence: true, numericality: { only_integer: true }
  ...
  validate :validate_course_id

private

  def validate_course_id
    errors.add(:course_id, "is invalid") unless Course.exists?(self.course_id)
  end
end

First, your model will make sure that the course_id is a valid integer, and then your custom validation will make sure that the course exists in the database.

Chris Peters
  • 17,918
  • 6
  • 49
  • 65
  • it works :) also thanks for correcting my problem statement ..haha btw, how can i let the user choose a course from a list rather than entering the id?? I know there is helper of select tag in rails, so I should put a array of all course inside it ? – code4j Sep 17 '12 at 01:10
  • Correct, you could use `select` populated by the array of courses. There are plenty of examples on SO and the web for that. – Chris Peters Sep 17 '12 at 10:19
  • Ok. This works fine. However, is it possible to do not perform second validation if first fails (if presence validation fails). When there is no `course_id` I get both presence validation error & custom validation error. – pablo Sep 14 '13 at 10:04
  • 2
    You could change the `presence` detection to look like this: `validates :course_id, presence: true, numericality: { only_integer }, unless: -> { errors.include?(:course_id) }`. If you try that, make sure that you put your custom validation before the `presence` detection. – Chris Peters Sep 14 '13 at 13:07
  • @ChrisPeters, this is what I'm looking for! Thanks. But why first should go custom validation? I think first should go `presence` validation. But this is not very important. Do you know how to make it work on each validation without writing `unless: ...` on each `validates`? I tried to go into rails source files to find out. But there things are pretty complicated. – pablo Sep 14 '13 at 23:03
  • Oh, I guess I misunderstood which order you want them to run in. You should order them however makes sense for your app. Not sure how you could avoid adding the `unless` to each individual call to `validates`. The best you could probably do is factor out the `lambda` into a method and point each `unless` to that instead. – Chris Peters Sep 16 '13 at 01:52
4

You can validate the presence of the course association. So, if does not exists, valid? will be false.

Your class should be:

class Student < ActiveRecord::Base
  attr_accessible :course_id, :name, :password, :status, :studentID, :year
  belongs_to :course

  validates :course, :name, :password, :status, :studentID, :year, presence: true
end

The differences of validating foreign key and the association itself are explained at this post.

If you don't need to validate presence of course, you can do this workaround:

validates :course, presence: true, if: -> {course_id.present?}
William Weckl
  • 2,435
  • 4
  • 26
  • 43
3

You can just do a custom validation method that checks the validity of your course id:

# course.rb
validates :course_id_is_valid

def course_id_is_valid
  if #course id isn't valid
    errors.add(:course_id, "is invalid")
  end
end

Alternately, if the course list is a discrete list, you can use the :in option on validations:

# course.rb
validates :course_id, :inclusion => { :in => list_of_courses, :message => "%{value} is not a course id" }

There are more options. Both of these examples come from the Rails docs:

http://guides.rubyonrails.org/active_record_validations_callbacks.html#custom-methods

Wheeyls
  • 1,012
  • 1
  • 7
  • 14
  • Assuming that you have an array that contains each item in the list. Actually this seems like it would be pretty unwieldly, you almost certainly want to use the custom validation method. – Wheeyls Sep 17 '12 at 22:48