module TrackRecordSections::SectionsMixin

All the guts of a Sections class inside a module, so multiple inheritance-like behaviour can be used for Reports which expose the Sections interface herein, along with their own Calculator interface for overall totals.

Public Instance Methods

each_group() { |group| ... } click to toggle source

As “#each_section()”, but for Groups.

# File lib/track_record_sections.rb, line 290
def each_group
  @groups.each_value do | group |
    yield( group )
  end
end
each_section() { |section| ... } click to toggle source

Retrieve all Sections, perhaps in order to assign caller data. Call with a block, invoked with a Section instance at each iteration. The returned Section order is arbitrary. Use e.g. “iterate()” for deterministic ordering.

# File lib/track_record_sections.rb, line 282
def each_section
  @sections.each_value do | section |
    yield( section )
  end
end
each_task() { |task, section, is_new_section, group, is_new_group| ... } click to toggle source

Iterate over the tasks provided in the constructor, in the same order as provided in the construtor. The caller’s block is invoked with parameters of the task, a Section, a flag indicating that the task started a new Section if true, a Group and a flag indicating that the task started a new Group if true.

# File lib/track_record_sections.rb, line 270
def each_task
  tasks.each do | task |
    section, is_new_section, group, is_new_group = retrieve( task.id.to_s )
    yield( task, section, is_new_section, group, is_new_group )
  end
end
group( task_id_str ) click to toggle source

Obtain a Group instance related to the given task, specified as a task ID in string form.

# File lib/track_record_sections.rb, line 236
def group( task_id_str )
  @task_to_group_map[ task_id_str ]
end
initialize_sections( tasks, optionalClass = Section ) click to toggle source

Initialize by passing in the array of non-nil Task instances that will be used for group and section processing. This must already be in an order that makes sense for such processing, e.g. sorted by project, customer, task title.

Ideally, pass an ActiveRecord::Relation instance that’ll return a set of appropriately sorted objects for most efficient operation.

In the optional section parameter, pass a Class which will be instantiated instead of TrackRecordSections::Section whenever a new section is being defined. The class must include SectionMixin along with whatever other behaviour it might define (which is of interest only to the caller and ignored herein). The custom class constructor must have the same signature as, and must call module method “initialize_section()”.

# File lib/track_record_sections.rb, line 153
def initialize_sections( tasks, optionalClass = Section )

  # Create new Section objects and a map between task IDs and
  # those objects, so we can get to them quickly.
  #
  # We run through the tasks in the array order. Whether or
  # not a task needs a new Section object instance, because
  # it represents a hithertoo unencountered project/customer
  # combination, tells us that this task's row will sit at
  # the top of a new section in e.g. a generated report. Keep
  # track of that flag in another task-keyed hash as it'll be
  # very useful for section-aware generators or similar views.
  #
  # Groups are also handled in this loop, creating a set of
  # flags, but not needing new section objects. While sections
  # are uniquely identified by a combination of project and
  # customer, groups are uniquely identified by title. Group
  # titles are based upon the presence of colon characters in
  # task names (everything up to the first colon).

  @task_to_section_map = {}
  @task_starts_section = {}
  @sections            = {}
  section_identifier   = 0

  # Groups are harder as we have to consider the no-colon-in-title
  # "ungrouped" tasks and how we need to treat it as a group change
  # if we change from ungrouped to group task, or vice versa.

  @task_to_group_map   = {}
  @task_starts_group   = {}
  @groups              = {}
  previous_group_id    = nil # Intentional potential first-row match to "ungrouped" task

  tasks.each do | task |
    task_id_str = task.id.to_s

    project_id  = task.project.try( :id ) || '-'
    customer_id = task.project.try( :customer ).try( :id ) || '-'
    section_id  = "#{ customer_id }_#{ project_id }"
    found       = @sections[ section_id ]

    if ( found.nil? )
      @task_starts_section[ task_id_str ] = true
      @task_to_section_map[ task_id_str ] = @sections[ section_id ] = optionalClass.new( section_identifier += 1, task.project )
    else
      @task_starts_section[ task_id_str ] = false
      @task_to_section_map[ task_id_str ] = found
    end

    group    = Group.new( task )
    group_id = group.title()
    found    = @groups[ group_id ]

    if ( found.nil? )
      @task_starts_group[ task_id_str ] = true
      @task_to_group_map[ task_id_str ] = @groups[ group_id ] = group
    else
      @task_starts_group[ task_id_str ] = ( previous_group_id != group_id )
      @task_to_group_map[ task_id_str ] = found
    end

    previous_group_id = group_id
  end
end
reassess_start_flags_using( tasks ) click to toggle source

If you want to change the task list at some later time, with some tasks omitted, call here to retain all section and group data but reasses the “this task starts a new section/group” flag settings in view of the new task list.

The new task list can be a reordered version of the original and can have tasks omitted from the original, but may contain no new tasks. Results are undefined in such a case.

The internal record of the task list is necessarily updated (else it would mismatch the flags) so the “#each_task” enumerator will, after calling here, return the new tasks list, not the original.

# File lib/track_record_sections.rb, line 309
def reassess_start_flags_using( tasks )
  previous_section = nil
  previous_group   = nil

  tasks.each do | task |
    task_id_str = task.id.to_s
    s           = section( task_id_str )
    g           = group( task_id_str )

    @task_starts_section[ task_id_str ] = ( s != previous_section )
    @task_starts_group  [ task_id_str ] = ( g != previous_group   )

    previous_section = s
    previous_group   = g
  end
end
retrieve( task_id_str ) click to toggle source

For the given task, which must be an instance present in the array of tasks given in the constructor, retrieve an array containing in order first to last index: a Section, a flag indicating that the task started a new Section if true, a Group and a flag indicating that the task started a new Group if true.

The task must be specified by its ID, as a string.

# File lib/track_record_sections.rb, line 255
def retrieve( task_id_str )
  [
    @task_to_section_map[ task_id_str ],
    @task_starts_section[ task_id_str ],
    @task_to_group_map  [ task_id_str ],
    @task_starts_group  [ task_id_str ]
  ]
end
section( task_id_str ) click to toggle source

Obtain a Section instance related to the given task, specified as a task ID in string form.

# File lib/track_record_sections.rb, line 222
def section( task_id_str )
  @task_to_section_map[ task_id_str ]
end
starts_new_group?( task_id_str ) click to toggle source

Returns ‘true’ if the given task, specified as a task ID in string form, starts a new Group.

# File lib/track_record_sections.rb, line 243
def starts_new_group?( task_id_str )
  @task_starts_group[ task_id_str ]
end
starts_new_section?( task_id_str ) click to toggle source

Returns ‘true’ if the given task, specified as a task ID in string form, starts a new Section.

# File lib/track_record_sections.rb, line 229
def starts_new_section?( task_id_str )
  @task_starts_section[ task_id_str ]
end