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).

Returns:

  • (Boolean)

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.

Returns:

  • (DateTime)

    The shifted start_at 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.

Parameters:

  • item (#name)

    Folder or Material to find unique name for.

Returns:

  • (String)

    A unique name.


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.

Returns:

  • (String)

    A unique name.


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

Returns:

  • (Pathname)

    The path of the 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