sortable_events.rb 4.6 KB

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