Pure Ruby interface to OrientDB
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



Use OrientDB to persistently store dynamic Ruby-Objects and use database queries to manage even very large datasets.

Other Documents

You need a ruby 2.3, 2.4 or a jruby 9.1x Installation and a working OrientDB-Instance (Version 2.2 prefered). The jruby-part is experimental.

Quick Start

  • clone the project,
  • run bundle install ; bundle update,
  • update config/connect.yml,
  • create the documentation:
  sdoc . -x spec -x example

and point the browser to ~/active-orient/doc/index.htm

  • start an irb-session by calling
cd bin
./active-orient-console test   # or d)develpoment, p)roduction environment as defined in config/connect.yml

«ORD» or «DB» is the Database-Instance itself. If the Database noticed in the config-file is not present, it is created on startup. A simple SQL-Query is submitted by providing a Block to »execute«

result =  ORD.execute { "select * from Stock" } 

Obviously, the class »Stock« has to exist. Let's create some classes

   ORD.create_class        'ClassDocumentName'  # creates or opens a basic document-class
   ORD.create_vertex_class 'ClassVertexName'  # creates or opens a vertex-class
   ORD.create_edge_class   'ClassEdgeName'  # creates or opens an edge-class, providing bidirectional links between documents
   {Classname}.delete_class			 # removes the class in the database and destroys the ruby-object

Classnames appear unchanged as Database-Classes. Strings and Symbols are accepted. Depending on the namespace choosen in 'config/config.yml' Model-Classes are allocated and linked to database-classes. For simplicity, here we omit any namespace ( :namespace: :object in config.yml). Thus the Model-Obects are accessible directly.

Naming-Convention: The name given in the »create-class«-Statement becomes the Database-Classname. In Ruby-Space its Camelized, ie: ORD.create_class(:hut_ab) generates a Ruby-Class »HutAb«.

This can be customized in the "naming_convention"-class-method, which has to be defined in 'config/boot.rb'. The naming_convention changes the ruby-view to the classes. The Database-Class-Name is derived from the argument to #CreateClass. ORD.create_class('HANDS_UP') creates a database-class "HANDS_UP' and a Ruby-Class "HandsUp".

ActiveOrient::Model's can be customized through methods defined in the model-directory. These methods are loaded automatically afert executing #CreateClass (and through the preallocation process). Further details in the Examples-Section.


The CRUD-Process (create, read = query, update and remove) is performed as

    # create the class
    ORD.create_class :m
    # create a record
    M.create name: 'Hugo', age: 46, interests: [ 'swimming', 'biking', 'reading' ]
    # query the database
    hugo = M.where( name: 'Hugo' ).first
    # update the dataset
    hugo.update father: M.create( name: "Volker", age: 76 )  # we create an internal link
    hugo.father.name	# --> volker
    # change array elements
    hugo.interests << "dancing"  # --> [ 'swimming', 'biking', 'reading', 'dancing' ]
    M.remove hugo 
    M.delete_class	# removes the class from OrientDB and deletes the ruby-object-definition


