sortable_events.rb 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. module SortableEvents
  2. extend ActiveSupport::Concern
  3. included do
  4. validate :validate_events_order
  5. end
  6. EVENTS_ORDER_KEY = 'events_order'.freeze
  7. def description_events_order(*args)
  8. self.class.description_events_order(*args)
  9. end
  10. module ClassMethods
  11. def can_order_created_events!
  12. raise 'Cannot order events for agent that cannot create events' if cannot_create_events?
  13. prepend AutomaticSorter
  14. end
  15. def can_order_created_events?
  16. include? AutomaticSorter
  17. end
  18. def cannot_order_created_events?
  19. !can_order_created_events?
  20. end
  21. def description_events_order(events = 'events created in each run', events_order_key = EVENTS_ORDER_KEY)
  22. <<-MD.lstrip
  23. To specify the order of #{events}, set `#{events_order_key}` to an array of sort keys, each of which looks like either `expression` or `[expression, type, descending]`, as described as follows:
  24. * _expression_ is a Liquid template to generate a string to be used as sort key.
  25. * _type_ (optional) is one of `string` (default), `number` and `time`, which specifies how to evaluate _expression_ for comparison.
  26. * _descending_ (optional) is a boolean value to determine if comparison should be done in descending (reverse) order, which defaults to `false`.
  27. Sort keys listed earlier take precedence over ones listed later. For example, if you want to sort articles by the date and then by the author, specify `[["{{date}}", "time"], "{{author}}"]`.
  28. Sorting is done stably, so even if all events have the same set of sort key values the original order is retained. Also, a special Liquid variable `_index_` is provided, which contains the zero-based index number of each event, which means you can exactly reverse the order of events by specifying `[["{{_index_}}", "number", true]]`.
  29. MD
  30. end
  31. end
  32. def can_order_created_events?
  33. self.class.can_order_created_events?
  34. end
  35. def cannot_order_created_events?
  36. self.class.cannot_order_created_events?
  37. end
  38. def events_order(key = EVENTS_ORDER_KEY)
  39. options[key]
  40. end
  41. module AutomaticSorter
  42. def check
  43. return super unless events_order
  44. sorting_events do
  45. super
  46. end
  47. end
  48. def receive(incoming_events)
  49. return super unless events_order
  50. # incoming events should be processed sequentially
  51. incoming_events.each do |event|
  52. sorting_events do
  53. super([event])
  54. end
  55. end
  56. end
  57. def create_event(event)
  58. if @sortable_events
  59. event = build_event(event)
  60. @sortable_events << event
  61. event
  62. else
  63. super
  64. end
  65. end
  66. private
  67. def sorting_events(&block)
  68. @sortable_events = []
  69. yield
  70. ensure
  71. events, @sortable_events = @sortable_events, nil
  72. sort_events(events).each do |event|
  73. create_event(event)
  74. end
  75. end
  76. end
  77. private
  78. EXPRESSION_PARSER = {
  79. 'string' => ->string { string },
  80. 'number' => ->string { string.to_f },
  81. 'time' => ->string { Time.zone.parse(string) },
  82. }
  83. EXPRESSION_TYPES = EXPRESSION_PARSER.keys.freeze
  84. def validate_events_order(events_order_key = EVENTS_ORDER_KEY)
  85. case order_by = events_order(events_order_key)
  86. when nil
  87. when Array
  88. # Each tuple may be either [expression, type, desc] or just
  89. # expression.
  90. order_by.each do |expression, type, desc|
  91. case expression
  92. when String
  93. # ok
  94. else
  95. errors.add(:base, "first element of each #{events_order_key} tuple must be a Liquid template")
  96. break
  97. end
  98. case type
  99. when nil, *EXPRESSION_TYPES
  100. # ok
  101. else
  102. errors.add(:base, "second element of each #{events_order_key} tuple must be #{EXPRESSION_TYPES.to_sentence(last_word_connector: ' or ')}")
  103. break
  104. end
  105. if !desc.nil? && boolify(desc).nil?
  106. errors.add(:base, "third element of each #{events_order_key} tuple must be a boolean value")
  107. break
  108. end
  109. end
  110. else
  111. errors.add(:base, "#{events_order_key} must be an array of arrays")
  112. end
  113. end
  114. # Sort given events in order specified by the "events_order" option
  115. def sort_events(events, events_order_key = EVENTS_ORDER_KEY)
  116. order_by = events_order(events_order_key).presence or
  117. return events
  118. orders = order_by.map { |_, _, desc = false| boolify(desc) }
  119. Utils.sort_tuples!(
  120. events.map.with_index { |event, index|
  121. interpolate_with(event) {
  122. interpolation_context['_index_'] = index
  123. order_by.map { |expression, type, _|
  124. string = interpolate_string(expression)
  125. begin
  126. EXPRESSION_PARSER[type || 'string'.freeze][string]
  127. rescue
  128. error "Cannot parse #{string.inspect} as #{type}; treating it as string"
  129. string
  130. end
  131. }
  132. } << index << event # index is to make sorting stable
  133. },
  134. orders
  135. ).collect!(&:last)
  136. end
  137. end