1

I wrote this conflict method to check if an appointment being saved or created dosent conflict with one thats already saved for a particular trainer. this method is called first thing after a person attempts to create or update existing appointments in the def create and def update.

it works when updating the appointment but dosent identify the conflict when creating.

any ideas?

  def is_conflicting()
    @new_appointment = person.appointments.build(params[:appointment])
    @appointments = Appointment.all(:conditions => { :date_of_appointment => @new_appointment.date_of_appointment, :doctor_id => @new_appointment.doctor_id})
    @appointments.each do |appointment|
      logger.info( appointment)
        if(@new_appointment.start_time < appointment.end_time && appointment.start_time < @new_appointment.end_time)
          return true 
        end
    end
    return false
  end

def create
    @appointment = person.appointments.build(params[:appointment])
      respond_to do |format|
        if(is_conflicting == false)
        if @appointment.save
....more code...
        end
        end
      end 
end

  def update
    @appointment = person.appointments.find(params[:id])
      respond_to do |format|
        if(is_conflicting == false)
          if @appointment.update_attributes(params[:appointment])
        .....more code...........
           end
         end
     end
   end

the part of the form where doctor gets set.

  <p>
    <%= f.label :doctor_id %>
    <%= f.select :doctor_id, Doctor.find(:all, :order => "name").collect { |s|
        [s.name, s.id]} %>
  </p>

thank you

Mo.
  • 40,243
  • 37
  • 86
  • 131

4 Answers4

3

You're creating an impossible condition. Your condition says @new_appointment must have a start_time after appointment's end_time, and an end_time before appointment's start_time...this is logically impossible.

I'd suggest using this: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Range/Overlaps.html

You will need to create ranges based on the start and end times doing something like @new_appointment.start_time..@new_appointment.end_time

Karl
  • 6,035
  • 5
  • 30
  • 39
  • Is the problem definitely happening in that conditional now? Can you debug into it and find a specific example that should be returning true from that method but is not? – David Aug 07 '10 at 18:14
2

build only operates on objects already persisted in the database: see here: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods and here Ruby on Rails. How do I use the Active Record .build method in a :belongs to relationship?

also, your code could still use some refactoring. your if statement will either return true or false, so why are you bothering to specifically return true. Also, you dont need empty parens and you should define your methods that return a boolean as ending with a question mark. Finally, why are you building a new appointment in the create method, then making a new object in the validation method?

def conflicting? appointment
  @appointments = Appointment.all(:conditions => {... all of them})
  # Enumerable#any? returns true or false for the collection, 
  # so you dont have to specify a return value 
  # since its the last evaluation in the method
  @appointments.any?{|apt| appointment.start_time < apt.end_time && apt.start_time < appointment.end_time} #=> takes each appointment in appointments assigns to apt and checks against the passed in appointment object
end

and then in your create or update method

# assuming start/end times are form parameters coming from a view
@appointment = Appointment.new params[:appointment]
# substituting the lookup and update_attributes in the update action, obviously
@appointment.save unless conflicting? @appointment
Community
  • 1
  • 1
Jed Schneider
  • 14,085
  • 4
  • 35
  • 46
  • Thanks for the explanation, I'm really new to rails (and programming for that matter). can you please show me what the create and update methods in the appointment controller should look like because when i try it it gives me an "unexpected UNLESS error". i just want it to save if there is no conflict and call the "new" action if it conflicts (to allow user to make the changes and notify him/her that time slot is taken). i really appreciate your help – Mo. Aug 07 '10 at 15:02
  • taking in to consideration that appointment must belong to person (which is a patient) which i why i thought that you had to do a "person.appointment.....so on". when creating a new appointment you are creating that appointment from /patients/3/appointments/new – Mo. Aug 07 '10 at 15:27
  • probably more involved than a single stackoverflow question. if you have your project on github, post the link and I'll take a look, if not, then maybe ask on the irc channel #railsbridge or #rubyonrails on freenode.net – Jed Schneider Aug 07 '10 at 16:23
  • i have been trying to slove this problem for over 3 days, im willing to pay for help, is there any websites where i can post my problem and pay someone for a detailed answer and explanation? i have honestly lost the motivation to code, rails ain't fun haha – Mo. Aug 07 '10 at 17:58
  • offer still stands, slap that puppy on github. or, my contact information is available on my blog, which is linked on my profile. – Jed Schneider Aug 07 '10 at 18:38
  • HI sorry it took me so long, i put the app on git hub finally. its on http://github.com/aldeirm2/EMR the validate works for the update but not for the create and i really cant see why. thanks – Mo. Aug 08 '10 at 23:36
