Handle date and time with Ruby on Rails

3

At this moment I am developing my web application, which has competitions. These competitions have, among others, these two fields:

-deadline, datetime

-finished, booblean

First of all, I need to know how I can do so that the user can choose the year, month, day, hours and minutes in deadline. I have tried with date_select , but I can only choose year, month and day.

On the other hand, I want that when the time and date of the system is the same as deadline , the field finished changes to true , how is this achieved in rails? Does the system have to be constantly checking?

Thank you very much!

    
asked by Jorge Vela Plaza 15.07.2017 в 12:36
source

2 answers

1
  

... how can I do so that the user can choose the deadline   year, month, day, hours and minutes

Use select_datetime , which will provide fields for year, month, day, hour, minutes and seconds.

  

... when the time and date of the system is the same as deadline , the   field finished change to true , how do you get this in rails?

The simplest way is to use some gem for scheduling tasks (eg Delayed Job , Sidekiq , Resque , among others), with which you program a task every time you generate a new competition record.

For example, you could create a method in your model (for the example I call it Competicion ) that is responsible for updating the field finished and send the notification email 1 :

class Competicion < ApplicationRecord
  # ...

  def fin_competicion
    self.update(:finished, true)
    CompeticionMailer.fin(self).deliver_now
  end
end

And you would schedule the task from your controller using Delayed Job 2 :

class CompeticionController < ApplicationController
  # ...

  def create
    @competicion = Competicion.new(competicion_params)

    if @competicion.save
      @competicion.delay(run_at: @competicion.deadline).fin_competicion
      # código para creación exitosa
    else
      # código para creación no exitosa
    end
  end

  # ...
end

1 It is assumed that the mailer CompeticionMailer was created with the fin method.

2 It is assumed that Delayed Job has already been installed and configured.

    
answered by 15.07.2017 / 13:22
source
1

To choose date and time, the simplest thing is to use select_datetime . Now if you do not want to be full of selects and use something more functional and nice, you could try a javascript library like Bootstrap Datepicker . There is also a gem that makes it easier for you to integrate this functionality.

Now, regarding your implementation, as a first thing a detail: I think the field finished is over. You could perfectly define a method that replaces its function:

def finished?
  deadline < Time.current
end

to consult the BD would not be necessary either:

def self.finished
  where('deadline < ?', Time.current)
end

something more useful could be to mark in the BD if the notification for that competition was sent or not.
Now, regarding the underlying problem, it is not something simple to implement, because you would need a process constantly consulting the BD for the competitions that have passed the deadline, nor is there anything native in rails that allows you to do what you ask. The solutions that come to mind are:

  • The easiest thing would be to create a task rake that performs the query and notifications and schedule it with whenever to run every so often The problem with this implementation is that when executing a task rake, a new rails process starts, apart from the one you are already running, which consumes time, memory and cpu, so schedule to run, for example, every 1 minute is enough and it is more likely that sooner rather than later you will have performance problems on your server.

  • Integrate sidekiq into your project which is a much lighter way of handling tasks as it executes them through threads instead of an entire process as a task would. Then at the moment that the deadline of the competition is created or updated, schedule a check for the date of the deadline to see if it is over. Your options are:

    • Use something like competicion.delay_until(competicion.deadline + 5.seconds).envia_notificacion (you can add a few seconds to give it a margin time and send the notification when appropriate). This is the simplest option, however it consumes more resources, since it serializes the current object and puts it in memory.
    • Create a worker and execute something like NotificaWorker.perform_at(competicion.deadline, competicion.id) . This takes only a little more work, but it is much lighter for sidekiq, since you are only sending the id of the competition, which obviously must be sought from within the worker.

    The problem of integrating sidekiq is that you have to install a new service (redis) and that you could eventually have problems with notifications that are not sent if you do not do the validations that correspond or that are scheduled notifications that never take place. In the background, make your logic that sends notifications tolerant to failures or exceptional situations. Just an example, imagine a troll that fills you with competitions with deadline to the year 2100, competitions that will not be notified in many decades and that little by little will be removing memory to notifications that do need it. In the worst case you will saturate your memory and kill the server.

What would I choose? A combination of both solutions, starting with integrating sidekiq and in case you find that there are notifications that are not being sent by XYZ anyway that may be happening, do a final sweep with a task rake every certain amount of hours reviewing for which competitions your notifications are still not sent.

    
answered by 15.07.2017 в 17:14