event.rb 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. require 'location'
  2. # Events are how Huginn Agents communicate and log information about the world. Events can be emitted and received by
  3. # Agents. They contain a serialized `payload` of arbitrary JSON data, as well as optional `lat`, `lng`, and `expires_at`
  4. # fields.
  5. class Event < ActiveRecord::Base
  6. include JsonSerializedField
  7. include LiquidDroppable
  8. acts_as_mappable
  9. json_serialize :payload
  10. belongs_to :user, optional: true
  11. belongs_to :agent, counter_cache: true
  12. has_many :agent_logs_as_inbound_event, class_name: "AgentLog", foreign_key: :inbound_event_id,
  13. dependent: :nullify
  14. has_many :agent_logs_as_outbound_event, class_name: "AgentLog", foreign_key: :outbound_event_id,
  15. dependent: :nullify
  16. scope :recent, lambda { |timespan = 12.hours.ago|
  17. where("events.created_at > ?", timespan)
  18. }
  19. after_create :update_agent_last_event_at
  20. after_create :possibly_propagate
  21. scope :expired, lambda {
  22. where("expires_at IS NOT NULL AND expires_at < ?", Time.now)
  23. }
  24. case ActiveRecord::Base.connection.adapter_name
  25. when /\Amysql/i
  26. # Protect the Event table from InnoDB's AUTO_INCREMENT Counter
  27. # Initialization by always keeping the latest event.
  28. scope :to_expire, -> { expired.where.not(id: maximum(:id)) }
  29. else
  30. scope :to_expire, -> { expired }
  31. end
  32. scope :with_location, -> {
  33. where.not(lat: nil).where.not(lng: nil)
  34. }
  35. def location
  36. @location ||= Location.new(
  37. # lat and lng are BigDecimal, but converted to Float by the Location class
  38. lat:,
  39. lng:,
  40. radius:
  41. if (h = payload[:horizontal_accuracy].presence) &&
  42. (v = payload[:vertical_accuracy].presence)
  43. (h.to_f + v.to_f) / 2
  44. else
  45. (h || v || payload[:accuracy]).to_f
  46. end,
  47. course: payload[:course],
  48. speed: payload[:speed].presence
  49. )
  50. end
  51. def location=(location)
  52. case location
  53. when nil
  54. self.lat = self.lng = nil
  55. return
  56. when Location
  57. else
  58. location = Location.new(location)
  59. end
  60. self.lat = location.lat
  61. self.lng = location.lng
  62. location
  63. end
  64. # Emit this event again, as a new Event.
  65. def reemit!
  66. agent.create_event(payload:, lat:, lng:)
  67. end
  68. # Look for Events whose `expires_at` is present and in the past. Remove those events and then update affected Agents'
  69. # `events_counts` cache columns. This method is called by bin/schedule.rb periodically.
  70. def self.cleanup_expired!
  71. transaction do
  72. affected_agents = Event.expired.group("agent_id").pluck(:agent_id)
  73. Event.to_expire.delete_all
  74. Agent.where(id: affected_agents).update_all "events_count = (select count(*) from events where agent_id = agents.id)"
  75. end
  76. end
  77. protected
  78. def update_agent_last_event_at
  79. agent.touch :last_event_at
  80. end
  81. def possibly_propagate
  82. # immediately schedule agents that want immediate updates
  83. propagate_ids = agent.receivers.where(propagate_immediately: true).pluck(:id)
  84. Agent.receive!(only_receivers: propagate_ids) unless propagate_ids.empty?
  85. end
  86. public def to_liquid
  87. Drop.new(self)
  88. end
  89. class Drop < LiquidDroppable::Drop
  90. def initialize(object)
  91. @payload = object.payload
  92. super
  93. end
  94. def liquid_method_missing(key)
  95. @payload[key]
  96. end
  97. def each(&block)
  98. @payload.each(&block)
  99. end
  100. def agent
  101. @payload.fetch(__method__) {
  102. @object.agent
  103. }
  104. end
  105. def created_at
  106. @payload.fetch(__method__) {
  107. @object.created_at
  108. }
  109. end
  110. def _location_
  111. @object.location
  112. end
  113. def as_json
  114. {
  115. location: _location_.as_json,
  116. agent: @object.agent.to_liquid.as_json,
  117. payload: @payload.as_json,
  118. created_at: created_at.as_json
  119. }
  120. end
  121. end
  122. end