entitymap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in entity_map.gemspec
4
+ gemspec
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,11 @@
1
+ module EntityMap
2
+ class Entity
3
+
4
+ class << self
5
+ def object_ref(o)
6
+ o.object_id
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module EntityMap
2
+ class Entity
3
+
4
+ def ==(other)
5
+ Entity.object_ref(self) == Entity.object_ref(other)
6
+ end
7
+
8
+ alias :eql? :==
9
+
10
+ end
11
+ 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
@@ -0,0 +1,3 @@
1
+ require 'entity_map/entity/new'
2
+ require 'entity_map/entity/methods'
3
+ require 'entity_map/entity/class_methods'
@@ -0,0 +1,15 @@
1
+ module EntityMap
2
+ class EntityGroup
3
+
4
+ def find(o)
5
+ @entity_hash[Entity.object_ref(o)]
6
+ end
7
+
8
+ class << self
9
+ def find(o)
10
+ single.find(o)
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module EntityMap
2
+ class EntityGroup
3
+
4
+ def initialize
5
+ @entity_hash = {}
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module EntityMap
2
+ class EntityGroup
3
+
4
+ class << self
5
+ def single
6
+ @single ||=EntityGroup.new
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ require 'entity_map/entity_group/initialize'
2
+ require 'entity_map/entity_group/single'
3
+ require 'entity_map/entity_group/find'
@@ -0,0 +1,10 @@
1
+ module EntityMap
2
+
3
+ attr_accessor :entity
4
+
5
+ def initialize
6
+ entity = Entity.new(self, EntityGroup.single)
7
+ super
8
+ end
9
+
10
+ end
@@ -0,0 +1,3 @@
1
+ module EntityMap
2
+ VERSION = "0.0.1"
3
+ end
data/lib/entity_map.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'entity_map/version'
2
+ require 'entity_map/entity_group'
3
+ require 'entity_map/entity'
4
+ require 'entity_map/included'
@@ -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,12 @@
1
+ require 'spec_helper'
2
+
3
+ module EntityMap
4
+
5
+ describe EntityGroup do
6
+
7
+ it "new creates new entity_group" do
8
+ subject.should_not be_nil
9
+ end
10
+ end
11
+
12
+ 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
@@ -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: []