EntityMap

The idea is to have an in-memory entity-relationsip model that has unique and unambiguous mapping of the (business) entities and the relationships between them. On the other hand, the lowest level will be limited to the simple concept of a foreign key/pointer from one entity to another, as an unambiguous implementation of an asymmetric relationship between two entities.

In ActiveRecord, there is an IdentityMap being developed, but even with IdentityMap turned on, the belongs_to and has_many relationships between Parent and Child are not uniquely mapped to each_other. It is possible to have at the same time

daisy.children # => [#<Child:0x9275fe8 @name="Peter">]
peter.mother # => #<Mother:0x964c518 @name="Maria">

If I am the child of Daisy, then she should be my mother, no?

So, the focus is on unambiguous and easy use of the relationships in an ER model (Entity-Relationship model) where the reciprocity of the view on the 2 directions of the same relationship is guaranteed correct.

Mapping this form of ER-model to a database as a persistence layer is a different aspect, after establishing the correct Entity and Relationship behavior.

Feature wish list

  • it "handles belongs_to - has_many atomically in 1 place"
  • it "has identity_map for entities"
  • it "can be persisted in 1 command"
  • it "will check validations on all entities before persisting"
  • it "allows select list in a form to work cleverly"
  • it "has 'live' scopes that are honored in memory"
  • it "can be marshalled"
  • it "works with a Plain Old Ruby Object"
  • it "works with other ORMs (ActiveRecord, Sequel, Datamapper)"
  • it "works with SimpleForm 2"
  • it "works with JRuby"
  • it "works in other languages (Java, ...) ?"

Usage Example

I want to be able to do:

class Parent
  include EntityMap #(A)
  entity_attributes :name #(B)
end

class Child
  include EntityMap
  entity_attributes :name, :grade
end

Child.relates_to Parent, :as => :mother #(B)
Child.relates_to Parent, :as => :father #(C)

daisy = Parent.new(:name => "Daisy")
peter = Child.new(:name => "Peter")

peter.mother = daisy #(D)

peter.mother # => #<Parent:0x961af18 @name="Daisy"> #(E)
daisy.mother_of # => [#<Child:0x9275fe8 @name="Peter">] #(F)

peter.mother = nil #(G)

peter.mother # => nil
daisy.mother_of # => [] #(H)

daisy.mother_of << peter #(I)

daisy.mother_of # => [#<Child:0x9275fe8 @name="Peter">]
peter.mother # => #<Parent:0x961af18 @name="Daisy"> #(J)

maria = Parent.new(:name => "Maria") #(K)

peter.mother = maria

peter.mother # => #<Parent:0x964c518 @name="Maria">
maria.mother_of # => [#<Child:0x9275fe8 @name="Peter">] #(L)
daisy.mother_of # => [] #(M)

peter.mother = daisy # back to reality

frans = Parent.new(:name => "Frans")
peter.father = frans

jan = Child.new(:name => "Jan")
jan.mother = maria
jan.father = frans

peter.father.father_of # => [#<Child:0x9275fe8 @name="Peter">, #<Child:0xa49e030 @name="Jan">] #(N)

# adding an implementation of persistence

class Child
  include ActiveRecord::Model
  include EntityMap
end

class Parent
  include ActiveRecord::Model
  include EntityMap
end

EntityMap.save # => true #(O)
peter.id # => 1 #(P)
jan.id # => 2
new_peter = Child.find(1)
peter.object_id == new_peter.object_id # => true #(Q)

Example of the Parent-Child ambiguity in ActiveRecord (4.0.0.beta)

peterv@ASUS:~/b/github/rails/rails/new_app$ cat app/models/*
class Child < ActiveRecord::Base
  belongs_to :parent, :inverse_of => :children
end
class Parent < ActiveRecord::Base
  has_many :children, :inverse_of => :parent
end

peterv@ASUS:~/b/github/rails/rails/new_app$ bundle exec rails c
Loading development environment (Rails 4.0.0.beta)
001:0> daisy = Parent.new(:name => "Daisy")
=> #<Parent id: nil, name: "Daisy", created_at: nil, updated_at: nil>
002:0> peter = Child.new(:name => "Peter")
=> #<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>
003:0> peter.parent = daisy
=> #<Parent id: nil, name: "Daisy", created_at: nil, updated_at: nil>
004:0> daisy.children
=> []
005:0> daisy.children << peter
=> [#<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>]
006:0> daisy.children
=> [#<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>]
007:0> maria = Parent.new(:name => "Maria")
=> #<Parent id: nil, name: "Maria", created_at: nil, updated_at: nil>
009:0> peter.parent = maria
=> #<Parent id: nil, name: "Maria", created_at: nil, updated_at: nil>
010:0> maria.children
=> []
011:0> daisy.children
=> [#<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>]
012:0> peter.parent
=> #<Parent id: nil, name: "Maria", created_at: nil, updated_at: nil>
013:0> # giving up ... my mother is "Maria", but I am child of "Daisy" ...