2

I think what you would want to do is push this down to a validation on the Appointment Model. See http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#M001391.

In the snippet below appt_range builds a range from start to end time, and the the validate method should be called on create/update.

Perhaps something like.

class Appointment < ActiveRecord::Base

  def appt_range
    start_time..end_time 
   end

   ...rest of code...
   protected
   def validate
     @appointments = Appointment.all(:conditions => { :date_of_appointment => date_of_appointment, :doctor_id => doctor_id})
     errors.add_to_base("Appointment Conflict") if @appointments.any? {|appt| appt.appt_range.overlaps? appt_range}
  end 
end

and then your controller would have

 def create
 @appointment = person.appointments.new(params[:appointment]))
    if @appointment.save
       ...
    end
end 

def update
    @appointment = person.appointments.find(params[:id])
    if @appointment.update_attributes(params[:appointment])
    ...
    end
end

But that being said (and this is a problem in your original code as well), there is a race condition/issue. Suppose a patient has an appt from 10:00 -> 10:30, and they want to move it to 10:15->10:45. The update will fail since the Dr is already booked for the patient at that time. Perhaps adding patient_id not current patient would solve that edge case, but your tests should cover that possibility .

Also I just whipped this up out of my head, and haven't tested it, so your mileage may vary (you didn't specify version of rails,, but from the code. looks to 2.3.x?). But hopefully this points you in the better direction ..

Edit...

I built a barebones/simple rails 2.3.8 app, to test it out, and it appears to work on create. take a look at http://github.com/doon/appt_test I included the dev db as well.

 rake db:migrate                                                                                                                                                  
==  CreateAppointments: migrating =============================================
-- create_table(:appointments)
   -> 0.0019s
==  CreateAppointments: migrated (0.0020s) ====================================

Loading development environment (Rails 2.3.8)
ruby-1.8.7-p299 > a=Appointment.new(:patient_id=>1, :doctor_id=>1, :date_of_appointment=>'08/10/2010', :start_time=>" 2010-08-10 8:00", :end_time=>"2010-08-10 10:00")
 => #<Appointment id: nil, patient_id: 1, doctor_id: 1, date_of_appointment: "2010-08-10", start_time: "2000-01-01 08:00:00", end_time: "2000-01-01 10:00:00", created_at: nil, updated_at: nil> 
ruby-1.8.7-p299 > a.save
  Appointment Load (0.2ms)   SELECT * FROM "appointments" WHERE ("appointments"."doctor_id" = 1 AND "appointments"."date_of_appointment" = '2010-08-10') 
  Appointment Create (0.5ms)   INSERT INTO "appointments" ("end_time", "created_at", "updated_at", "patient_id", "doctor_id", "date_of_appointment", "start_time") VALUES('2000-01-01 10:00:00', '2010-08-07 22:20:33', '2010-08-07 22:20:33', 1, 1, '2010-08-10', '2000-01-01 08:00:00')
 => true 
ruby-1.8.7-p299 > b=Appointment.new(:patient_id=>1, :doctor_id=>1, :date_of_appointment=>'08/10/2010', :start_time=>" 2010-08-10 9:00", :end_time=>"2010-08-10 11:00")
 => #<Appointment id: nil, patient_id: 1, doctor_id: 1, date_of_appointment: "2010-08-10", start_time: "2000-01-01 09:00:00", end_time: "2000-01-01 11:00:00", created_at: nil, updated_at: nil> 
ruby-1.8.7-p299 > b.save
  Appointment Load (0.4ms)   SELECT * FROM "appointments" WHERE ("appointments"."doctor_id" = 1 AND "appointments"."date_of_appointment" = '2010-08-10') 
 => false 
ruby-1.8.7-p299 > b.errors['base']
 => "Appointment Conflict" 
