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" ...