Class: Ace::Assign::Molecules::AssignmentManager

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/assign/molecules/assignment_manager.rb

Overview

Manages assignment YAML file operations.

Handles creation, loading, and updating of assignment.yaml files. Uses ace-b36ts for assignment ID generation.

Instance Method Summary collapse

Constructor Details

#initialize(cache_base: nil) ⇒ AssignmentManager

Returns a new instance of AssignmentManager.

Parameters:

  • cache_base (String) (defaults to: nil)

    Base cache directory



16
17
18
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 16

def initialize(cache_base: nil)
  @cache_base = cache_base || Ace::Assign.cache_dir
end

Instance Method Details

#clear_currentObject

Clear current assignment selection

Removes the .current symlink, falling back to .latest resolution



135
136
137
138
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 135

def clear_current
  current_symlink = File.join(@cache_base, ".current")
  File.delete(current_symlink) if File.symlink?(current_symlink)
end

#create(name:, source_config:, description: nil, parent: nil) ⇒ Models::Assignment

Create a new assignment

Parameters:

  • name (String)

    Assignment name

  • description (String, nil) (defaults to: nil)

    Assignment description

  • source_config (String)

    Path to source config file

  • parent (String, nil) (defaults to: nil)

    Parent assignment ID for hierarchy linking

Returns:



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 27

def create(name:, source_config:, description: nil, parent: nil)
  # Ensure cache base directory exists before generate_assignment_id
  FileUtils.mkdir_p(@cache_base)

  assignment_id = generate_assignment_id
  cache_dir = File.join(@cache_base, assignment_id)

  # Create directories
  FileUtils.mkdir_p(cache_dir)
  FileUtils.mkdir_p(File.join(cache_dir, "steps"))
  FileUtils.mkdir_p(File.join(cache_dir, "reports"))

  now = Time.now.utc

  assignment = Models::Assignment.new(
    id: assignment_id,
    name: name,
    description: description,
    created_at: now,
    updated_at: now,
    source_config: source_config,
    cache_dir: cache_dir,
    parent: parent
  )

  # Write assignment.yaml
  write_assignment_file(assignment)

  # Update .latest symlink for O(1) active assignment lookup
  update_latest_symlink(assignment_id)

  assignment
end

#current_idString?

Get the currently selected assignment ID (from .current symlink)

Returns:

  • (String, nil)

    Current assignment ID or nil



143
144
145
146
147
148
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 143

def current_id
  current_symlink = File.join(@cache_base, ".current")
  return nil unless File.symlink?(current_symlink)

  File.basename(File.readlink(current_symlink))
end

#delete(assignment_id) ⇒ Boolean

Delete an assignment’s cache directory and clean up symlinks

Parameters:

  • assignment_id (String)

    Assignment ID to delete

Returns:

  • (Boolean)

    true if deleted, false if not found



154
155
156
157
158
159
160
161
162
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 154

def delete(assignment_id)
  dir = File.join(@cache_base, assignment_id)
  return false unless File.directory?(dir)

  cleanup_symlink(".current", assignment_id)
  cleanup_symlink(".latest", assignment_id)
  FileUtils.rm_rf(dir)
  true
end

#find_activeModels::Assignment?

Find the most recent active assignment

Uses resolution order:

  1. .current symlink (explicit user selection)

  2. .latest symlink (auto, most recent)

  3. Scan all assignments (fallback)

Returns:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 83

def find_active
  return nil unless File.directory?(@cache_base)

  # Priority 1: use .current symlink if it exists (explicit selection)
  current_symlink = File.join(@cache_base, ".current")
  if File.symlink?(current_symlink)
    assignment_id = File.basename(File.readlink(current_symlink))
    assignment = load(assignment_id)
    return assignment if assignment
  end

  # Priority 2: use .latest symlink if it exists
  latest_symlink = File.join(@cache_base, ".latest")
  if File.symlink?(latest_symlink)
    assignment_id = File.basename(File.readlink(latest_symlink))
    assignment = load(assignment_id)
    return assignment if assignment
  end

  # Fallback: find all assignment directories
  assignments = Dir.glob(File.join(@cache_base, "*", "assignment.yaml"))
    .map { |f| load_from_file(f) }
    .compact
    .sort_by(&:updated_at)
    .reverse

  assignments.first
end

#listArray<Models::Assignment>

List all assignments

Returns:



192
193
194
195
196
197
198
199
200
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 192

def list
  return [] unless File.directory?(@cache_base)

  Dir.glob(File.join(@cache_base, "*", "assignment.yaml"))
    .map { |f| load_from_file(f) }
    .compact
    .sort_by(&:updated_at)
    .reverse
end

#load(assignment_id) ⇒ Models::Assignment?

Load an existing assignment by ID

Parameters:

  • assignment_id (String)

    Assignment ID

Returns:



65
66
67
68
69
70
71
72
73
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 65

def load(assignment_id)
  cache_dir = File.join(@cache_base, assignment_id)
  assignment_file = File.join(cache_dir, "assignment.yaml")

  return nil unless File.exist?(assignment_file)

  data = YAML.safe_load_file(assignment_file, permitted_classes: [Time, Date])
  Models::Assignment.from_h(data, cache_dir: cache_dir)
end

#set_current(assignment_id) ⇒ Object

Set current assignment via .current symlink

Parameters:

  • assignment_id (String)

    Assignment ID to set as current

Raises:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 116

def set_current(assignment_id)
  assignment = load(assignment_id)
  raise AssignmentErrors::NotFound, "Assignment '#{assignment_id}' not found" unless assignment

  current_symlink = File.join(@cache_base, ".current")

  # Remove old symlink if it exists
  File.delete(current_symlink) if File.symlink?(current_symlink)

  # Create new symlink
  target_dir = File.join(@cache_base, assignment_id)
  File.symlink(target_dir, current_symlink)

  assignment
end

#update(assignment) ⇒ Models::Assignment

Update assignment metadata

Parameters:

Returns:



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/ace/assign/molecules/assignment_manager.rb', line 168

def update(assignment)
  # Create new assignment with updated timestamp
  updated = Models::Assignment.new(
    id: assignment.id,
    name: assignment.name,
    description: assignment.description,
    created_at: assignment.created_at,
    updated_at: Time.now.utc,
    source_config: assignment.source_config,
    cache_dir: assignment.cache_dir,
    parent: assignment.parent
  )

  write_assignment_file(updated)

  # Update .latest symlink since this assignment was just updated
  update_latest_symlink(assignment.id)

  updated
end