【问题标题】:How to validate overlapping times in Rails如何在 Rails 中验证重叠时间
【发布时间】:2016-04-06 11:29:50
【问题描述】:

我有一个Event model,在我的日程安排应用程序中有form 时间和to 时间,我想在保存之前验证重叠时间。

我的视图如下;

Departure date: Dec 31, 2016

Day1
07:00 - 07:20 event1
10:30 - 11:30 event2
15:40 - 16:10 event3
[add event button]

Day2
08:15 - 09:05 event4
12:08 - 13:04 event5
14:00 - 14:25 event6
[add event button]

[save schedule button]

from时间和to时间可以同时更改和添加。

如果我尝试为Day1 添加(或更改为)07:05 - 07:30,我想做的是显示错误,例如,13:50 - 14:30Day2 等等。

虽然我尝试了一些带有overlapbetweencover 和引用this postthis post 等的代码,但我无法将它们应用到我的代码中。

schema.rb

  create_table "events", force: :cascade do |t|
    t.time     "from"
    t.time     "to"
    t.integer  "room_id"
    ...

  create_table "rooms", force: :cascade do |t|
    t.integer  "schedule_id"
    ...

  create_table "schedules", force: :cascade do |t|
    t.integer  "user_id"
    t.date     "departure_date"
    ...

给出以下模型

class Schedule < ActiveRecord::Base
  belongs_to :user
  has_many :rooms, inverse_of: :schedule
  accepts_nested_attributes_for :rooms, allow_destroy: true
  ...

class Room < ActiveRecord::Base
  belongs_to :schedule, inverse_of: :rooms
  has_many :events, inverse_of: :room
  accepts_nested_attributes_for :events, allow_destroy: true
  ...

class Event < ActiveRecord::Base
  belongs_to :room, inverse_of: :events
  has_one :schedule, autosave: false, through: :room
  ...

_schedule_form.html.erb

  <%= render 'shared/error_messages', object: f.object %>
  <%= f.label :title %>
  <%= f.text_field :title, class: 'form-control' %>
  <br>
    <%= f.label :departure_date %>
    <div class="input-group date" id="datetimepicker">
      <%= f.text_field :departure_date, :value => (f.object.departure_date.strftime('%b/%d/%Y') if f.object.departure_date), class: 'form-control' %>
      <span class="input-group-addon">
        <span class="glyphicon glyphicon-calendar"></span>
      </span>
    </div>
  <script type="text/javascript">
    $(function () {
      $('#datetimepicker').datetimepicker({format:'MMM-DD-YYYY'});
    });
  </script>
  <br>
  <div id="room">
    <%= f.simple_fields_for :rooms do |a| %>
      <div id="room_<%= a.object.object_id %>">
        <p class="day-number-element-selector"><b>Day&nbsp;<%= a.index.to_i + 1 %></b></p>

        <%= a.simple_fields_for :events do |e| %>
          <span class="form-inline">
            <p>
              <%= e.input :from, label: false %>&nbsp;&nbsp;&nbsp;-&nbsp;&nbsp;&nbsp;
              <%= e.input :to, label: false %>
            </p>
          </span>
          <%= e.input :title, label: false %>
        <% end %>
      </div>

      <%= a.link_to_add "Add event", :events, data: {target: "#room_#{a.object.object_id}"}, class: "btn btn-primary" %>

      <%= a.input :room %>

    <% end %>
  </div>

如果您能告诉我如何检查和显示错误,将不胜感激。

编辑!

还是不行

event.rb

class Event < ActiveRecord::Base
  before_save :assign_date
  belongs_to :room, inverse_of: :events
  has_one :schedule, autosave: false, through: :room
  validate :cannot_overlap_another_event

scope :in_range, -> range {
  where('(\'from\' BETWEEN ? AND ?)', range.first, range.last)
}
scope :exclude_self, -> id { where.not(id: id) }

def cannot_overlap_another_event
  range = Range.new from, to
  overlaps = Event.exclude_self(id).in_range(range)
  overlap_error unless overlaps.empty?
end

def overlap_error
  errors.add(:overlap_error, 'There is already an event scheduled in this hour!')
end

development.log

