Skip to content
No description, website, or topics provided.
Ruby
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
lib
test
.gitignore
.travis.yml
Gemfile
Guardfile
LICENSE.txt
README.md
Rakefile
arbutus.gemspec

README.md

Arbutus

Gem Version

Arbutus is a re-implementation of the ideas behind Closure Tree using the repository pattern. Designed for Hanami::Model - no ActiveRecord!

Features

From ClosureTree readme:

  • Get all ancestors in 1 SELECT
  • Get all descendants in 1 SELECT
  • Get all siblings in 1 SELECT
  • ! Structure as hash by default
  • Find node by ancestry path in 1 SELECT
  • 2 INSERTs on node creation
  • 3 INSERT/UPDATEs on node reparenting
  • Deterministic ordering
  • Polymorphic hierarchies
  • ? concurrency locking

Installation

Come on now:

gem 'arbutus'
bundle install

Or:

gem install arbutus

Setup

Hanami::Model.migration do
  change do
    create_table :regions do
      primary_key :id
      column      :name,      String
      foreign_key :parent_id, :regions
    end
    
    create_table :region_hierarchies do
      # primary_key nil
      foreign_key :ancestor_id,   :regions, null: false
      foreign_key :descendant_id, :regions, null: false
      column      :depth,         Integer,  null: false
      # probably a few unique constraints
    end
  end
end

class RegionRepository < Hanami::Repository
  include Arbutus::Repository
end

TODO

Instance methods:

  • find_all_by_depth(depth, base_node: nil)
  • with_ancestor(ancestors) - ?

as_hash: false option?

Config options:

  • :dependent
    • :orphan makes children roots (default)
    • :adopt makes children's grandparent the new parent
    • :destroy deletes all children
  • :parent_column_name
  • :hierarchy_table_name
  • :name_column - used for path
  • :order
    • #siblings_for takes only: :(before|after) option
    • #add_child takes position: Integer (indexed like array, default is -1)
    • defines #add_sibling_for(in_place, new_entity, position: :after)

Usage

All methods taking one or more node arguments can accept either an entity or a primary key integer.

Create & Update

Create root node:

country = Region.create(name: 'Canada')

Create child node:

repo = RegionRepository.new
province = repo.add_child(country, name: 'British Columbia')
city = repo.add_child(province, name: 'Vancouver')

Move node:

capital = Region.create(name: 'Victoria')
# capital.parent_id == nil
capital = repo.add_child(province, capital)
# You need to re-assign the variable to load the updated result into memory
# capital.parent_id == province.id

Read

Basic associations
  • repo.parent_for(node) returns the parent node
  • repo.children_for(node) returns an array of child nodes
Hierarchical associations

These functions return query objects that can be chained with other ROM filters (i.e. you can call .where on the return value of these functions). By default the data is ordered from root to leaf.

  • ancestors_for(node)
  • descendants_for(node)
  • siblings_for(node)

Options hash:

  • with_self: true includes the current node in the result set.
  • pluck: :name returns the data as an array of the given attribute, in this case :name. true defaults to the primary key, generally :id.
Helpers
  • root_nodes returns a query object for all nodes without a parent.
  • root_node(node) returns the root node for the given node.
  • leaf_nodes(node) returns a query object for all leaf nodes of the given node, or all leaf nodes for the repository if no base node is given.
  • depth_for(node, base_node = nil) returns an integer for the depth of the node, or the depth from the base node if one is given. Root nodes have a depth of 0.
Boolean helpers

Self-explanatory:

  • root_node?(node)
  • child_node?(node)
  • leaf_node?(node)

Delete

repo.delete(node, dependent = nil)

The optional second argument overrides the default :dependent configuration.

You can’t perform that action at this time.