Skip to content
Yoko Harada edited this page May 2, 2013 · 5 revisions

Datomic distribution has a great sample maintained by Seattle user group. This document explains how to run the Seattle example on Diametric gem. This document assumes Peer-based API on JRuby. REST-API is a subset of Peer API and some of methods using here are not supported.

  1. Schema definition

    class Seattle # Organization Definition
      include Diametric::Entity
      include Diametric::Persistence::Peer
    
      attribute :name, String, :cardinality => :one, :fulltext => true, :doc => "A community's name"
      attribute :url, String, :cardinality => :one, :doc => "A community's url"
      attribute :neighborhood, Ref, :cardinality => :one, :doc => "A community's neighborhood"
      attribute :category, String, :cardinality => :many, :fulltext => true, :doc => "All community categories"
      attribute :orgtype, Ref, :cardinality => :one, :doc => "A community orgtype enum value"
      attribute :type, Ref, :cardinality => :one, :doc => "A community type enum value"
      enum :orgtype, [:community, :commercial, :nonprofit, :personal]
      enum :type, [:email_list, :twitter, :facebook_page, :blog, :website, :wiki, :myspace, :ning]
    end
    
    class Neighborhood
      include Diametric::Entity
      include Diametric::Persistence::Peer
    
      attribute :name, String, :cardinality => :one, :unique => :identity, :doc => "A unique neighborhood name(upsertable)"
      attribute :district, Ref, :cardinality => :one, :doc => "A neighborhood's district"
    end
    
    class District
      include Diametric::Entity
      include Diametric::Persistence::Peer
    
      attribute :name, String, :cardinality => :one, :unique => :identity, :doc => "A unique district name (upsertable)"
      attribute :region, Ref, :cardinality => :one, :doc => "A district region enum value"
      enum :region, [:n, :ne, :e, :se, :s, :sw, :w, :nw]
    end

    This definitions is equivalent to Datomic schema below:

    [
     ;; seattle
    
     {:db/id #db/id[:db.part/db]
      :db/ident :seattle/name
      :db/valueType :db.type/string
      :db/cardinality :db.cardinality/one
      :db/fulltext true
      :db/doc "A community's name"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :seattle/url
      :db/valueType :db.type/string
      :db/cardinality :db.cardinality/one
      :db/doc "A community's url"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :seattle/neighborhood
      :db/valueType :db.type/ref
      :db/cardinality :db.cardinality/one
      :db/doc "A community's neighborhood"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :seattle/category
      :db/valueType :db.type/string
      :db/cardinality :db.cardinality/many
      :db/fulltext true
      :db/doc "All community categories"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :seattle/orgtype
      :db/valueType :db.type/ref
      :db/cardinality :db.cardinality/one
      :db/doc "A community orgtype enum value"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :seattle/type
      :db/valueType :db.type/ref
      :db/cardinality :db.cardinality/one
      :db/doc "A community type enum value"
      :db.install/_attribute :db.part/db}
    
     ;; community/org-type enum values
     [:db/add #db/id[:db.part/user] :db/ident :seattle.orgtype/community]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.orgtype/commercial]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.orgtype/nonprofit]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.orgtype/personal]
    
     ;; community/type enum values
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/email-list]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/twitter]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/facebook-page]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/blog]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/website]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/wiki]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/myspace]
     [:db/add #db/id[:db.part/user] :db/ident :seattle.type/ning]
    
     ;; neighborhood
     {:db/id #db/id[:db.part/db]
      :db/ident :neighborhood/name
      :db/valueType :db.type/string
      :db/cardinality :db.cardinality/one
      :db/unique :db.unique/identity
      :db/doc "A unique neighborhood name (upsertable)"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :neighborhood/district
      :db/valueType :db.type/ref
      :db/cardinality :db.cardinality/one
      :db/doc "A neighborhood's district"
      :db.install/_attribute :db.part/db}
    
     ;; district
     {:db/id #db/id[:db.part/db]
      :db/ident :district/name
      :db/valueType :db.type/string
      :db/cardinality :db.cardinality/one
      :db/unique :db.unique/identity
      :db/doc "A unique district name (upsertable)"
      :db.install/_attribute :db.part/db}
    
     {:db/id #db/id[:db.part/db]
      :db/ident :district/region
      :db/valueType :db.type/ref
      :db/cardinality :db.cardinality/one
      :db/doc "A district region enum value"
      :db.install/_attribute :db.part/db}
    
     ;; district/region enum values
     [:db/add #db/id[:db.part/user] :db/ident :district.region/n]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/ne]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/e]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/se]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/s]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/sw]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/w]
     [:db/add #db/id[:db.part/user] :db/ident :district.region/nw]
     ]
  2. Basic Usage

    The basic usage is:

    1. Create a connection
    2. Create schemas
    3. Create model instances
    4. Save model data
    datomic_uri = "datomic:mem://seattle-#{SecureRandom.uuid}"
    @s_conn2 = Diametric::Persistence::Peer.connect(datomic_uri)
    Neighborhood.create_schema(@s_conn2).get
    District.create_schema(@s_conn2).get
    Seattle.create_schema(@s_conn2).get
    
    district = District.new
    district.name = "East"
    district.region = District::Region::E
    neighborhood = Neighborhood.new
    neighborhood.name = "Capitol Hill"
    neighborhood.district = district
    seattle = Seattle.new
    seattle.name = "15th Ave Community"
    seattle.url = "http://groups.yahoo.com/group/15thAve_Community/"
    seattle.neighborhood = neighborhood
    seattle.category = ["15th avenue residents"]
    seattle.orgtype = Seattle::Orgtype::COMMUNITY
    seattle.type = Seattle::Type::EMAIL_LIST
    seattle.save(@n_conn2)
    • In this example, saving district and neighborhood model individually is an option. The last seattle.save will do at the same time.
    • Enum type has a format <module name>::(<module name>::)<constant name>. Thus, enum value of e in District model schema is District::Region::E. Also, enum value of community in Seattle model schema is Seattle::Orgtype::COMMUNITY.
  3. Query

    Query class constructor has three arguments:

    query = Diametric::Query.new(model, connection_or_database=nil, resolve=false)
    • First argument: model class
    • Second argument: connection or database (connection.db)
      If the value is not given, the default connection will be applied.
    • Third argument: resolve association recursively or not.
      By default, the association won't be resolved, so the reference value is a dbid of the instance.

    Example:

    query = Diametric::Query.new(Seattle, @s_conn2, true)
    seattle = query.where(:name => "15th Ave Community").first
    seattle.name #=> "15th Ave Community"
    seattle.url #=>  "http://groups.yahoo.com/group/15thAve_Community/"
    seattle.neighborhood.name #=> "Capitol Hill"
    seattle.neighborhood.district.name #=> "East"
    seattle.neighborhood.district.region #=> ":district.region/e"
    seattle.category #=> #<Set: {"15th avenue residents"}>
    seattle.orgtype #=> ":seattle.orgtype/community"
    seattle.type #=> ":seattle.type/email-list"
  4. Reading Seattle example data

    Seattle example has two sets of relatively big data, seattle-data0.dtm and seattle-data1.dtm. Creating each model instances like in the basic usage is not practical. Diametric gem supports Datomic's readAll method.

    filename = File.join("<path to>", "seattle-data0.dtm")
    list = Diametric::Persistence::Utils.read_all(filename)
    @s_conn2.transact(list.first).get

    Now, we can make queries to the data.

  5. Navigating up query

    On Datomic, the associations are bi-directional. As for seattle example, the association is "Seattle has a Neighborhood". From Seattle model instance, we can find an instance of Neighborhood model. Also, from Neighborhood model instance, we can find what Seattle instances have the neighborhood whose name is ...

    Example:

    neighborhood = Diametric::Query.new(Neighborhood, @s_conn2).where(:name => "Capitol Hill").first
    seattles = neighborhood.seattle_from_this_neighborhood(@s_conn2)
    seattles.collect(&:name) #=> ["15th Ave Community", "15th Ave Community", "Capitol Hill Community Council", "Capitol Hill Housing", "Capitol Hill Triangle", "CHS Capitol Hill Seattle Blog", "KOMO Communities - Captol Hill"]
  6. Going back to the past database state

    Datomic's database has an idea of "time". We can pull out a state of the database at some time in the past.

    Example:

    datomic_uri = "datomic:mem://seattle-#{SecureRandom.uuid}"
    conn = Diametric::Persistence::Peer.connect(datomic_uri)
    Neighborhood.create_schema(conn).get
    District.create_schema(conn).get
    Seattle.create_schema(conn).get
    filename = File.join("<path to>", "seattle-data0.dtm")
    list = Diametric::Persistence::Utils.read_all(filename)
    conn.transact(list.first).get
    query = Diametric::Query.new(Seattle, conn)
    query.all.size #=> 362
    # at this moment, 362 seattle instances are saved
    
    past = Time.now
    
    filename1 = File.join("<path to>", "seattle-data1.dtm")
    list1 = Diametric::Persistence::Utils.read_all(filename1)
    conn.transact(list1.first).get
    query = Diametric::Query.new(Seattle, conn)
    query.all.size #=> 571
    
    past_db = conn.db.as_of(past)
    query_to_past = Diametric::Query.new(Seattle, past_db)
    query_to_past.all.size #=> 362
    # before the "past", it was 362.