Skip to content

Synchronising the calendar of availabilities

Renato Mascarenhas edited this page Aug 18, 2016 · 1 revision

Supplier implementations typically contain two parts: synchronising the properties and their information (title, descriptions, images); and synchronising the calendar of availabilities, which indicate what dates are available or not and what the rates are.

These activities are represented by the two kinds of workers supplier implementations are able to provide: metadata and availabilities. The former is documented in another Wiki page - it is suggested that that page is read before this one, since it includes more extensive information related to the way synchronising is supposed to work on Concierge; this page, on the other hand, is focused solely on the process of synchronising the calendar of availabilities for a given property on Concierge.

A working example

# apps/workers/suppliers/acme/availabilities.rb

module Workers::Suppliers::Acme

  class Availabilities

    attr_reader :synchronisation, :host

    def initialize(host)
      @host = host
      @synchronisation = Workers::CalendarSynchronisation.new(host)
    end

    def perform
      properties = properties_from(host)

      properties.each do |property|
        synchronisation.start(property.identifier) do
          calendar = Roomorama::Calendar.new(property.identifier)
          dates = client.fetch_calendar(property.identifier)

          dates.each do |data|
            entry = Roomorama::Calendar::Entry.new(
              date: data["date"],
              available: data["available"] == 1,
              nightly_rate: data["price"]
            )

            calendar.add(entry)
          end

          Result.new(calendar)
        end
      end

      synchronisation.finish!
    end

    private

    def properties_from(host)
      PropertyRepository.from_host(host)
    end

    def client
      @client ||= Acme::Client.new(credentials)
    end

    def credentials
      @credentials ||= Concierge::Credentials.for("acme")
    end
  end

end

Concierge::Announcer.on("availabilities.Acme") do |host|
  Workers::Suppliers::Acme::Availabilities.new(host).perform
end

Let's analyse the code above step by step.


def initialize(host)
  @host = host
  @synchronisation = Workers::CalendarSynchronisation.new(host)
end

This is very similar to what is done in the initialization of the metadata worker. An instance of Host is received. However, this time the supporting core class from Concierge is Workers::CalendarSynchronisation. A similar interface is exposed, which makes the process more seamless.


def perform
  properties = properties_from(host)

# ...
def properties_from(host)
  PropertyRepository.from_host(host)
end

At the first line of the perform method, all properties from the given host are loaded from the database. This step is important and ensures that only previously synchronised properties have their calendars fetched from the supplier.

Important: As a rule of thumb, calendar implementations should scope their calendar calls only to properties previously synchronised (i.e., properties in the properties table.) This can substantially speed up the synchronisation process, as well as reduce the number of network requests to supplier APIs, which typically impose some form of rate limitting.


properties.each do |property|
  synchronisation.start(property.identifier) do
  calendar = Roomorama::Calendar.new(property.identifier)
  dates = client.fetch_calendar(property.identifier)

This time, the code iterates over each property previously sychronised by Concierge for this host. For each one of them a Roomorama::Calendar instance is created. As with Workers::PropertySynchronisation, the Workers::CalendarSynchronisation class also expects an instance of Result to be returned - however, the result should wrap a Roomorama::Calendar when successful.

The fetch_calendar is a ficticious method which is supposed to download the calendar of availabilities for a given property.


dates.each do |data|
  entry = Roomorama::Calendar::Entry.new(
    date: data["date"],
    available: data["available"] == 1,
    nightly_rate: data["price"]
  )

  calendar.add(entry)
end

An entry (that is, an instance of Roomorama::Calendar::Entry) represents a single date in the availabilities calendar. It groups the date, whether or not it is available, and what the rates are (see the class' documentation for a list of supported fields.)

At the end of the process, the entry is added to the calendar, meaning it will be sent to Roomorama via API.


Result.new(calendar)

As was mentioned previously, the Workers::CalendarSynchronisation#start method expects an instance of Result to be returned back, and it should wrap the corresponding Roomorama::Calendar instance when successful.


Concierge::Announcer.on("availabilities.Acme") do |host|
  Workers::Suppliers::Acme::Availabilities.new(host).perform
end

Supplier implementations are expected to listen for the availabilities.<supplier_name> event. When it is triggered, an instance of Host is passed, and the calendar of availabilities for all properties of that host should be sycnhronised.

Final Considerations

  • In case the block passed to Workers::CalendarSynchronisation#start returns an unsuccessful Result, the error is propertly stored to the database, along with the context. No error saving is necessary after the method is called, therefore, if this practice is followed.

  • There is multi-unit support for calendar synchronisation. The Roomorama::Calendar class has a add_unit method, which receives another instance of Roomorama::Calendar, representing the availabilities for one of the units of the parent property.

  • If the supplier does not provide a breakdown of prices per date, but only stays (price for check-in/check-out combination), then the support Roomorama::Calendar::StaysMapper class can be used to automatically generate a valid set of Roomorama::Calendar::Entry objects. See the documentation of that class for more information.