ruby-1.8.7-p299 > c=Appointment.new(:patient_id=>1, :doctor_id=>1, :date_of_appointment=>'08/10/2010', :start_time=>" 2010-08-10 11:00", :end_time=>"2010-08-10 12:00")
 => #<Appointment id: nil, patient_id: 1, doctor_id: 1, date_of_appointment: "2010-08-10", start_time: "2000-01-01 11:00:00", end_time: "2000-01-01 12:00:00", created_at: nil, updated_at: nil> 
ruby-1.8.7-p299 > c.save
  Appointment Load (0.3ms)   SELECT * FROM "appointments" WHERE ("appointments"."doctor_id" = 1 AND "appointments"."date_of_appointment" = '2010-08-10') 
  Appointment Create (0.4ms)   INSERT INTO "appointments" ("end_time", "created_at", "updated_at", "patient_id", "doctor_id", "date_of_appointment", "start_time") VALUES('2000-01-01 12:00:00', '2010-08-07 22:21:39', '2010-08-07 22:21:39', 1, 1, '2010-08-10', '2000-01-01 11:00:00')
 => true 

and here is my Appointment class (I used the validate :symbol method)

class Appointment < ActiveRecord::Base

  validate :conflicting_appts

  def appt_range
    start_time..end_time 
  end


  private
  def conflicting_appts
    @appointments = Appointment.all(:conditions => { :date_of_appointment => date_of_appointment, :doctor_id => doctor_id})
    errors.add_to_base("Appointment Conflict") if @appointments.any? {|appt| appt.appt_range.overlaps? appt_range}
  end
end

Also in playing around with this, though of another case you should be sure to test for. patient A has appt with dr A from 10-11. Patient B has appt with Dr a from 11-12. These will overlap in the current implementation since they share 11 in common, and will be marked as conflict.

So I am not sure why it isn't working on create, if you want to show your code we can look at it.

Ok I figured out why it isn't working, and it has to do with the start and end time. Take a look at this.

from testing... (adding a logger inside the validation shows me this).

appt.range == Sat Jan 01 09:06:00 UTC 2000..Sat Jan 01 21:10:00 UTC 2000 , my range = Tue Aug 10 09:15:00 UTC 2010..Tue Aug 10 14:19:00 UTC 2010
appt.range == Sat Jan 01 22:06:00 UTC 2000..Sat Jan 01 23:06:00 UTC 2000 , my range = Tue Aug 10 09:15:00 UTC 2010..Tue Aug 10 14:19:00 UTC 2010
appt.range == Sat Jan 01 09:30:00 UTC 2000..Sat Jan 01 12:14:00 UTC 2000 , my range = Tue Aug 10 09:15:00 UTC 2010..Tue Aug 10 14:19:00 UTC 2010
appt.range == Sat Jan 01 09:31:00 UTC 2000..Sat Jan 01 12:20:00 UTC 2000 , my range = Tue Aug 10 09:15:00 UTC 2010..Tue Aug 10 14:19:00 UTC 2010

What is happening is the date part is getting truncated off and set to Jan 1, 2000, when you pull it from the DB.. So when you are querying against the database the ranges aren't going to overlap you are looking for dates in 2010. Making the start/end times a datetime would solve the issue, as then the date would be significant again. Else need to modify the appt_range to adjust the date back to date_of_appointment. It doesn't happen on update since you are dealing with all data from the db. so everything has the same date on it

see http://github.com/doon/EMR/commit/b453bb3e70b5b6064bb8693cf3986cf2219fbad5

def appt_range
   s=Time.local(date_of_appointment.year, date_of_appointment.month, date_of_appointment.day, start_time.hour, start_time.min, start_time.sec)
   e=Time.local(date_of_appointment.year, date_of_appointment.month, date_of_appointment.day, end_time.hour, end_time.min, end_time.sec)
  s..e
end

fixes it by coercing start and end time into using the date_of_appointment...

