Class: Course::Material::Folder

Inherits:
ApplicationRecord show all
Includes:
Course::ModelComponentHost::Component, DuplicationStateTrackingConcern
Defined in:
app/models/course/material/folder.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DuplicationStateTrackingConcern

#clear_duplication_flag, #duplicating?, #set_duplication_flag

Instance Attribute Details

#children_countObject (readonly)

Returns the number of subfolders in current folder.


28
29
30
31
32
# File 'app/models/course/material/folder.rb', line 28

calculated :children_count, (lambda do
  select("count('*')").
    from('course_material_folders children').
    where('children.parent_id = course_material_folders.id')
end)

#material_countObject (readonly)

Returns the number of files in current folder.


21
22
23
24
# File 'app/models/course/material/folder.rb', line 21

calculated :material_count, (lambda do
  Course::Material.select("count('*')").
    where('course_materials.folder_id = course_material_folders.id')
end)

Class Method Details

.after_course_initialize(course) ⇒ Object


45
46
47
48
49
# File 'app/models/course/material/folder.rb', line 45

def self.after_course_initialize(course)
  return if course.persisted? || course.root_folder?

  course.material_folders.build(name: 'Root')
end

.without_empty_linked_folderObject

Filter out the empty linked folders (i.e. Folder with an owner).


39
40
41
42
43
# File 'app/models/course/material/folder.rb', line 39

def self.without_empty_linked_folder
  select do |folder|
    folder.concrete? || folder.children_count != 0 || folder.material_count != 0
  end
end

Instance Method Details

#before_duplicate_save(_duplicator) ⇒ Object


161
162
163
# File 'app/models/course/material/folder.rb', line 161

def before_duplicate_save(_duplicator)
  self.name = next_valid_name
end

#build_materials(files) ⇒ Object


51
52
53
54
55
# File 'app/models/course/material/folder.rb', line 51

def build_materials(files)
  files.map do |file|
    materials.build(name: Pathname.normalize_filename(file.original_filename), file: file)
  end
end

#concrete?Boolean

Check if the folder is standalone and does not belongs to any owner(e.g. assessments).


70
71
72
# File 'app/models/course/material/folder.rb', line 70

def concrete?
  owner_id.nil?
end

#effective_start_atDateTime

Take Course#advance_start_at_duration into account when calculating folder's start datetime.


99
100
101
# File 'app/models/course/material/folder.rb', line 99

def effective_start_at
  start_at - course&.advance_start_at_duration
end

#initialize_duplicate(duplicator, other) ⇒ Object


103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'app/models/course/material/folder.rb', line 103

def initialize_duplicate(duplicator, other)
  # Do not shift the time of root folder
  self.start_at = other.parent_id.nil? ? Time.zone.now : duplicator.time_shift(other.start_at)
  self.end_at = duplicator.time_shift(other.end_at) if other.end_at
  self.updated_at = other.updated_at
  self.created_at = other.created_at
  self.owner = duplicator.duplicate(other.owner)
  self.course = duplicator.options[:destination_course]
  initialize_duplicate_parent(duplicator, other)
  initialize_duplicate_children(duplicator, other)
  set_duplication_flag
  initialize_duplicate_materials(duplicator, other)
end

#initialize_duplicate_children(duplicator, other) ⇒ Object


135
136
137
138
139
140
141
142
143
144
145
# File 'app/models/course/material/folder.rb', line 135

def initialize_duplicate_children(duplicator, other)
  # Add only subfolders that have already been duplicated as its children.
  # If a subfolder has been selected for duplication, but has not yet been duplicated,
  # then the subfolder's duplicate will be added as a child of the current folder later on when
  # the child is being duplicated and `initialize_duplicate_parent` is being called on the duplicated
  # child folder. `duplicator.duplicate(folder)` will merely retrieve the subfolder's duplicate,
  # rather than trigger the duplication of the subfolder.
  children << other.children.
              select { |folder| duplicator.duplicated?(folder) }.
              map { |folder| duplicator.duplicate(folder) }
end

#initialize_duplicate_materials(duplicator, other) ⇒ Object


147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'app/models/course/material/folder.rb', line 147

def initialize_duplicate_materials(duplicator, other)
  self.materials = if other.concrete?
                     # Create associations only for materials which have been duplicated. For child materials
                     # that are duplicated later, the duplicated material will parent itself under the
                     # current folder. (see `Course::Material#initialize_duplicate`)
                     other.materials.
                       select { |material| duplicator.duplicated?(material) }.
                       map { |material| duplicator.duplicate(material) }
                   else
                     # If folder is virtual, all it's materials are duplicated by default.
                     duplicator.duplicate(other.materials).compact
                   end
end

#initialize_duplicate_parent(duplicator, other) ⇒ Object


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/models/course/material/folder.rb', line 117

def initialize_duplicate_parent(duplicator, other)
  duplicating_course_root_folder = duplicator.mode == :course && other.parent.nil?
  self.parent = if duplicating_course_root_folder
                  nil
                elsif duplicator.duplicated?(other.parent)
                  duplicator.duplicate(other.parent)
                else
                  # If parent has not been duplicated yet, put the current duplicate under the root folder
                  # temporarily. The folder will be re-parented only afterwards when the parent is being
                  # duplicated. This will be done when `#initialize_duplicate_children` is called on the
                  # duplicated parent folder.
                  #
                  # If the folder's parent is not selected for duplication, the current duplicated folder
                  # will remain a child of the root folder.
                  duplicator.options[:destination_course].root_folder
                end
end

#next_uniq_child_name(item) ⇒ String

Finds a unique name for item among the folder's existing contents by appending a serial number to it, if necessary. E.g. "logo.png" will be named "logo.png (1)" if the files named "logo.png" and "logo.png (0)" exist in the folder.


80
81
82
83
84
85
86
87
# File 'app/models/course/material/folder.rb', line 80

def next_uniq_child_name(item)
  taken_names = contents_names(item).map(&:downcase)
  name_generator = FileName.new(item.name, path: :relative, add: :always,
                                           format: '(%d)', delimiter: ' ')
  new_name = item.name
  new_name = name_generator.create while taken_names.include?(new_name.downcase)
  new_name
end

#next_valid_nameString

Finds a unique name for the current folder among its siblings.


92
93
94
# File 'app/models/course/material/folder.rb', line 92

def next_valid_name
  parent.next_uniq_child_name(self)
end

#pathPathname

Returns the path of the folder, note that '/' will be returned for root_folder


60
61
62
63
64
65
# File 'app/models/course/material/folder.rb', line 60

def path
  folders = ancestors.reverse + [self]
  folders.shift # Remove the root folder
  path = File.join('/', folders.map(&:name))
  Pathname.new(path)
end