Background jobs in pieces

Hegedüs Zoltán

The problem

class Topic < ActiveRecord::Base
  has_many :posts
end

class Post < ActiveRecord::Base
  belongs_to :topic
end




topic = Topic.find(1)

result = topic.title + ': ' + topic.posts.map do |post|
  "#{post.title}: #{post.body}"
end.join(';')

puts result

Topic

Post

Post

Post

posts

Export classes

class Export < ActiveRecord::Base
  belongs_to :target, polymorphic: true
  after_commit :enqueue_export_job, on: :create

  def enqueue_export_job
    "Jobs::#{self.class}".constantize.perform_later(self)
  end
end

class Export::TopicExport < Export
  include Export::HasChildren
  has_many :post_exports,
    class_name: 'Export::PostExport',
    inverse_of: :parent,
    foreign_key: :parent_id

  alias topic target

  private

  def compose_child_content
    Jobs::TopicExport::Composer.perform_later(self)
  end
end

class Export::PostExport < Export
  include HasParent
  
  alias post target
end

TopicExport

post_exports

PostExport

PostExport

PostExport

Topic

Post

Post

Post

target

target

Export-export relationships

module Export::HasChildren
  extend ActiveSupport::Concern

  included do
    has_many :child_exports,
        class_name: 'Export',
        foreign_key: :parent_id
  end

  def child_export_complete
    unless child_exports.where.not(status: :complete).exists?
        compose_child_content
    end
  end
end

module Export::HasParent
  extend ActiveSupport::Concern

  included do
    belongs_to :parent,
        class_name: 'Export'

    after_commit :notify_parent_of_status_change,
        on: :update
  end

  def notify_parent_of_status_change
    parent.child_export_complete if status == 'complete'
  end
end

Export

children

Export

Export

Export

parent

Background jobs

class Jobs::TopicExport < ActiveJob::Base
  def perform(export)
    export.topic.posts.each do |post|
      export.post_exports.create!(target: post)
    end
  end
end

class Jobs::PostExport < ActiveJob::Base
  def perform(export)
    @export = export
    export.update_attributes!(status: :complete, result: content)
  end

  attr_reader :export

  def content
    "#{export.post.title}: #{export.post.body}"
  end
end

class Jobs::TopicExport::Composer < ActiveJob::Base
  def perform(export)
    @export = export
    export.update_attributes!(status: :complete, result: content)
  end

  attr_reader :export

  def content
    export.topic.title + ':' + 
        export.post_exports.map(&:result).join(";")
  end
end

TopicExport

post_exports

PostExport

PostExport

PostExport

Topic

Post

Post

Post

target

target

Background jobs

class Jobs::TopicExport < ActiveJob::Base
  def perform(export)
    my_splittable_config.each do |config_fragment|
      export.my_child_export.create!(settings: config_fragment)
    end
  end
end

TopicExport

my_child_exports

PostExport

PostExport

PostExport

Solution

topic = Topic.find(1)

Topic

Post

Post

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

TopicExport

Topic

Post

Post

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

# tik

TopicExport

Topic

Post

Jobs::TopicExport

Post

PostExport

PostExport

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

# tik-tak

TopicExport

Jobs::PostExport

Topic

Post

Jobs::TopicExport

Post

PostExport

PostExport

PostExport

Post

Jobs::PostExport

PostExport

Post

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

# tik-tak

TopicExport

Jobs::PostExport

Topic

Post

Jobs::TopicExport

Post

PostExport

PostExport

PostExport

Post

Jobs::PostExport

PostExport

Post

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

# tik-tak

TopicExport

Jobs::PostExport

Topic

Post

Jobs::TopicExport

Post

PostExport

PostExport

PostExport

Post

Jobs::PostExport

PostExport

Post

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

# tik-tak-tik

TopicExport

Jobs::PostExport

Topic

Post

Jobs::TopicExport

Post

PostExport

PostExport

PostExport

Post

Jobs::PostExport

PostExport

Post

Jobs::TopicExport::Composer

TopicExport

PostExport

PostExport

Topic

Solution

topic = Topic.find(1)

export = Export::TopicExport.create!(target: topic)

# tik-tak-tik-tak

puts export.reload.result

TopicExport

Jobs::PostExport

Topic

Post

Jobs::TopicExport

Post

PostExport

PostExport

PostExport

Post

Jobs::PostExport

PostExport

Post

Jobs::TopicExport::Composer

TopicExport

PostExport

PostExport

TopicExport

.result =

background-jobs

By hegedus-zoltan