Doon
  • 19,719
  • 3
  • 40
  • 44
  • Hi I just tried that, same problem, works great for update but dosent work for create. I honestly dont have any idea as of to why? what could be thee reason? btw its rails 2.3.8 if that changes anything. great answer though, short, simple and clean, if only it worked for create. – Mo. Aug 07 '10 at 19:21
  • It should work on create. did you make validate protected in the model? Put a log statement in the validation and make sure it is getting called on the .save – Doon Aug 07 '10 at 19:31
  • I made the validate protected and i put in a log mesg to see if it was being called, the validate is getting called for create, just not doing anything about it. :( – Mo. Aug 07 '10 at 19:42
  • could it be something to do with appointment having a nested relationship with doctor and patient model but then again why would it work on update? – Mo. Aug 07 '10 at 19:43
  • I revised above to show the validate method as protected. Other way way would be to do something like validate :does_not_conflict, and make a private does_not_conflict method with the same code as the the validate above. – Doon Aug 07 '10 at 19:44
  • ok so validate is getting called, but it isn't locating a conflicting appt, In the validation, check to make sure that both date_of_appointment and doctor_id are not nil? and are set correctly. – Doon Aug 07 '10 at 19:48
  • i guess what I am asking is how are you assigning the Dr, is the doctor_id passed in from the form... – Doon Aug 07 '10 at 19:51
  • yes, doctor_id is being passed in as an int from the form, is that a problem? – Mo. Aug 07 '10 at 20:09
  • what i do is i load all the doctors and put them in a drop down list and assign the ID as the value that gets saved. i updated the question to show that form element. – Mo. Aug 07 '10 at 20:19
  • nope that should be fine. Was just making sure you wheren't doing something like Creating the appt, and then doctor.appointments << @appointment (since then doctor_id wouldn't have been set). I don't see a reason why it shouldn't find the conflict on create, but would on update. when you run create, do you see it pulling all the Appts for the doctor, and can you see an overlap in the sql logs? – Doon Aug 07 '10 at 20:42
  • I'll try building a rails 2.3.8 app in a bit to that does this, and will let you know what I find... – Doon Aug 07 '10 at 20:46
  • i can't seem to replicate why yours doesn't work on create, see revised answer above. – Doon Aug 07 '10 at 23:20
  • very odd, still dosent work for me. i will post my code on github as soon as possible and post the link here if you dont mind having a look at it, its really driving me insane! btw thanks Doon i really appreciate all the effort your putting in and help. – Mo. Aug 08 '10 at 03:12
  • I cant get git to work properly so i cant upload the code to github, just nothing is working for me these days. what code would you like me to put up? ill put it in the question. or is there somewhere else i can just upload the code to? i can put project in a dropbox folder and share it with you if you want to have a look? thanks again – Mo. Aug 08 '10 at 13:13
  • Schema.rb. appointment model controller and all the views for the appointment. Should be good. – Doon Aug 08 '10 at 16:08
  • sup man! i finally managed to get the app on github. http://github.com/aldeirm2/EMR please let me know if u figure out why it wont work on create. thanks – Mo. Aug 08 '10 at 22:47
  • See my pull request, and the commit posted above. Also updated my answer to include why it didn't work, and some udpated code that fixes .. – Doon Aug 10 '10 at 14:39
  • You are a hero/code master. you have been so helpful, i can not thank you enough. it works! – Mo. Aug 10 '10 at 19:04
0

The number of if else statements in there hurt to look at. I can't tell what the logic is. Try using the enumerable class for this kind of work. You probably just have an error in your nesting. I've found that return true can often have undesirable results, like always returning true even when you think the logic is solid.

jruby-1.5.0 > apt1 = (1.hour.from_now..2.hour.from_now) #=> Fri, 06 Aug 2010 01:58:43 UTC +00:00..Fri, 06 Aug 2010 02:58:43 UTC +00:00 

jruby-1.5.0 > apt2 = (3.hour.from_now..4.hour.from_now)#=> Fri, 06 Aug 2010 03:59:07 UTC +00:00..Fri, 06 Aug 2010 04:59:07 UTC +00:00 

jruby-1.5.0 > @appointments = [apt1, apt2]

jruby-1.5.0 > @appointments.any?{|apt| (5.hour.from_now..6.hour.from_now).overlaps? apt } #=> false 

jruby-1.5.0 > @appointments.any?{|apt| (1.hour.from_now..3.hour.from_now).overlaps? apt } #=> true 
Jed Schneider
  • 14,085
  • 4
  • 35
  • 46
  • problem is not with the method, the condition works. when updating an existing appointment it dosent allow you to change it in to a time space where another appointment exists, it just dosent work when creating a new appointment. – Mo. Aug 07 '10 at 14:17