Module: TreeNodes
- Included in:
- ArchivalObject, ClassificationTerm, DigitalObjectComponent
- Defined in:
- backend/app/model/mixins/tree_nodes.rb
Defined Under Namespace
Modules: ClassMethods
Class Method Summary (collapse)
Instance Method Summary (collapse)
-
- (Object) absolute_position
-
- (Object) children
-
- (Boolean) has_children?
-
- (Object) order_siblings
this is just a CYA method, that might be removed in the future.
-
- (Object) set_position_in_list(target_position, sequence)
-
- (Object) set_root(new_root)
-
- (Object) siblings
-
- (Object) transfer_to_repository(repository, transfer_group = [])
-
- (Object) trigger_index_of_child_nodes
-
- (Object) update_from_json(json, opts = {}, apply_nested_records = true)
-
- (Object) update_position_only(parent_id, position)
Class Method Details
+ (Object) included(base)
7 8 9 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 7 def self.included(base) base.extend(ClassMethods) end |
Instance Method Details
- (Object) absolute_position
122 123 124 125 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 122 def absolute_position relative_position = self.position self.class.dataset.filter(:parent_name => self.parent_name).where { position < relative_position }.count end |
- (Object) children
200 201 202 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 200 def children self.class.filter(:parent_id => self.id).order(:position) end |
- (Boolean) has_children?
205 206 207 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 205 def has_children? self.class.filter(:parent_id => self.id).count > 0 end |
- (Object) order_siblings
this is just a CYA method, that might be removed in the future. We need to be sure that all the positional gaps.j
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 40 def order_siblings # add this to avoid DB constraints siblings.update(:parent_name => Sequel.lit(DB.concat('CAST(id as CHAR(10))', "'_temp'"))) # get set a list of ids and their order based on their position position_map = siblings.select(:id).order(:position).each_with_index.inject({}) { |m,( obj, index) | m[obj[:id]] = index; m } # now we do the update in batches of 200 position_map.each_slice(200) do |pm| # the slice reformat the hash...so quickly format it back pm = pm.inject({}) { |m,v| m[v.first] = v.last; m } # this ids that we're updating in this batch sibling_ids = pm.keys # the resulting update will look like: # UPDATE "ARCHIVAL_OBJECT" SET "POSITION" = (CASE WHEN ("ID" = 10914) # THEN 0 WHEN ("ID" = 10915) THEN 1 WHEN ("ID" = 10912) THEN 2 WHEN # ("ID" = 10913) THEN 3 WHEN ("ID" = 10916) THEN 4 WHEN ("ID" = 10921) # THEN 5 WHEN ("ID" = 10917) THEN 6 WHEN ("ID" = 10920) THEN 7 ELSE 0 # END) WHERE (("ROOT_RECORD_ID" = 3) AND ("PARENT_ID" = 10911) AND (NOT # "POSITION" IS NULL) AND ("ID" IN (10914, 10915, 10912, 10913, 10916, # 10921, 10917, 10920)) # ) # this should be faster than just iterating thru all the children, # since it does it in batches of 200 and limits the number of updates. siblings.filter(:id => sibling_ids).update( :position => Sequel.case(pm, 0, :id) ) end # now we return the parent_name back so our DB constraints are back on.:w siblings.update(:parent_name => self.parent_name ) end |
- (Object) set_position_in_list(target_position, sequence)
72 73 74 75 76 77 78 79 80 81 82 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 111 112 113 114 115 116 117 118 119 120 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 72 def set_position_in_list(target_position, sequence) # Find the position of the element we'll be inserted after. If there are no # elements, or if our target position is zero, then we'll get inserted at # position zero. predecessor = if target_position > 0 siblings.filter(~:id => self.id).order(:position).limit(target_position).select(:position).all else [] end new_position = !predecessor.empty? ? (predecessor.last[:position] + 1) : 0 100.times do DB.attempt { # Go right to the database here to avoid bumping lock_version for tree changes. self.class.dataset.db[self.class.table_name].filter(:id => self.id).update(:position => new_position) return }.and_if_constraint_fails { # Someone's in our spot! Move everyone out of the way and retry. # Bump the sequence to maintain the invariant that sequence.number >= max(position) # (since we're about to increment the last N positions by 1) Sequence.get(sequence) # Sigh. Work around: # http://stackoverflow.com/questions/5403437/atomic-multi-row-update-with-a-unique-constraint siblings. filter { position >= new_position }. update(:parent_name => Sequel.lit(DB.concat('CAST(id as CHAR(10))', "'_temp'"))) # Do the update we actually wanted siblings. filter { position >= new_position }. update(:position => Sequel.lit('position + 1')) # Puts it back again siblings. filter { position >= new_position}. update(:parent_name => self.parent_name ) # Now there's a gap at new_position ready for our element. } end raise "Failed to set the position for #{self}" end |
- (Object) set_root(new_root)
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 12 def set_root(new_root) self.root_record_id = new_root.id save refresh if self.parent_id.nil? # Set ourselves to the end of the list update_position_only(nil, nil) else update_position_only(self.parent_id, self.position) end children.each do |child| child.set_root(new_root) end end |
- (Object) siblings
30 31 32 33 34 35 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 30 def siblings self.class.dataset. filter(:root_record_id => self.root_record_id, :parent_id => self.parent_id, ~:position => nil) end |
- (Object) transfer_to_repository(repository, transfer_group = [])
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 210 def transfer_to_repository(repository, transfer_group = []) # All records under this one will be transferred too children.each_with_index do |child, i| child.transfer_to_repository(repository, transfer_group + [self]) # child.update_position_only( child.parent_id, i ) end RequestContext.open(:repo_id => repository.id ) do self.update_position_only(self.parent_id, self.position) unless self.root_record_id.nil? end # ensure that the sequence if updated super end |
- (Object) trigger_index_of_child_nodes
147 148 149 150 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 147 def trigger_index_of_child_nodes self.children.update(:system_mtime => Time.now) self.children.each(&:trigger_index_of_child_nodes) end |
- (Object) update_from_json(json, opts = {}, apply_nested_records = true)
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 128 def update_from_json(json, opts = {}, apply_nested_records = true) sequence = self.class.sequence_for(json) self.class.set_root_record(json, sequence, opts) obj = super # Then lock in a position (which may involve contention with other updates # happening to the same tree of records) if json[self.class.root_record_type] && json.position self.set_position_in_list(json.position, sequence) end trigger_index_of_child_nodes obj end |
- (Object) update_position_only(parent_id, position)
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'backend/app/model/mixins/tree_nodes.rb', line 153 def update_position_only(parent_id, position) if self[:root_record_id] root_uri = self.class.uri_for(self.class.root_record_type.intern, self[:root_record_id]) parent_uri = parent_id ? self.class.uri_for(self.class.node_record_type.intern, parent_id) : root_uri sequence = "#{parent_uri}_children_position" parent_name = if parent_id "#{parent_id}@#{self.class.node_record_type}" else "root@#{root_uri}" end new_values = { :parent_id => parent_id, :parent_name => parent_name, :position => Sequence.get(sequence), :system_mtime => Time.now } # Run through the standard validation without actually saving self.set(new_values) self.validate if self.errors && !self.errors.empty? raise Sequel::ValidationFailed.new(self.errors) end # let's try and update the position. If it doesn't work, then we'll fix # the position when we set it in the list...there can be problems when # transfering to another repo when there's holes in the tree... DB.attempt { self.class.dataset.filter(:id => self.id).update(new_values) }.and_if_constraint_fails { new_values.delete(:position) self.class.dataset.filter(:id => self.id).update(new_values) } self.refresh self.set_position_in_list(position, sequence) if position else raise "Root not set for record #{self.inspect}" end end |