【问题标题】:identifying conflicting appointments in rails works for update but not create?识别 Rails 中的冲突约会是否适用于更新而不是创建?
【发布时间】:2011-03-26 02:08:31
【问题描述】:

我编写了这个冲突方法来检查正在保存或创建的约会是否与已经为特定培训师保存的约会发生冲突。在有人尝试在def createdef update 中创建或更新现有约会后,首先调用此方法。

它在更新约会时有效,但在创建时识别冲突。

有什么想法吗?

  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

设置医生的表格部分。

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

谢谢你

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    你正在创造一个不可能的条件。你的条件说@new_appointment必须在appointment的end_time之后有一个start_time,在appointment的start_time之前有一个end_time......这在逻辑上是不可能的。

    我建议使用这个: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Range/Overlaps.html

    您需要根据开始和结束时间创建范围,例如@new_appointment.start_time..@new_appointment.end_time

    【讨论】:

    • 现在这个条件肯定会出现问题吗?您能否对其进行调试并找到一个应该从该方法返回 true 但不是的特定示例?
    【解决方案2】:

    我认为您想要做的是将其下推至约会模型上的验证。见http://api.rubyonrails.org/classes/ActiveRecord/Validations.html#M001391

    在 appt_range 下面的 sn-p 中构建了一个从开始到结束时间的范围,并且应该在创建/更新时调用 validate 方法。

    也许是这样的。

    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
    

    然后你的控制器就会有

     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
    

    但是话虽如此(这也是您原始代码中的问题),但存在竞争条件/问题。假设患者有一个从 10:00 -> 10:30 开始的 appt,并且他们想将其移至 10:15->10:45。由于当时已经为患者预约了医生,因此更新将失败。也许添加 patient_id 而不是当前患者可以解决这种极端情况,但您的测试应该涵盖这种可能性。

    另外,我只是把它从脑海中浮现出来,还没有测试过,所以你的里程可能会有所不同(你没有指定 rails 的版本,但是从代码来看。看起来是 2.3.x?)。但希望这能为您指明更好的方向..

    编辑...

    我构建了一个准系统/简单的 rails 2.3.8 应用程序来测试它,它似乎可以在 create 上运行。看看http://github.com/doon/appt_test 我也包含了开发数据库。

     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 
    

    这是我的 Appointment 类(我使用了 validate :symbol 方法)

    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
    

    同样在玩这个,虽然你应该确保测试另一种情况。患者 A 在 10 至 11 日与 A 医生有约会。患者 B 从 11 到 12 日与 Dr a 进行了约会。这些将在当前实现中重叠,因为它们共享 11 个共同点,并将被标记为冲突。

    所以我不确定为什么它在创建时不起作用,如果您想显示您的代码,我们可以查看它。

    好的,我知道它为什么不起作用了,这与开始时间和结束时间有关。看看这个。

    来自测试...(在验证中添加一个记录器向我展示了这一点)。

    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
    

    当您从数据库中提取日期部分时,日期部分被截断并设置为 2000 年 1 月 1 日。因此,当您查询数据库时,您正在查看的范围不会重叠对于 2010 年的日期。将开始/结束时间设为日期时间将解决问题,因为这样日期将再次重要。否则需要修改 appt_range 以将日期调整回 date_of_appointment。更新时不会发生这种情况,因为您正在处理数据库中的所有数据。所以一切都有相同的日期

    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
    

    通过强制开始和结束时间使用 date_of_appointment 来修复它...

    【讨论】:

    • 您好,我刚试过,同样的问题,对更新很有效,但对创建无效。老实说,我不知道为什么?你是什​​么原因?顺便说一句,如果这改变了任何东西,它的rails 2.3.8。很好的答案,简短,简单,干净,只要它适用于创建。
    • 它应该适用于创建。您是否在模型中进行了验证保护?在验证中添加一条日志语句,并确保在 .save 上调用它
    • 我将验证设置为受保护,并输入了一个日志消息以查看它是否被调用,验证被调用以进行创建,只是没有做任何事情。 :(
    • 这可能与预约与医生和患者模型有嵌套关系有关,但又为什么它会在更新时起作用?
    • 我在上面进行了修改以将验证方法显示为受保护。另一种方法是执行类似 validate :does_not_conflict 之类的操作,并使用与上述验证相同的代码创建一个私有 does_not_conflict 方法。
    【解决方案3】:

    build 仅对已保存在数据库中的对象进行操作:请参见此处:http://apidock.com/rails/ActiveRecord/Associations/ClassMethods 在这里Ruby on Rails. How do I use the Active Record .build method in a :belongs to relationship?

    此外,您的代码仍然可以使用一些重构。您的 if 语句将返回 true 或 false,那么您为什么还要费心专门返回 true。此外,您不需要空括号,您应该将返回布尔值的方法定义为以问号结尾。最后,为什么要在 create 方法中创建一个新的约会,然后在验证方法中创建一个新对象?

    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
    

    然后在您的创建或更新方法中

    # 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
    

    【讨论】:

    • 感谢您的解释,我真的是 Rails 新手(以及相关的编程)。你能告诉我约会控制器中的创建和更新方法应该是什么样子吗,因为当我尝试它时,它会给我一个“意外的除非错误”。如果没有冲突,我只想保存它,如果它发生冲突,则调用“新”操作(以允许用户进行更改并通知他/她占用了时间段)。非常感谢您的帮助
    • 考虑到预约必须属于一个人(这是一个病人),这就是为什么我认为你必须做一个“person.appointment......等等”。创建新约会时,您是从 /patients/3/appointments/new 创建该约会
    • 可能比单个 stackoverflow 问题更复杂。如果你的项目在 github 上,请发布链接,我会看一下,如果没有,请在 irc 频道 #railsbridge 或 freenode.net 上的#rubyonrails 上询问
    • 我已经尝试解决这个问题超过 3 天,我愿意付费寻求帮助,有没有网站可以发布我的问题并付费给某人以获得详细的答案和解释?老实说,我已经失去了编码的动力,rails 不好玩哈哈
    • offer 仍然有效,在 github 上打那只小狗。或者,我的联系信息可以在我的博客上找到,该博客链接在我的个人资料中。
    【解决方案4】:

    其中的 if else 语句的数量令人不快。我说不清这是什么逻辑。尝试使用可枚举类进行此类工作。您可能只是在嵌套中出现错误。我发现return true 经常会产生不良结果,例如即使您认为逻辑可靠,也总是返回 true。

    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 
    

    【讨论】:

    • 问题不在于方法,条件有效。更新现有约会时,它不允许您将其更改为存在另一个约会的时间空间,它只是在创建新约会时起作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-16
    相关资源
    最近更新 更多