Skip to content
This repository was archived by the owner on Apr 17, 2018. It is now read-only.

datamapper/dm-is-tree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DataMapper plugin enabling easy creation of tree structures from your DM models.

This requires a foreign key property for your model, which by default would be called :parent_id.

Install the dm-is-tree gem.

$ (sudo)? gem install dm-is-tree

Download or clone dm-is-versioned from Github.

$ cd /path/to/dm-is-tree

$ rake install            # will install dm-is-tree

To start using this gem, just…

require 'dm-is-tree'

Lets say we have a Category model…

class Category
  include DataMapper::Resource
  property :id,         Serial
  property :name,       String
end

…and we want to have a tree structure within it, something like this:

the_parent
  +- child
      +- grandchild1
      +- grandchild2

To achieve this we just add the following to the model:

class Category
  <snip...>

  is :tree, :order => :name

end

# No need to define the :parent_id property, it will be added automatically
property :parent_id,  Integer

This will automatically add the following to your model:

  • #parent / #parent=

  • #children / #children=

  • #siblings

  • #generation

  • #ancestors

    • also aliased as #self_and_siblings for those used to AR’s :acts_as_tree

  • #root

    • also aliased as #first_root for those used to AR’s :acts_as_tree

  • #first_root

  • #roots

Before we go onto the usage examples, a few quick words about configuration options available:

Specifies the column name to use for tracking of the tree (default: #parent_id).

class Category
  <snip...>

  is :tree, :child_key => :some_other_foreign_key_id

end

Specifies the name of the Model to use for the tree (default: Model class name defined in)

class Category
  <snip...>

  is :tree, :model => 'SomeStrangeModelName'

end

Specifies the sort order of the children when retrieving them (default: not present)

class Category
  <snip...>

  is :tree, :order => [:updated_at, :name]

end

To create the above structure, we would start with:

the_parent = Category.create(:name => "the_parent")

The #parent instance method returns the node referenced by the foreign key - :parent_id or the defined :child_key.

# by default #parent and #parent_id return nil when there is no parent
the_parent.parent  # => nil

The #parent= instance method sets the :parent_id foreign key to the parent’s id attribute value.

To define a parent you can use either of these syntaxes:

a_child = Category.create(:name => "a_child", :parent => the_parent)

a_child = Category.create(:name => "a_child", :parent_id => the_parent.id)

a_child = Category.create(:name => "a_child")
a_child.parent = the_parent

When retrieving the parent, you will receive the full parent object ( or nil if none was declared )

a_child.parent  # => the_parent

The #children instance method returns all nodes with the current node as their parent, in the order specified by the :order configuration option.

# by default #children return an empty Array when there are no children
a_child.children # => []

# or an Array with child objects when there are children
the_parent.children # => [a_child]

The #children= instance method adds children by setting the :parent_id foreign key to the parent’s id attribute value.

To add a child you can use either of these syntaxes:

child = the_parent.children.create(:name => "child")

grandchild1 = Category.create(:name => "grandchild1")
child.children << grandchild1

grandchild2 = child.children.create(:name => "grandchild2")

When retrieving children, or a child, you will receive an Array of child objects.

child.children # => [grandchild1, grandchild2,...]

# just retrieve the first child
child.children.first # => grandchild1

The #siblings instance method returns all the children of the parent, excluding the current node.

# by default #siblings return an empty Array when there are no siblings
the_parent.siblings # => []

grandchild1.siblings # => [grandchild2]

The #generation instance method returns all the children of the parent, including the current node.

# by default #generation return an Array with itself only when it's a 'lonely child'
the_parent.generation # => [the_parent]

#
grandchild1.generation # => [grandchild1, grandchild2]

The #ancestors instance method returns all the ancestors of the current node.

# by default it returns an empty Array when born through immaculate conception (is root)
the_parent.ancestors # => []

grandchild2.ancestors # => [the_parent, a_child]

The #root instance method returns the root (parent) of the current node.

# by default returns itself only when it's the root node
the_parent.root # =>  the_parent

a_child.root # => the_parent

grandchild2.root # => the_parent

The #first_root class method returns the first root declared in the model.

Category.first_root # =>  the_parent

The #roots class method returns an Array of the roots declared in the model.

Category.roots # =>  [the_parent]

parent2 = Category.create(:name => 'parent2')

Category.roots # =>  [the_parent, parent2]
parent = Category.create(:name => "parent")

child = parent.children.create(:name => "child")

grandchild1 = child.children.create(:name => "grandchild1")

grandchild2 = Category.create(:name => "grandchild2")
child.children << grandchild2

grandchild3 = Category.create(:name => "grandchild2")
grandchild3.parent = child

parent.parent  # => nil
child.parent  # => parent

parent.children  # => [child]
parent.children.first.children.first  # => grandchild1

parent.siblings # => []
grandchild1.siblings # => [grandchild2]

parent.generation # => [parent]
grandchild1.generation # => [grandchild1, grandchild2]

parent.ancestors # => []
grandchild2.ancestors # => [parent, child]

parent.root # =>  parent
parent.root # => parent
grandchild2.root # => parent

Category.first_root  # => parent
Category.roots  # => [parent]

Now there are some gotcha’s that might not be entirely obvious to everyone, so let’s clarify them here.

By default dm-is-tree allows you to save a record as a child of it self, which is quite unnatural. To prevent this, I would humbly suggest adding this custom validation code to your model(s).

class Category
  <snip...>

  # prevent saving Category as child of self, except when new?
  validates_with_method :parent_id,
                        :method => :category_cannot_be_made_a_child_of_self,
                        :unless => :new?

  protected

    def category_cannot_be_made_a_child_of_self
      if self.id === self.parent_id
        return [
          false,
          "A Category [ #{self.name} ] cannot be made a child of it self [ #{self.name} ]"
        ]
      else
        return true
      end
    end

end

An example:

parent = Category.create(:name => "parent")
child = parent.children.create(:name => 'child')

child.parent = child

child.save  # => return false

child.errors.on(:parent_id)
  # => ["A Category [ child ] cannot be made a child of it self [ child ]"]

By default the sorting order is alphabetic, but this spans the entire table (with all nodes), which might not be what you want.

To prevent this, order the results by :parent_id first, and secondly by :name or whatever you wish to sort by.

class Category
  <snip...>

  is :tree, :order => [:parent_id, :name]

end

That’s about it.

If something is not behaving intuitively, it is a bug, and should be reported. Report it here: datamapper.lighthouseapp.com/

  • Make it automatically prevent saving self as child of self.

  • Anything else missing?

  • Fork the project.

  • Make your feature addition or bug fix.

  • Add tests for it. This is important so we don’t break it in a future version unintentionally.

  • Commit, do not mess with rakefile, version, or history.

    • (if you want to have your own version, that is fine but bump version in a commit by itself we can ignore when we pull)

  • Send us a pull request. Bonus points for topic branches.

Copyright © 2011 Timothy Bennett. Released under the MIT License.

See LICENSE for details.

Credit also goes to these contributors.

Current Maintainer: Garrett Heaver (www.linkedin.com/pub/dir/garrett/heaver)

About

DataMapper plugin allowing the creation of tree structures from data models

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 18

Languages