Module: DataMapper::Is::NestedSet::InstanceMethods

Defined in:
lib/dm-is-nested_set/is/nested_set.rb

Instance Method Summary collapse

Instance Method Details

#ancestorResource, NilClass

get the parent of this node. Same as #parent, but finds it from lft/rgt instead of parent-key

Returns:

  • (Resource, NilClass)

    returns the parent-object, or nil if this is root/detached



282
283
284
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 282

def ancestor
  ancestors.last
end

#ancestorsCollection

get all ancestors of this node

Returns:

  • (Collection)

    collection of all parents, with root as first item

See Also:



273
274
275
276
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 273

def ancestors
  nested_set.all(:lft.lt => lft, :rgt.gt => rgt)
  #self_and_ancestors.reject{|r| r.key == key } # because identitymap is not used in console
end

#descendantsCollection

get all descendants of this node

Returns:

  • (Collection)

    flat collection, sorted according to nested_set positions

See Also:



316
317
318
319
320
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 316

def descendants
  # TODO add argument for returning as a nested array.
  # TODO supply filtering-option?
  nested_set.all(:lft => (lft + 1)..(rgt - 1))
end

#leaf?Boolean

check if this node is a leaf (does not have subnodes). use this instead ofdescendants.empty?

Returns:

  • (Boolean)


334
335
336
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 334

def leaf?
  rgt - lft == 1
end

#leavesCollection

get all descendants of this node that does not have any children

Returns:

  • (Collection)


326
327
328
329
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 326

def leaves
  # TODO supply filtering-option?
  nested_set.all(:lft => (lft + 1)..rgt, :conditions => [ 'rgt = lft + ?',  1 ])
end

#left_siblingResource, NilClass

get sibling to the left of/above this node in the nested tree

Returns:

  • (Resource, NilClass)

    the resource to the left, or nil if self is leftmost

See Also:



362
363
364
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 362

def left_sibling
  self_and_siblings.detect { |v| v.rgt == lft - 1 }
end

#levelInteger

get the level of this node, where 0 is root. temporary solution

Returns:

  • (Integer)


255
256
257
258
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 255

def level
  # TODO make a level-property that is cached and intelligently adjusted when saving objects
  ancestors.length
end

#move(vector) ⇒ FalseClass

move self / node to a position in the set. position can only be changed through this

Examples:

Usage
* node.move :higher           # moves node higher unless it is at the top of parent
* node.move :lower            # moves node lower unless it is at the bottom of parent
* node.move :below => other   # moves this node below other resource in the set
* node.move :into => other    # same as setting a parent-relationship

Parameters:

  • vector (Symbol, Hash)

    A symbol, or a key-value pair that describes the requested movement

  • :higher<Symbol> (Hash)

    a customizable set of options

  • :highest<Symbol> (Hash)

    a customizable set of options

  • :lower<Symbol> (Hash)

    a customizable set of options

  • :lowest<Symbol> (Hash)

    a customizable set of options

  • :indent<Symbol> (Hash)

    a customizable set of options

  • :outdent<Symbol> (Hash)

    a customizable set of options

  • :into<Resource> (Hash)

    a customizable set of options

  • :above<Resource> (Hash)

    a customizable set of options

  • :below<Resource> (Hash)

    a customizable set of options

  • :to<Integer> (Hash)

    a customizable set of options

Returns:

  • (FalseClass)

    returns false if it cannot move to the position, or if it is already there

Raises:

See Also:



168
169
170
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 168

def move(vector)
  move_without_saving(vector) && save
end

#move_without_saving(vector) ⇒ Object

See Also:



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 174

