entitymap 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +155 -0
- data/Rakefile +1 -0
- data/entitymap.gemspec +25 -0
- data/lib/entity_map/entity/class_methods.rb +11 -0
- data/lib/entity_map/entity/methods.rb +11 -0
- data/lib/entity_map/entity/new.rb +24 -0
- data/lib/entity_map/entity.rb +3 -0
- data/lib/entity_map/entity_group/find.rb +15 -0
- data/lib/entity_map/entity_group/initialize.rb +9 -0
- data/lib/entity_map/entity_group/single.rb +11 -0
- data/lib/entity_map/entity_group.rb +3 -0
- data/lib/entity_map/included.rb +10 -0
- data/lib/entity_map/version.rb +3 -0
- data/lib/entity_map.rb +4 -0
- data/spec/entity/class_methods_spec.rb +20 -0
- data/spec/entity/include_spec.rb +27 -0
- data/spec/entity/methods_spec.rb +26 -0
- data/spec/entity/new_spec.rb +25 -0
- data/spec/entity_group/find_spec.rb +26 -0
- data/spec/entity_group/new_spec.rb +12 -0
- data/spec/entity_group/single_spec.rb +19 -0
- data/spec/spec_helper.rb +1 -0
- metadata +88 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 Peter Vandenabeele (http://vandenabeele.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# EntityMap
|
2
|
+
|
3
|
+
The concept is to have an in-memory entity-relationsip model that has unique and
|
4
|
+
unambiguous mapping of the (business) entities and the relationships between them,
|
5
|
+
but still closely linked to the SQL concept of a foreign key as unambiguous
|
6
|
+
implementation of a relationship between tables).
|
7
|
+
|
8
|
+
In ActiveRecord, there is an IdentityMap being developed, but even with
|
9
|
+
IdentityMap turned on, the belongs_to and has_many relationships between
|
10
|
+
Parent and Child are not uniquely mapped to each_other. It is possible to
|
11
|
+
have at the same time
|
12
|
+
|
13
|
+
``
|
14
|
+
daisy.children # => [#<Child:0x9275fe8 @name="Peter">]
|
15
|
+
peter.mother # => #<Mother:0x964c518 @name="Maria">
|
16
|
+
```
|
17
|
+
If I am the child of Daisy, then she should be my mother, huh ...
|
18
|
+
|
19
|
+
So, the focus is on unambiguous and easy use of the relationships
|
20
|
+
in an ER model (Entity-Relationship model) where the reciprocity
|
21
|
+
of the 2 directions of the relationship is guaranteed correct.
|
22
|
+
|
23
|
+
Mapping this form of ER-model to a database as a persistence
|
24
|
+
layer is a second priority, after establishing the correct
|
25
|
+
Entity and Relationship behavior.
|
26
|
+
|
27
|
+
## Feature wish list
|
28
|
+
|
29
|
+
it "handles belongs_to - has_many atomically in 1 place"
|
30
|
+
it "has identity_map for entities"
|
31
|
+
it "can be persisted in 1 command"
|
32
|
+
it "will check validations on _all_ entities before persisting"
|
33
|
+
it "allows select list in a form to work cleverly"
|
34
|
+
it "has 'live' scopes that are honored in memory"
|
35
|
+
it "can be marshalled"
|
36
|
+
it "works with a Plain Old Ruby Object"
|
37
|
+
it "works with other ORMs (ActiveRecord, Sequel, Datamapper)"
|
38
|
+
it "works with SimpleForm 2"
|
39
|
+
it "works with JRuby"
|
40
|
+
it "works in other languages (Java, ...) ?"
|
41
|
+
|
42
|
+
## Usage Example
|
43
|
+
|
44
|
+
I want to be able to do:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class Parent
|
48
|
+
include EntityMap #(A)
|
49
|
+
entity_attributes :name #(B)
|
50
|
+
end
|
51
|
+
|
52
|
+
class Child
|
53
|
+
include EntityMap
|
54
|
+
entity_attributes :name, :grade
|
55
|
+
end
|
56
|
+
|
57
|
+
Child.relates_to Parent, :as => :mother #(B)
|
58
|
+
Child.relates_to Parent, :as => :father #(C)
|
59
|
+
|
60
|
+
daisy = Parent.new(:name => "Daisy")
|
61
|
+
peter = Child.new(:name => "Peter")
|
62
|
+
|
63
|
+
peter.mother = daisy #(D)
|
64
|
+
|
65
|
+
peter.mother # => #<Parent:0x961af18 @name="Daisy"> #(E)
|
66
|
+
daisy.mother_of # => [#<Child:0x9275fe8 @name="Peter">] #(F)
|
67
|
+
|
68
|
+
peter.mother = nil #(G)
|
69
|
+
|
70
|
+
peter.mother # => nil
|
71
|
+
daisy.mother_of # => [] #(H)
|
72
|
+
|
73
|
+
daisy.mother_of << peter #(I)
|
74
|
+
|
75
|
+
daisy.mother_of # => [#<Child:0x9275fe8 @name="Peter">]
|
76
|
+
peter.mother # => #<Parent:0x961af18 @name="Daisy"> #(J)
|
77
|
+
|
78
|
+
maria = Parent.new(:name => "Maria") #(K)
|
79
|
+
|
80
|
+
peter.mother = maria
|
81
|
+
|
82
|
+
peter.mother # => #<Parent:0x964c518 @name="Maria">
|
83
|
+
maria.mother_of # => [#<Child:0x9275fe8 @name="Peter">] #(L)
|
84
|
+
daisy.mother_of # => [] #(M)
|
85
|
+
|
86
|
+
peter.mother = daisy # back to reality
|
87
|
+
|
88
|
+
frans = Parent.new(:name => "Frans")
|
89
|
+
peter.father = frans
|
90
|
+
|
91
|
+
jan = Child.new(:name => "Jan")
|
92
|
+
jan.mother = maria
|
93
|
+
jan.father = frans
|
94
|
+
|
95
|
+
peter.father.father_of # => [#<Child:0x9275fe8 @name="Peter">, #<Child:0xa49e030 @name="Jan">] #(N)
|
96
|
+
|
97
|
+
# adding an implementation of persistence
|
98
|
+
|
99
|
+
class Child
|
100
|
+
# not sure if this is enough ActiveRecord
|
101
|
+
include ActiveRecord::Persistence
|
102
|
+
include ActiveRecord::FinderMethods
|
103
|
+
end
|
104
|
+
|
105
|
+
class Parent
|
106
|
+
# not sure if this is enough ActiveRecord
|
107
|
+
include ActiveRecord::Persistence
|
108
|
+
include ActiveRecord::FinderMethods
|
109
|
+
end
|
110
|
+
|
111
|
+
EntityMap.save # => true #(O)
|
112
|
+
peter.id # => 1 #(P)
|
113
|
+
jan.id # => 2
|
114
|
+
new_peter = Child.find(1)
|
115
|
+
peter.object_id == new_peter.object_id # => true #(Q)
|
116
|
+
peter.ref == new_peter.ref # => true #(R)
|
117
|
+
```
|
118
|
+
|
119
|
+
## Example of the Parent-Child ambiguity in ActiveRecord (4.0.0.beta)
|
120
|
+
|
121
|
+
```
|
122
|
+
peterv@ASUS:~/b/github/rails/rails/new_app$ cat app/models/*
|
123
|
+
class Child < ActiveRecord::Base
|
124
|
+
belongs_to :parent, :inverse_of => :children
|
125
|
+
end
|
126
|
+
class Parent < ActiveRecord::Base
|
127
|
+
has_many :children, :inverse_of => :parent
|
128
|
+
end
|
129
|
+
|
130
|
+
peterv@ASUS:~/b/github/rails/rails/new_app$ bundle exec rails c
|
131
|
+
Loading development environment (Rails 4.0.0.beta)
|
132
|
+
001:0> daisy = Parent.new(:name => "Daisy")
|
133
|
+
=> #<Parent id: nil, name: "Daisy", created_at: nil, updated_at: nil>
|
134
|
+
002:0> peter = Child.new(:name => "Peter")
|
135
|
+
=> #<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>
|
136
|
+
003:0> peter.parent = daisy
|
137
|
+
=> #<Parent id: nil, name: "Daisy", created_at: nil, updated_at: nil>
|
138
|
+
004:0> daisy.children
|
139
|
+
=> []
|
140
|
+
005:0> daisy.children << peter
|
141
|
+
=> [#<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>]
|
142
|
+
006:0> daisy.children
|
143
|
+
=> [#<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>]
|
144
|
+
007:0> maria = Parent.new(:name => "Maria")
|
145
|
+
=> #<Parent id: nil, name: "Maria", created_at: nil, updated_at: nil>
|
146
|
+
009:0> peter.parent = maria
|
147
|
+
=> #<Parent id: nil, name: "Maria", created_at: nil, updated_at: nil>
|
148
|
+
010:0> maria.children
|
149
|
+
=> []
|
150
|
+
011:0> daisy.children
|
151
|
+
=> [#<Child id: nil, name: "Peter", parent_id: nil, created_at: nil, updated_at: nil>]
|
152
|
+
012:0> peter.parent
|
153
|
+
=> #<Parent id: nil, name: "Maria", created_at: nil, updated_at: nil>
|
154
|
+
013:0> # giving up ... my mother is "Maria", but I am child of "Daisy" ...
|
155
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/entitymap.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "entity_map/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "entitymap"
|
7
|
+
s.version = EntityMap::VERSION
|
8
|
+
s.authors = ["Peter Vandenabeele"]
|
9
|
+
s.email = ["peter@vandenabeele.com"]
|
10
|
+
s.homepage = "http://github.com/petervandenabeele/entitymap"
|
11
|
+
s.summary = %q{Mapping business entities and relations}
|
12
|
+
s.description = <<DESCRIPTION
|
13
|
+
Mapping entities and relations atomically in memory during the
|
14
|
+
processing of a business transaction.
|
15
|
+
Persistance is executed later in 1 separate atomic transaction.
|
16
|
+
DESCRIPTION
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
# s.add_runtime_dependency "none for the moment"
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module EntityMap
|
2
|
+
class Entity
|
3
|
+
|
4
|
+
attr_reader :object, :entity_group
|
5
|
+
|
6
|
+
def initialize(object, entity_group)
|
7
|
+
raise "INTERNAL ERROR: Entity.new must have EntityGroup argument" unless EntityGroup === entity_group
|
8
|
+
@entity_group = entity_group
|
9
|
+
@object = object
|
10
|
+
insert_into_entity_hash
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def insert_into_entity_hash
|
16
|
+
eh = @entity_group.instance_variable_get(:@entity_hash)
|
17
|
+
ref = Entity.object_ref(@object)
|
18
|
+
# object_ref should be unique in the hash
|
19
|
+
raise "INTERNAL ERROR: Second Entity for same object_ref is built" if eh[ref]
|
20
|
+
eh[ref] = self
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/entity_map.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EntityMap
|
4
|
+
|
5
|
+
describe Entity, "class methods" do
|
6
|
+
|
7
|
+
it "object_ref is equal for same objects" do
|
8
|
+
s1 = "test"
|
9
|
+
Entity.object_ref(s1).should == Entity.object_ref(s1)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "object_ref is different for different objects" do
|
13
|
+
s1 = "test"
|
14
|
+
s2 = "test"
|
15
|
+
Entity.object_ref(s1).should_not == Entity.object_ref(s2)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EntityMap
|
4
|
+
|
5
|
+
describe Entity do
|
6
|
+
|
7
|
+
it "creates a new entity when an object in target class is created" do
|
8
|
+
class Test
|
9
|
+
include EntityMap
|
10
|
+
end
|
11
|
+
|
12
|
+
t = Test.new
|
13
|
+
EntityGroup.find(t).should_not be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises error when object with same object_ref would be added to hash" do
|
17
|
+
class Test
|
18
|
+
include EntityMap
|
19
|
+
end
|
20
|
+
|
21
|
+
t = Test.new
|
22
|
+
lambda {Entity.new(t, EntityGroup.single)}.should raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EntityMap
|
4
|
+
|
5
|
+
describe Entity do
|
6
|
+
|
7
|
+
let(:random_object) {Object.new}
|
8
|
+
let(:entity_group) {EntityGroup.new}
|
9
|
+
let(:entity) {Entity.new(random_object, entity_group)}
|
10
|
+
|
11
|
+
it "== uses object_ref to check" do
|
12
|
+
entity.should == entity
|
13
|
+
end
|
14
|
+
|
15
|
+
it "eql uses object_ref to check" do
|
16
|
+
entity.eql?(entity).should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "different entity is not ==" do
|
20
|
+
entity_2=Entity.new("a", entity_group)
|
21
|
+
entity.should_not == entity_2
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EntityMap
|
4
|
+
|
5
|
+
describe Entity do
|
6
|
+
|
7
|
+
let(:random_object) {Object.new}
|
8
|
+
let(:entity_group) {EntityGroup.new}
|
9
|
+
let(:entity) {Entity.new(random_object, entity_group)}
|
10
|
+
|
11
|
+
it "new creates new entity" do
|
12
|
+
entity.should_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is part of an EntityGroup" do
|
16
|
+
entity.entity_group.should_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "raises an exception when supplied entity_group is not correct" do
|
20
|
+
lambda {Entity.new(random_object,nil)}.should raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EntityMap
|
4
|
+
|
5
|
+
describe EntityGroup do
|
6
|
+
|
7
|
+
let(:object) {Object.new}
|
8
|
+
|
9
|
+
it "finds an entity in this group (INSTANCE)" do
|
10
|
+
entity = Entity.new(object,subject)
|
11
|
+
eh = subject.instance_variable_get(:@entity_hash)
|
12
|
+
eh[Entity.object_ref(object)] = entity
|
13
|
+
subject.find(object).should == entity
|
14
|
+
end
|
15
|
+
|
16
|
+
it "finds an entity in all groups (CLASS)" do
|
17
|
+
entity_group = EntityGroup.single
|
18
|
+
entity = Entity.new(object, entity_group)
|
19
|
+
eh = entity_group.instance_variable_get(:@entity_hash)
|
20
|
+
eh[Entity.object_ref(object)] = entity
|
21
|
+
EntityGroup.find(object).should == entity
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module EntityMap
|
4
|
+
|
5
|
+
describe EntityGroup do
|
6
|
+
|
7
|
+
# in a later phase, multiple groups can exist
|
8
|
+
it "single returns an instance of EntityGroup" do
|
9
|
+
EntityGroup.single.should be_a(EntityGroup)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "single returns a singleton instance of EntityGroup" do
|
13
|
+
eg_1 = EntityGroup.single
|
14
|
+
eg_2 = EntityGroup.single
|
15
|
+
eg_1.object_id.should == eg_2.object_id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'entity_map'
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: entitymap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Vandenabeele
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &72628490 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *72628490
|
25
|
+
description: ! 'Mapping entities and relations atomically in memory during the
|
26
|
+
|
27
|
+
processing of a business transaction.
|
28
|
+
|
29
|
+
Persistance is executed later in 1 separate atomic transaction.
|
30
|
+
|
31
|
+
'
|
32
|
+
email:
|
33
|
+
- peter@vandenabeele.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- .gitignore
|
39
|
+
- .rspec
|
40
|
+
- Gemfile
|
41
|
+
- MIT-LICENSE
|
42
|
+
- README.md
|
43
|
+
- Rakefile
|
44
|
+
- entitymap.gemspec
|
45
|
+
- lib/entity_map.rb
|
46
|
+
- lib/entity_map/entity.rb
|
47
|
+
- lib/entity_map/entity/class_methods.rb
|
48
|
+
- lib/entity_map/entity/methods.rb
|
49
|
+
- lib/entity_map/entity/new.rb
|
50
|
+
- lib/entity_map/entity_group.rb
|
51
|
+
- lib/entity_map/entity_group/find.rb
|
52
|
+
- lib/entity_map/entity_group/initialize.rb
|
53
|
+
- lib/entity_map/entity_group/single.rb
|
54
|
+
- lib/entity_map/included.rb
|
55
|
+
- lib/entity_map/version.rb
|
56
|
+
- spec/entity/class_methods_spec.rb
|
57
|
+
- spec/entity/include_spec.rb
|
58
|
+
- spec/entity/methods_spec.rb
|
59
|
+
- spec/entity/new_spec.rb
|
60
|
+
- spec/entity_group/find_spec.rb
|
61
|
+
- spec/entity_group/new_spec.rb
|
62
|
+
- spec/entity_group/single_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
homepage: http://github.com/petervandenabeele/entitymap
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.10
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Mapping business entities and relations
|
88
|
+
test_files: []
|