....
Started POST "/schedules" for 218.33.213.91 at 2016-04-07 11:44:59 +0000
Processing by SchedulesController#create as HTML
...
      [1m[35m (0.5ms)[0m  begin transaction
      [1m[36m (0.5ms)[0m  [1mSELECT COUNT(*) FROM "events" WHERE ("events"."id" IS NOT NULL) AND (('from' BETWEEN '2016-04-07 07:00:00.000000' AND '2016-04-07 07:20:00.000000'))[0m
      [1m[35m (0.3ms)[0m  SELECT COUNT(*) FROM "events" WHERE ("events"."id" IS NOT NULL) AND (('from' BETWEEN '2016-04-07 07:05:00.000000' AND '2016-04-07 07:30:00.000000'))
      [1m[36mSQL (0.5ms)[0m  [1mINSERT INTO "schedules" ("title", "departure_date", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)[0m  [["title", "test title"], ["departure_date", "2016-04-10"], ["user_id", 1], ["created_at", "2016-04-07 11:45:00.061460"], ["updated_at", "2016-04-07 11:45:00.061460"]]
...

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-4


    【解决方案1】:

    好吧,如果您需要服务器端验证,您可以在模型类中实现一些 custom 验证器:

    validate :cannot_overlap_another_event
    

    接下来你需要自己编写这个方法:

    def cannot_overlap_another_event
      range = Range.new from, to
      overlaps = Appointment.exclude_self(id).in_range(range)
      overlap_error unless overlaps.empty?
    end
    

    解释这段代码的作用,您创建了一个带有fromto 日期的Range 对象。然后它使用辅助范围排除Event 本身并检查此范围内是否有事件。

    scope :in_range, -> range {
      where('(from BETWEEN ? AND ?)', range.first, range.last)
    }
    scope :exclude_self, -> id { where.not(id: id) }
    

    overlap_error 是一种填充模型的错误哈希以显示在屏幕上的方法:

    def overlap_error
      errors.add(:overlap_error, 'There is already an event scheduled in this hour!')
    end
    

    【讨论】:

    • 感谢您的回答,@MurifoX。虽然我编辑了event.rbNoMethodError (undefined method "from" for 2016-04-06 12:39:00 UTC..2016-04-06 12:40:00 UTC:Range):app/models/event.rb:17:in "block in &lt;class:Event&gt;"app/models/event.rb:23:in "cannot_overlap_another_event"app/controllers/schedules_controller.rb:37:in "create",但在我尝试创建新计划时显示。我在问题的底部更新了event.rb
    • @SamuraiBlue Ops,我的错,你应该使用 range.firstrange.last,而不是你的模型属性名称。
    • 感谢您的快速回复,@MurifoX。显示另一个错误SQLite3::SQLException (no such column: start):。很抱歉在我搜索此错误的含义之前再次询问。
    • 感谢您的更新,@MurifoX。下一个错误是SQLite3::SQLException (near "from": syntax error):。感谢您的合作。
    • @SamuraiBlue 也许您的日期格式错误 (stackoverflow.com/questions/8187288/sql-select-between-dates)。此外,在开发中使用 postgres 等其他数据库也是一个好习惯。这样,当您使用另一个不是 SQLite3 的数据库时,在生产环境中进行测试时就不会感到意外
    【解决方案2】:

    看看这个https://makandracards.com/makandra/984-test-if-two-date-ranges-overlap-in-ruby-or-rails。关键是定义一个范围来检查重叠的兄弟姐妹。是这样的:

    # Return a scope for all interval overlapping the given interval, including the given interval itself
      named_scope :overlapping, lambda { |interval| {
        :conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
      }}
    

    您也可以查看此 gem:https://github.com/robinbortlik/validates_overlap 我认为它可以提供帮助。

    【讨论】:

    • 那个 validates_overlap gem 是救命稻草!这应该是真正被接受的答案!谢谢!
    【解决方案3】:

    在您的 EventsController#CreateEventsController#Update 方法中检查旧时间之间的新时间,例如:

    events = Event.where(:departure_date => event_params[:departure_date])
    events.each do |event|
     if event_params[:to].between?(event.to, event.from) || event_params[:from].between?(event.to, event.from)
      flash[:error] = "Current Slot Time is already taken!"
      redirect_to :back
      return
     end
    end
    

    【讨论】:

      【解决方案4】:

      我在一个项目中遇到了同样的问题,我的解决方案是在模型中包含一个验证方法,所以我有一个用于休假的模型,我必须添加时间段(失效),注意这些失效不重叠;假设您有一组失效 (active_permits) 并且您刚刚从表单中获得了一个新失效 (lapse),那么只有两个基本规则可以确定这两个时间段是否重叠: lapse[:end] 必须更小 (即发生早于)active_permit[:start] 或 lapse[:start] 必须大于(发生晚于)active_permit[:end],因此除非满足这些规则之一,否则两个周期会重叠。代码必须是这样的:

      def lapse_validation(lapse)
        error = 0
        @active_permits.each do |active_permit|
          unless (lapse[:end] < active_permit[:start] || lapse[:start] > active_permit[:end])
            error = 1
            errors.add(:new_permit, ": The dates you entered overlapp an existing leave period")
          end
        end
        return error
      end
      

      您可以在此处找到有关重叠时段的更详细讨论:https://www.soliantconsulting.com/blog/determining-two-date-ranges-overlap/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多