DEV Community

Cover image for Ruby Adventures with ArcadeDB 2
Hartmut B.
Hartmut B.

Posted on

Ruby Adventures with ArcadeDB 2

note: Clone the git respiratory https://github.com/topofocus/dev-adventures to play with the data. :end-note

Part One: Ruby Adventures with ArcadeDB

Some Basics

ActiveRecord is the standard abstraction layer for databases in ruby. It translates ruby code to SQL, optimizes queries and is compatible to many low level database drivers. Its promise: You don't have to know anything about databases, can use every relational database – everywhere. Just stick to ruby code.

Relational databases are a popular way to organize structured data. But data sets aren't always neatly organized. Sometimes, the data is forced into a mold that doesn't fit just because the tool requires it.

ArcadeDB is a modern multi model database. Its promise is analogous to ActiveRecord: Whatever the structure of your data is, the database offers an effective host. The ruby-interface to ArcadeDB aims to combine the
user friendlines of ActiveRecord and the flexibility of ArcadeDB.

The Power of RID

Relational Databases organize data in tables. Very often, a column is called id and carries the index. ArcadeDB too has tables. They're called types and carry object-oriented behaviors. In addition the local data-structure can always inspected via RID's.
This is happening:

select from #1:0

{:@rid=>"#449:0", :@type=>"human", :@cat=>"v", :name=>"hugo }

The RID exposes itself as human-type, its a vertex (@cat: "v") and has a property name.

The ruby-interface connects the @type to an appropriate Model class, assigns the database properties to corresponding model-attributes and exposes the data to customized model-methods.

Its much simpler to access pieces of data from ArcadeDB then from any relational database. ArcadeDB is a native object database, no abstraction is needed to fit the data representation in the database to the data-model in your ruby program.

Exploring the Structure

The Account-Type is a vertex. This implies, it might have edges, that connect it to other vertices. This is explored with the nodes-method

> first_human =  DB.get 1,0      # select from #1:0
> [<human[#1:0]:{0->}{->1}, name: hugo>] 
> first_account.nodes              # select both() from #1:0
> [<human[#2:0]:{1->}{->0}, name: berta>] 
Enter fullscreen mode Exit fullscreen mode

A connected Account-record is retrieved!

Bidirectional connections – with attributes

Lets design a simple relation

Image description

> first_account.assign via: IsMarriedTo
                        to: second_account
                      date: Date.new( 1955, 12, 21)
                      town: "Dublin"
Enter fullscreen mode Exit fullscreen mode

If -- by convention -- outgoing connections belong to the male and incoming one to the female, a query of Human's which got out-connections of type IsMarriedTo, loads a list of married man. Compare that to efforts needed with relational databases.

Its possible to use the node-method here, too.
But ArcadeDB offers a versatile alternative.

Arcade::Match.new( type: Human, as: :h)
             .out( IsMarriedTo )
             .execute { "h.rid, h.name" }
Enter fullscreen mode Exit fullscreen mode

Arcade::Match is a simple wrapper of the Match Statement, which »queries the database in a declarative manner, using pattern matching (inspired by Cypher)«.
The compiled match statement looks like this:

MATCH { type: human, as: h }
      .out( 'is_married_to')
RETURN h.rid, h.name
Enter fullscreen mode Exit fullscreen mode

and returns an array with all rid's and names of the suspects.

The query is easily extended to only marriages in the last decade of the 20th century:

Arcade::Match.new( type: Human, as: :h)
             .out( IsMarriedTo , where: { date: 1990 .. 2000})
             .execute { "h.rid, h.name" }
=> MATCH { type: human, as: h }
      .out( 'is_married_to', where: ( date between 1990 and 2000) )
RETURN h.rid, h.name
Enter fullscreen mode Exit fullscreen mode

The properties on the bidirectional connection(edge) are essential to this query.

ArcadeDB is not only a object-database regarding to the data-storage but to the connections between the data itself.
If a couple divorces, the IsMarriedToconnection is replaced by a IsDivorced-edge. Both edge-classes are childs of IsFriend Edges.

Until now, this example lacks realism, as same-sex marriages are not allowed (Only man have out-connections). This can easily be addressed via inheritance.

Image description

The final setup of the database looks as following

DB = Arcade::Init.db  # Reference to the database
[Male, Female, IsMarriedTo, IsDivorced].each { |y| y.create_type  }  
# Inheritance is respected, `Human` and `IsFriend` are allocated implicitly

hugo, paul, jochen = ['Hugo', 'Paul', 'Jochen'].map do | man |
   Male.insert name: man
end
berta, hilde, maria = ['Berta', 'Hilde', 'Maria'].map do | woman |
   Female.insert name: woman
end
hugo.assign via: IsMarriedTo, town: 'Duesseldorf', date: Date.new(1956,12,1), to: berta
paul.assign via: IsDivorced, date: Date.new(1959,4,5), to: hilde
maria.assign via: IsFriend, to: [ jochen, hilde, hugo ]
Enter fullscreen mode Exit fullscreen mode

Verify the data structure

Human.all
=> [<male[#66:0]:{1->}{->1}, name: Hugo>,                 
    <male[#69:0]:{0->}{->1}, name: Paul>,                 
    <male[#72:0]:{1->}{->0}, name: Jochen>,
  <female[#90:0]:{1->}{->0}, name: Berta>,
  <female[#93:0]:{2->}{->0}, name: Hilde>,
  <female[#96:0]:{0->}{->3}, name: Maria>]

Female.find( name: 'Maria').out
=>                                                                 
[  <male[#66:0]:{1->}{->1}, name: Hugo>,                              
 <female[#93:0]:{2->}{->0}, name: Hilde>,                           
   <male[#72:0]:{1->}{->0}, name: Jochen>]

# query for divorced couples
Arcade::Match.new( type: Human, as: m)
             .out(IsDivorced)
             .node( as: :f)
             .execute { "m.name, f.name" }

INFO->Q: MATCH { type: human, as: m }
                .out('is_divorced')
               { as: f } 
         RETURN m.name, f.name 
 =>                                                                                                          
[{:m.name =>"Paul", :f.name=>"Hilde"}]  
Enter fullscreen mode Exit fullscreen mode

( to be continued ... )

Top comments (0)