entitymap 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|