Create a Tree of Objects with create_classes

  ORD.create_class  :sector
  ORD.create_class(  :industry, :category, :subcategory ){ Sector }
  Industry.create name: 'Communications'   #--->   Create an Industry-Record with the attribute "name"
  Sector.where  name: 'Communications'	   #--->   an Array with the Industry-Object
  => [#<Industry:0x0000000225e098 @metadata= (...) ] 

Preallocation and Cashing

All database-classes are preallocated after connecting to the database. Thus you can use Model-Classes from the start.

If the "rid" is known, any Object can be retrieved and correctly allocated by

  the_object =  V.get "xx:yy" # or "#xx:yy"
  --->  {ActiveOrient::Model} Object 

The database-class «V» is present in any case. Any model-class can be used, even the parent »ActiveOrient::Model« »get« queries the database and gets the current database-content. Any preciously changed (and unsafed ) attributes are lost.

However, Model-Instances are cashed by ActiveOrient. There is always only one instance of a specific database-record.

 ORD.create_class :m
 m1= M.create test: 1
 m1.test = 2
 # access changed attributes
 m2 = V.autoload_object m1.rid
 m2.test --> 2
 # access database-value
 m2 =  V.get m1.rid   
 m1.test --> 1

m1 and m2 share the same Ruby-ObjectID.


The schemaless mode has many limitations. ActiveOrient offers a Ruby way to define Properties and Indexes

ORD.create_class  :M  
M.create_property :symbol 			# the default-case: type: :string, no index
M.create_property :con_id,   type: :integer
M.create_property :details,  type: :link, other_class: 'Contracts'
M.create_property :items,    type: :linklist, :linklist: Item
M.create_property :name,    index: :unique	# or  M.create_property( 'name' ){ :unique }

Active Model interface

As for ActiveRecord-Tables, the Model-class itself provides methods to inspect and filter datasets form the database.

  M.where town: 'Berlin'
  M.like "name =  G*"

  M.count where: { town: 'Berlin' }

»count« gets the number of datasets fulfilling the search-criteria. Any parameter defining a valid SQL-Query in Orientdb can be provided to the »count«, »where«, »first« and »last«-method.

A »normal« Query is submitted via

  M.get_records projection: { projection-parameter },
		  distinct: { some parameters },
		  where: { where-parameter },
		  order: { sorting-parameters },
		  group_by: { one grouping-parameter},
		  unwind:  ,
		  skip:    ,

#  or
 query = OrientSupport::OrientQuery.new {paramter}  
 M.query_database query

To update several records, a class-method »update_all« is defined.

  M.update_all connected: false   	# add a property »connected» to each record
  M.update_all set:{ connected: true },  where: "symbol containsText 'S'" 


  ORD.create_vertex_class :the_vertex
  ORD.create_edge_class :the_edge
  vertex_1 = TheVertex.create  color: "blue"
  vertex_2 = TheVertex.create  flower: "rose"
  TheEdge.create_edge attributes: {:birthday => Date.today }, from: vertex_1, to: vertex_2

It connects the vertexes and assigns the attributes to the edge.

To query a graph, SQL-like-Queries and Match-statements can be used (see below).

Links and LinkLists

A record in a database-class is defined by a »rid«. If this is stored in a class, a link is set. In OrientDB links are used to realize unidirectional 1:1 and 1:n relationships.

ActiveOrient autoloads Model-objects when they are accessed. Example: If an Object is stored in Cluster 30 and id 2, then "#30:2" fully qualifies the ActiveOrient::Model object and sets the link if stored somewhere.

  ORD.create_class 'test_link', 'test_base'

  link_document =  TestLink.create  att: 'one attribute'
  base_document =  TestBase.create  base: 'my_base', single_link: link_document

base_document.single_link just contains the rid. When accessed, the ActiveOrient::Model::Testlinkclass-object is autoloaded and


reads the stored content of link_document.

To store a list of links to other Database-Objects, a simple Array is allocated

  # predefined linkmap-properties
  TestLink.create_property  :links,  type: :linklist, linkedClass: :test_links 
  base_document =  TestBase.create links: []  
  (0 .. 20).each{|y| base_document.links << TestLink.create( nr: y )}
  #or in schemaless-mode
  base_document = TestBase.create links: (0..20).map{|y| TestLink.create nr: y}

base_document.links behaves like a ruby-array.

If you got an undirectional graph

a --> b ---> c --> d

the graph elements can be explored by joining the objects (a[6].b[5].c[9].d)

Refer to the "Time-Graph"-Example for an Implementation of an bidirectional Graph with the same Interface


Edges provide bidirectional Links. They are easily handled

  ORD.create_vertex_class :the_vertex 	# -->  TheVertex
  ORD.create_edge_class  :the_edge      # -->  TheEdge

  start = TheVertex.create something: 'nice'
  the_end  = TheVertex.create something: 'not_nice'
  the_edge = TheEdge.create attributes: {transform_to: 'very bad'},
			       from: start,
			       to: the_end

Edges are connected to vertices by »in« and »out«-Methods. Inheritance is maintained on the class-level. Attention: in_ and out_-Attributes just reflect the morphology, to reveal their oo-behavior, the database has to be queried.


  ORD.create_class( top_edge ) { THE_EDGE }
  ORD.create_class( on_top_edge ) { TOP_EDGE }

  ON_TOP_Edge.create  from: start, to: the_end 
  => [#<TOP_EDGE:0x00000001d92f28 @metadata= ... ]

Bidirectional links are displayed and retrieved by

  start.edges	  # :in | :out | :all 
   => ["#73:0"] 
  start.edges(:out).map &:from_orient
  => [#<TOP_EDGE:0x00000001d92f28 @metadata= ... ]

The create-Method od Edge-Classes takes a block. Then all statements are transmitted in batch-mode. Assume, Vertex1 and Vertex2 are Vertex-Classes and TheEdge is an Edge-Class, then

  record1 = (1 .. 100).map{|y| Vertex1.create testentry: y  }
  record2 = (:a .. :z).map{|y| Vertex2.create testentry: y  }
  edges = TheEdge.create attributes: { study: 'Experiment1'} do  | attributes |
   # map returns an array, which is further processed by #create_edge 
    ('a'.ord .. 'z'.ord).map do |o| 
	  { from: record1.find{|x| x.testentry == o },
	    to:  record2.find{ |x| x.testentry.ord == o },
	    attributes: attributes.merge( key: o.chr ) }

connects the vertices and provides a variable "key" and a common "study" attribute to each edge.


Contrary to traditional SQL-based Databases OrientDB handles sub-queries very efficiently. In addition, OrientDB supports precompiled statements (let-Blocks).

ActiveOrient is equipped with a simple QueryGenerator: ActiveSupport::OrientQuery. It works in two ways: a comprehensive and a subsequent one

  q =  OrientSupport::OrientQuery.new
  q.from = Vertex     # If a constant is used, then the correspending
		      # ActiveOrient::Model-class is refered
  q.where << a: 2
  q.where << 'b > 3 '
  q.distinct = :profession
  q.order = {:name => :asc}

is equivalent to

  q =  OrientSupport::OrientQuery.new from:  Vertex ,
				      where: [{ a: 2 }, 'b > 3 '],
				      distinct:  :profession,
				      order:  { :name => :asc }
  => select distinct( profession ) from Vertex where a = 2 and b > 3  order by name asc

Both eayss can be mixed.

If sub-queries are necessary, they can be introduced as OrientSupport::OrientQuery or as »let-block«.

  OQ = OrientSupport::OrientQuery
  q = OQ.new from: 'ModelQuery'
  q.let << "$city = adress.city"
  q.where = "$city.country.name = 'Italy' OR $city.country.name = 'France'"
  # => select from ModelQuery let $city = adress.city where $city.country.name = 'Italy' OR $city.country.name = 'France'


  q =  OQ.new
  q.let << {a: OQ.new( from: '#5:0' ) }
  q.let << {b: OQ.new( from: '#5:1' ) }
  q.let << '$c= UNIONALL($a,$b) '
  q.projection << 'expand( $c )'
  q.to_s  # => select expand( $c ) let $a = ( select from #5:0 ), $b = ( select from #5:1 ), $c= UNIONALL($a,$b)


  last_12_open_interest_records = OQ.new from: OpenInterest, 
					order: { fetch_date: :desc } , limit: 12
  bunch_of_contracts = OQ.new from: last_12_open_interest_records, 
			      projection: 'expand( contracts )'
  distinct_contracts = OQ.new from: bunch_of_contracts, 
			      projection: 'expand( distinct(@rid) )'

   => "select expand( distinct(@rid) ) from ( select expand( contracts ) from ( select  from open_interest order by fetch_date desc limit 12 ) ) "

  cq = ORD.get_documents query: distinct_contracts

this executes the query and returns the adressed rid's, which are eventually retrieved from the rid-cache.


A Match-Query starts at the given ActiveOrient::Model-Class. The where-cause narrows the sample to certain records. In the simplest version this can be returned:

  ORD.create_class :Industry
  Industry.match where:{ name: "Communications" }
  => #<Query:0x00000004309608 @metadata={"type"=>"d", "class"=>nil, "version"=>0, "fieldTypes"=>"Industries=x"}, @attributes={"Industries"=>"#21:1", (...)}>

The attributes are the return-Values of the Match-Query. Unless otherwise noted, the pluralized Model-Classname is used as attribute in the result-set. Note that the Match statement returns a »Query«-record. Its up to the usere, to transform the attributes to Model-Objects. This is done by the »to_orient« directive, ie. »xx.Industries.to_orient «

  Industry.match where name: "Communications" 
  ## is equal to
  Industry.match( where: { name: 'Communications' }).first.Industries

The Match-Query uses this result-set as start for subsequent queries on connected records. If a linear graph: Industry <- Category <- Subcategory <- Stock is build, Subcategories can accessed starting at Industry defining

  var = Industry.match( where: { name: 'Communications'}) do | query |
    query.connect :in, count: 2, as: 'Subcategories'
    puts query.to_s  # print the query prior sending it to the database
    query            # important: block has to return the query 
  => MATCH {class: Industry, as: Industries} <-- {} <-- { as: Subcategories }  RETURN Industries, Subcategories

The result-set has two attributes: Industries and Subcategories, pointing to the filtered datasets.

By using subsequent »connect« and »statement« method-calls even complex Match-Queries can be constructed.

Other Documents

  start.the_edge  # --> Array of "TheEdge"-instances connected to the vertex
  start.the_edge.where transform_to: 'good'     # ->  empty array
  start.the_edge.where transform_to: 'very bad' #-->  previously connectd edge
  start.something     # 'nice'
  end.something	      # 'not_nice'
  start.the_edge.where( transform_to: 'very bad').in.something  # => ["not_nice"] 
  the_edge.delete # To delete the edge