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

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

For a quick start, clone the project, run bundle install & bundle update, update config/connect.yml, create the documentation by calling »rdoc«
and start an irb-session: 
```
cd bin
./active-orient-console test   # or d)develpoment, p)roduction environment as defined in config/connect.yml
```

»ORD« is the Database-Instance itself. If the Database noticed is not present, it is created on startup.
A simple SQL-Query is submitted by providing a Block to »execute«
 ```ruby
 result =  ORD.execute { "select * from Stock" } 
 ```
Obviously, the class »Stock« has to exist. 
Let's create some classes

 ```ruby
    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.

#### CRUD
The CRUD-Process (create, read = query, update and remove) is performed as
```ruby	
    ORD.create_class :M
    M.create name: 'Hugo', age: 46, interests: [ 'swimming', 'biking', 'reading' ]
    # or
    new_record =  M.new  age: 46, interests: [ 'swimming', 'biking', 'reading' ]
    new_record.save   # alternative: new_record.update
    ##
    hugo = M.where( name: 'Hugo' ).first
    hugo.update set: { :father => M.create( name: "Volker", age: 76 ) } # we create an internal link
    hugo.father.name	# --> volker
    M.remove hugo 
    M.delete_class	# removes the class from OrientDB and deletes the ruby-object-definition
 ```
 
#### Inherence

Create a Tree of Objects with create_classes
```ruby
  ORD.create_classes  sector: [ :industry, :category, :subcategory ] 
  => {Sector=>[Industry, Category, Subcategory]}
  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= (...) ] 
 ```
 ***notice*** to create inherent Vertices use ORD.create_classes( sector: [ :industry, :category, :subcategory ]){ :V  } 

#### Preallocation of Model-Classes
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
```ruby
  the_object =  ActiveOrient::Model.autoload_object "xx:yy" # or "#xx:yy"
  --->  {ActiveOrient::Model} Object 
```

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

 ```ruby
 ORD.create_class  :M, :item
 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 }
 ```

(Experimental) You can put restrictions on your properties with the command "alter_property":

```ruby
  M.alter_property property: "value", attribute: "MIN", alteration: 0
  M.alter_property property: "value", attribute: "MAX", alteration: 23
```

#### Active Model interface

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

```ruby
  M.all   
  M.first
  M.last  	# notice: last does not work in orientdb version 2.2, because the sorting algorithm for rid's is damaged
  M.all.last    # or M.where( ... ).last  walkaround for  Orientdb V 2.2
  M.where town: 'Berlin'

  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
```ruby
  M.get_records projection: { projection-parameter },
		  distinct: { some parameters },
		  where: { where-parameter },
		  order: { sorting-parameters },
		  group_by: { one grouping-parameter},
		  unwind:  ,
		  skip:    ,
		  limit:  

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

```

Graph-support:

```ruby
  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.

```ruby
  ORD.create_class 'test_link'
  ORD.create_class '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

``` ruby
   base_document.single_link.att
```
reads the stored content of link_document.

To store a list of links to other Database-Objects, a simple Array is allocated
``` ruby
  # 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.update
```
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
Edges provide bidirectional Links. They are easily handled
```ruby
  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

  (...)
  the_edge.delete # To delete the edge
```
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
```ruby
  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 ) }
      end  
```
connects the vertices and provides a variable "key" and a common "study" attribute to each edge.

There is a basic support for traversals through a graph.
The Edges are accessed  by their names (downcase).
```ruby
  start = TheVertex.where: {something: "nice"}
  start[0].e1[0]
  --> #<E1:0x000000041e4e30	
      @metadata={"type"=>"d", "class"=>"E1", "version"=>60, "fieldTypes"=>"out=x,in=x", "cluster"=>16, "record"=>43}, 
      @attributes={"out"=>"#31:23", "in"=>"#31:15", "transform_to"=>"very bad" }>
```

The Attributes "in" and "out" can be used to move across the graph

```ruby
   start[0].e1[0].out.something
   # ---> "not_nice"
   start[0].e1[0].in.something
   # ---> "nice"
```

(Experimental) In alternative you can "humanize" your code in the following way:

```ruby
   Vertex.add_edge_link name: "ends",  edge: TheEdge
   start.ends.something # <-- Similar output as start[0].e1[0].out.something
```

#### Queries

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

```ruby

  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

```ruby
  q =  OrientSupport::OrientQuery.new from:  Vertex ,
				      where: [{ a: 2 }, 'b > 3 '],
				      distinct:  :profession,
				      order:  { :name => :asc }
  q.to_s
  => 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«.

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

or

```ruby
  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)
```

or

```ruby
  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) )'

  distinct_contracts.to_s
   => "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.
#### Match

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 returnd:
  
```ruby
  ORD.create_class :Industry
  Industry.match where:{ name: "Communications" }
  => #<ActiveOrient::Model::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.

```ruby
  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

```ruby
  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 
  end
  => 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.