def move_without_saving(vector)
  if vector.kind_of?(Hash)
    action, object = vector.keys[0], vector.values[0]
  else
    action = vector
  end

  changed_scope = nested_set_scope != original_nested_set_scope

  position = case action
    when :higher  then left_sibling  ? left_sibling.lft      : nil  # : "already at the top"
    when :highest then ancestor      ? ancestor.lft + 1      : nil  # : "is root, or has no parent"
    when :lower   then right_sibling ? right_sibling.rgt + 1 : nil  # : "already at the bottom"
    when :lowest  then ancestor      ? ancestor.rgt          : nil  # : "is root, or has no parent"
    when :indent  then left_sibling  ? left_sibling.rgt      : nil  # : "cannot find a sibling to indent into"
    when :outdent then ancestor      ? ancestor.rgt + 1      : nil  # : "is root, or has no parent"
    when :into    then object        ? object.rgt            : nil  # : "supply an object"
    when :above   then object        ? object.lft            : nil  # : "supply an object"
    when :below   then object        ? object.rgt + 1        : nil  # : "supply an object"
    when :to      then object        ? object.to_i           : nil  # : "supply a number"
  end

  ##
  # raising an error whenever it couldnt move seems a bit harsh. want to return self for nesting.
  # if anyone has a good idea about how it should react when it cant set a valid position,
  # don't hesitate to find me in #datamapper, or send me an email at sindre -a- identu -dot- no
  #
  # raise UnableToPositionError unless position.is_a?(Integer) && position > 0
  return false if !position || position < 1
  # return false if you are trying to move this into another scope
  return false if [ :into, :above, :below ].include?(action) && nested_set_scope != object.nested_set_scope
  # if node is already in the requested position
  if lft == position || rgt == position - 1
    self.parent = ancestor # must set this again, because it might have been changed by the user before move.
    return false
  end

  ##
  # if this node is already positioned we need to move it, and close the gap it leaves behind etc
  # otherwise we only need to open a gap in the set, and smash that buggar in
  #
  if lft && rgt
    # raise exception if node is trying to move into one of its descendants (infinite loop, spacetime will warp)
    raise RecursiveNestingError if position > lft && position < rgt
    # find out how wide this node is, as we need to make a gap large enough for it to fit in
    gap = rgt - lft + 1

    # make a gap at position, that is as wide as this node
    model.base_model.adjust_gap!(nested_set, position - 1, gap)

    eager_props = model.properties.values_at(:lft, :rgt)

    # FIXME don't use @api private
    # offset this node (and all its descendants) to the right position
    eager_load(eager_props)

    old_position = lft
    offset = position - old_position

    nested_set.all(:rgt => lft..rgt).adjust!({ :lft => offset, :rgt => offset }, true)

    # close the gap this movement left behind.
    model.base_model.adjust_gap!(nested_set, old_position, -gap)

    # FIXME don't use @api private
    eager_load(eager_props)

  else
    # make a gap where the new node can be inserted
    model.base_model.adjust_gap!(nested_set, position - 1, 2)
    # set the position fields
    self.lft, self.rgt = position, position + 1
  end

  self.parent = ancestor
end

#nested_setObject

the whole nested set this node belongs to. served flat like a pancake!



137
138
139
140
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 137

def nested_set
  # TODO add option for serving it as a nested array
  model.base_model.all(nested_set_scope.merge(:order => [ :lft ]))
end

#nested_set_scopeObject



119
120
121
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 119

def nested_set_scope
  Hash[ model.base_model.nested_set_scope.map { |p| [ p, attribute_get(p) ] } ]
end

#original_nested_set_scopeObject



126
127
128
129
130
131
132
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 126

def original_nested_set_scope
  # TODO commit
  pairs = model.base_model.nested_set_scope.map do |p|
    [ p, (property = properties[p]) && original_attributes.key?(property) ? original_attributes[property] : attribute_get(p) ]
  end
  Hash[ pairs ]
end

#right_siblingResource, NilClass

get sibling to the right of/above this node in the nested tree

Returns:

  • (Resource, NilClass)

    the resource to the right, or nil if self is rightmost

See Also:



371
372
373
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 371

def right_sibling
  self_and_siblings.detect { |v| v.lft == rgt + 1 }
end

#rootResource, NilClass

get the root this node belongs to. this will atm always be the same as Resource.root, but has a meaning when scoped sets is implemented

Returns:

  • (Resource, NilClass)


291
292
293
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 291

def root
  nested_set.first
end

#root?Boolean

check if this node is a root

Returns:

  • (Boolean)


298
299
300
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 298

def root?
  !parent && !new?
end

#self_and_ancestorsCollection

get all ancestors of this node, up to (and including) self

Returns:

  • (Collection)


264
265
266
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 264

def self_and_ancestors
  nested_set.all(:lft.lte => lft, :rgt.gte => rgt)
end

#self_and_descendantsCollection

get all descendants of this node, including self

Returns:

  • (Collection)

    flat collection, sorted according to nested_set positions



306
307
308
309
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 306

def self_and_descendants
  # TODO supply filtering-option?
  nested_set.all(:lft => lft..rgt)
end

#self_and_siblingsCollection

get all siblings of this node, and include self

Returns:

  • (Collection)


342
343
344
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 342

def self_and_siblings
  parent ? parent.children : [ self ]
end

#siblingsCollection

get all siblings of this node

Returns:

  • (Collection)

See Also:



351
352
353
354
355
# File 'lib/dm-is-nested_set/is/nested_set.rb', line 351

def siblings
  # TODO find a way to return this as a collection?
  # TODO supply filtering-option?
  self_and_siblings.reject { |r| r.key == key } # because identitymap is not used in console
end