module Aeternitas::Pollable

Mixin that enables the frequent polling of the receiving class. Classes including this method must implement the .poll method. Polling behaviour can be configured via {.pollable_options}. @note Can only be used by classes inheriting from ActiveRecord::Base @example

class MyWebsitePollable < ActiveRecord::Base
  includes Aeternitas::Pollable

  polling_options do
    polling_frequency :daily
    lock_key ->(obj) {obj.url}
  end

  def poll
    response = HTTParty.get(self.url)
    raise StandardError, "#{self.url} responded with #{response.status}" unless response.success?
    HttpSource.create!(content: response.parsed_response)
  end
end

Public Instance Methods

add_source(raw_content) click to toggle source

Creates a new source with the given content if it does not exist @example

#...
def poll
  response = HTTParty.get("http://example.com")
  add_source(response.parsed_response)
end
#...

@param [String] raw_content the sources raw content @return [Aeternitas::Source] the newly created or existing source

# File lib/aeternitas/pollable.rb, line 123
def add_source(raw_content)
  source = self.sources.create(raw_content: raw_content)
  return nil unless source.persisted?

  Aeternitas::Metrics.log(:sources_created, self.class)
  source
end
execute_poll() click to toggle source

This method runs the polling workflow

# File lib/aeternitas/pollable.rb, line 51
def execute_poll
  _before_poll

  begin
    guard.with_lock { poll }
  rescue StandardError => e
    if pollable_configuration.deactivation_errors.include?(e.class)
      disable_polling(e)
      return false
    elsif pollable_configuration.ignored_errors.include?(e.class)
      pollable_meta_data.has_errored!
      raise Aeternitas::Errors::Ignored, e
    else
      pollable_meta_data.has_errored!
      raise e
    end
  end

  _after_poll
rescue StandardError => e
  begin
    log_poll_error(e)
  ensure
    raise e
  end
end
guard() click to toggle source
# File lib/aeternitas/pollable.rb, line 99
def guard
  guard_key = pollable_configuration.guard_options[:key].call(self)
  guard_timeout = pollable_configuration.guard_options[:timeout]
  guard_cooldown = pollable_configuration.guard_options[:cooldown]
  Aeternitas::Guard.new(guard_key, guard_cooldown, guard_timeout)
end
poll() click to toggle source

This method implements the class specific polling behaviour. It is only called after the lock was acquired successfully.

@abstract This method must be implemented when {Aeternitas::Pollable} is included

# File lib/aeternitas/pollable.rb, line 84
def poll
  raise NotImplementedError, "#{self.class.name} does not implement #poll, required by Aeternitas::Pollable"
end
pollable_configuration() click to toggle source

Access the Pollables configuration

@return [Aeternitas::Pollable::Configuration] the pollables configuration

# File lib/aeternitas/pollable.rb, line 109
def pollable_configuration
  self.class.pollable_configuration
end
register_pollable() click to toggle source

Registers the instance as pollable.

@note Manual registration is only needed if the object was created before

{Aeternitas::Pollable} was included. Otherwise it is done automatically after creation.
# File lib/aeternitas/pollable.rb, line 92
def register_pollable
  self.pollable_meta_data ||= create_pollable_meta_data(
      state: 'waiting',
      pollable_class: self.class.name
  )
end

Private Instance Methods

_after_poll() click to toggle source

Run all postpolling methods

# File lib/aeternitas/pollable.rb, line 143
def _after_poll
  pollable_meta_data.wait! do
    pollable_meta_data.update_attributes!(
      last_polling: Time.now,
      next_polling: pollable_configuration.polling_frequency.call(self)
    )
  end

  pollable_configuration.after_polling.each { |action| action.call(self) }

  if @start_time
    execution_time = Time.now - @start_time
    Aeternitas::Metrics.log_value(:execution_time, self.class, execution_time)
    Aeternitas::Metrics.log(:guard_timeout_exceeded, self.class) if execution_time > pollable_configuration.guard_options[:timeout]
    @start_time = nil
  end
  Aeternitas::Metrics.log(:successful_polls, self.class)
end
_before_poll() click to toggle source

Run all prepolling methods

# File lib/aeternitas/pollable.rb, line 134
def _before_poll
  @start_time = Time.now
  Aeternitas::Metrics.log(:polls, self.class)

  pollable_configuration.before_polling.each { |action| action.call(self) }
  pollable_meta_data.poll!
end
inherited(other) click to toggle source
Calls superclass method
# File lib/aeternitas/pollable.rb, line 191
def inherited(other)
  super
  other.pollable_configuration = @pollable_configuration.copy
end
log_poll_error(e) click to toggle source
# File lib/aeternitas/pollable.rb, line 162
def log_poll_error(e)
  if e.is_a? Aeternitas::Guard::GuardIsLocked
    Aeternitas::Metrics.log(:guard_locked, self.class)
    Aeternitas::Metrics.log_value(:guard_timeout, self.class, e.timeout - Time.now)
  elsif e.is_a? Aeternitas::Errors::Ignored
    Aeternitas::Metrics.log(:ignored_error, self.class)
    Aeternitas::Metrics.log(:failed_polls, self.class)
  else
    Aeternitas::Metrics.log(:failed_polls, self.class)
  end
end
pollable_configuration=(config) click to toggle source
# File lib/aeternitas/pollable.rb, line 181
def pollable_configuration=(config)
  @pollable_configuration = config
end
polling_options(&block) click to toggle source

Configure the polling process. For available configuration options see {Aeternitas::Pollable::Configuration} and {Aeternitas::Pollable::DSL}

# File lib/aeternitas/pollable.rb, line 187
def polling_options(&block)
  Aeternitas::Pollable::Dsl.new(self.pollable_configuration, &block)
end