1

Example from Guide with additional property in association model

class CreateAppointments < ActiveRecord::Migration
  def change
    create_table :physicians do |t|
      t.string :name
      t.timestamps null: false
    end

    create_table :patients do |t|
      t.string :name
      t.timestamps null: false
    end

    create_table :appointments do |t|
      t.belongs_to :physician, index: true
      t.belongs_to :patient, index: true
      t.datetime :appointment_date
      t.timestamps null: false
    end
  end
end

Model Appointment has a validation:

class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient

  validates :appointment_date, presence: true
end

class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, through: :appointments
end

When I add patient to physicians some_physician.patient << some_patient I have to define appointment_date. How to do it correctly?

Arslan Ali
  • 17,418
  • 8
  • 58
  • 76
davydes
  • 105
  • 9

3 Answers3

3

How to do it correctly?

In my opinion, you can define a method, and pass that method into before_save callback.

class Appointment < ActiveRecord::Base
  before_save :set_appointment_date

  private
  def set_appointment_date
    self.appointment_date = Time.now
  end
end

That's how you don't need to explicitly set the appointment_date each time you create a relationship. It will be automatically set for you.

Edit: before_save will also fire if you later want to update your appointment object, though that is a very rare case. But it will update the appointment_date. For that, you can use before_create callback, and appointment_date will be touched only once in that case.

Arslan Ali
  • 17,418
  • 8
  • 58
  • 76
  • sorry, but appointment_date is property of Appointment, not Patient – davydes Jun 30 '15 at 13:06
  • @DavydovEugene I fixed it. Thanks! – Arslan Ali Jun 30 '15 at 13:07
  • So, `has_many :patients, through: :appointments` makes no sense, because I have to use only `has_many :appointments` association. – davydes Jun 30 '15 at 13:15
  • But an appointment only happens when a patient _and_ a physician both agree. And yeah you can skip `has_many :patients, through: :appointments` if you do not want keep record of how many patients are there for the appointments of a particular doctor, which obviously you wouldn't want. – Arslan Ali Jun 30 '15 at 13:56
  • @DavydovEugene something to note on that `before_save` callback, it will happen every time you save an appointment. So if you are updating an appointment for some other reason it will change the `appointment_date` to now. – DickieBoy Jun 30 '15 at 14:13
  • @DickieBoy Well, if that is the case, you can use `before_create` callback. I leave it up to the OP, and also edit my question to reflect this. Thanks for your comment. – Arslan Ali Jun 30 '15 at 14:35
2

The reason you have to define it is because some_physician.patient << some_patient creates an implicit appointment to link the two. An appointment is invalid if you leave appointment_date blank.

If you don't want an appointment, define a new relationship between physicians and patients.

Otherwise it makes sense to do something like:

appointment = some_physician.appointments.create(patient: some_patient, appointment_date: Time.now)
DickieBoy
  • 4,886
  • 1
  • 28
  • 47
2

It is can be achieved without before_save callback, Rails update timestamps automatically. Just create a right migration file, assume you are use postgresql as orm.

create_table :appointments do |t|
  t.belongs_to :physician, index: true
  t.belongs_to :patient, index: true
  t.timestamps :appointment_date, default: 'now()'
  t.timestamps null: false
end

Instead datetime columntype use timestamps as created_at or updated_at with a plain sql function now()

Current date and time (equivalent to current_timestamp)

It's happens under the hood.

Some references

Community
  • 1
  • 1
Roman Kiselenko
  • 43,210
  • 9
  • 91
  • 103