gom 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +111 -0
  3. data/Rakefile +48 -0
  4. data/lib/gom.rb +7 -0
  5. data/lib/gom/object.rb +23 -0
  6. data/lib/gom/object/id.rb +30 -0
  7. data/lib/gom/object/injector.rb +45 -0
  8. data/lib/gom/object/inspector.rb +55 -0
  9. data/lib/gom/object/mapping.rb +61 -0
  10. data/lib/gom/object/proxy.rb +44 -0
  11. data/lib/gom/spec.rb +4 -0
  12. data/lib/gom/spec/acceptance/adapter_with_stateful_storage.rb +111 -0
  13. data/lib/gom/spec/acceptance/read_only_adapter_with_stateless_storage.rb +50 -0
  14. data/lib/gom/storage.rb +35 -0
  15. data/lib/gom/storage/adapter.rb +51 -0
  16. data/lib/gom/storage/configuration.rb +65 -0
  17. data/lib/gom/storage/fetcher.rb +69 -0
  18. data/lib/gom/storage/remover.rb +47 -0
  19. data/lib/gom/storage/saver.rb +59 -0
  20. data/spec/acceptance/adapter_spec.rb +10 -0
  21. data/spec/acceptance/object_spec.rb +33 -0
  22. data/spec/fake_adapter.rb +37 -0
  23. data/spec/lib/gom/object/id_spec.rb +56 -0
  24. data/spec/lib/gom/object/injector_spec.rb +51 -0
  25. data/spec/lib/gom/object/inspector_spec.rb +30 -0
  26. data/spec/lib/gom/object/mapping_spec.rb +158 -0
  27. data/spec/lib/gom/object/proxy_spec.rb +91 -0
  28. data/spec/lib/gom/object_spec.rb +40 -0
  29. data/spec/lib/gom/storage/adapter_spec.rb +73 -0
  30. data/spec/lib/gom/storage/configuration_spec.rb +92 -0
  31. data/spec/lib/gom/storage/fetcher_spec.rb +89 -0
  32. data/spec/lib/gom/storage/remover_spec.rb +47 -0
  33. data/spec/lib/gom/storage/saver_spec.rb +86 -0
  34. data/spec/lib/gom/storage_spec.rb +106 -0
  35. data/spec/spec_helper.rb +7 -0
  36. data/spec/storage.configuration +4 -0
  37. metadata +138 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Philipp Brüll
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.rdoc ADDED
@@ -0,0 +1,111 @@
1
+
2
+ = Generic Object Mapper
3
+
4
+ The Generic Object Mapper maps ruby objects to different storage engines and vice versa. The interface is designed to
5
+ be small and try to avoid any unnecessary dependencies between GOM and your code. On the other side, the storage engine
6
+ is plugged-in via an adapter interface. Currently, the following adapters are provided.
7
+
8
+ * filesystem - http://github.com/phifty/gom-filesystem-adapter
9
+ * couchdb - http://github.com/phifty/gom-couchdb-adapter
10
+
11
+ == Configuration
12
+
13
+ At the beginning of your program the configuration should be read with the following command.
14
+
15
+ GOM::Storage::Configuration.read filename
16
+
17
+ The configuration file should be written in the YML format and look like...
18
+
19
+ storage_name:
20
+ adapter: filesystem
21
+ directory: /var/project-name/data
22
+
23
+ Look at the adapter pages to see the adapter-specific configuration values.
24
+
25
+ == How to use
26
+
27
+ === Storing an object
28
+
29
+ To store an object just pass it to <tt>GOM::Storage.store</tt>.
30
+
31
+ class Book
32
+
33
+ attr_accessor :author_name
34
+ attr_accessor :pages
35
+
36
+ end
37
+
38
+ book = Book.new
39
+ book.author_name = "Mr. Storyteller"
40
+ book.pages = 1253
41
+
42
+ GOM::Storage.store book, :storage_name
43
+
44
+ The storage name doesn't have to be specified. If it's missing, the object's previously use storage or the default
45
+ storage is used.
46
+
47
+ There is no base class needed for your model class. GOM inspects your object, reads all the instance variables and
48
+ passes the values to specified store adapter. The first time an object is stored, an id is generated and assigned to
49
+ the object. This id an be determined by calling <tt>GOM::Object.id</tt>.
50
+
51
+ book_id = GOM::Object.id book
52
+ # book_id => "storage_name:1234..."
53
+
54
+ === Fetching an object
55
+
56
+ Once an object is stored, it can be easily brought back to life by using it's id to fetch it from the storage.
57
+
58
+ book = GOM::Storage.fetch book_id
59
+
60
+ The storage name is encoded (prefixed) in the id and don't have to be specified. The classname of the object was also
61
+ saved during the storage and the fetch instantiate a new object using the constructor. If the constructor requires
62
+ arguments, <tt>nil</tt> will be passed for each of them. The internal state (the instance variables) will be
63
+ overwritten anyway.
64
+
65
+ === Removing an object
66
+
67
+ To remove an object from the storage, simply pass it to <tt>GOM::Storage.remove</tt>.
68
+
69
+ GOM::Storage.remove book
70
+
71
+ It's also possible to use just the id to remove the assigned object.
72
+
73
+ GOM::Storage.remove book_id
74
+
75
+ == Relations
76
+
77
+ GOM does make a distinction between object properties and object relations. The properties are more atomic values that
78
+ can be stored in a key/value-way and relations are links to more complex objects. Since in Ruby everything is an object,
79
+ it's necessary to mark the relations. This is done by the following way.
80
+
81
+ class Book
82
+
83
+ attr_accessor :author
84
+
85
+ end
86
+
87
+ class Author
88
+
89
+ attr_accessor :name
90
+
91
+ end
92
+
93
+ author = Author.new
94
+ author.name = "Mr. Storyteller"
95
+
96
+ book = Book.new
97
+ book.author = GOM::Object.reference author
98
+
99
+ The <tt>GOM::Object.reference</tt> call creates a proxy to the referenced object, that passes every call to it. For
100
+ example, the call
101
+
102
+ book.author.name
103
+
104
+ will return the instance variable <tt>@name</tt> ("Mr. Storyteller") from the author object.
105
+
106
+ == Development
107
+
108
+ Development has been done test-driven and the code follows at most the Clean Code paradigms. Code smells has been
109
+ removed by using the reek[http://github.com/kevinrutherford/reek] code smell detector.
110
+
111
+ This project is still experimental and under development. Any bug report and contribution is welcome!
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ gem 'rspec'
3
+ gem 'reek'
4
+ require 'rspec'
5
+ require 'rake/rdoctask'
6
+ require 'rspec/core/rake_task'
7
+ require 'reek/rake/task'
8
+
9
+ task :default => :spec
10
+
11
+ namespace :gem do
12
+
13
+ desc "Builds the gem"
14
+ task :build do
15
+ system "gem build *.gemspec && mkdir -p pkg/ && mv *.gem pkg/"
16
+ end
17
+
18
+ desc "Builds and installs the gem"
19
+ task :install => :build do
20
+ system "gem install pkg/"
21
+ end
22
+
23
+ end
24
+
25
+ Reek::Rake::Task.new do |task|
26
+ task.fail_on_error = true
27
+ end
28
+
29
+ desc "Generate the rdoc"
30
+ Rake::RDocTask.new do |rdoc|
31
+ rdoc.rdoc_files.add [ "README.rdoc", "lib/**/*.rb" ]
32
+ rdoc.main = "README.rdoc"
33
+ rdoc.title = ""
34
+ end
35
+
36
+ desc "Run all specs in spec directory"
37
+ RSpec::Core::RakeTask.new do |task|
38
+ task.pattern = "spec/gom/**/*_spec.rb"
39
+ end
40
+
41
+ namespace :spec do
42
+
43
+ desc "Run all integration specs in spec/acceptance directory"
44
+ RSpec::Core::RakeTask.new(:acceptance) do |task|
45
+ task.pattern = "spec/acceptance/**/*_spec.rb"
46
+ end
47
+
48
+ end
data/lib/gom.rb ADDED
@@ -0,0 +1,7 @@
1
+
2
+ module GOM
3
+
4
+ autoload :Object, File.join(File.dirname(__FILE__), "gom", "object")
5
+ autoload :Storage, File.join(File.dirname(__FILE__), "gom", "storage")
6
+
7
+ end
data/lib/gom/object.rb ADDED
@@ -0,0 +1,23 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ autoload :Id, File.join(File.dirname(__FILE__), "object", "id")
7
+ autoload :Injector, File.join(File.dirname(__FILE__), "object", "injector")
8
+ autoload :Inspector, File.join(File.dirname(__FILE__), "object", "inspector")
9
+ autoload :Mapping, File.join(File.dirname(__FILE__), "object", "mapping")
10
+ autoload :Proxy, File.join(File.dirname(__FILE__), "object", "proxy")
11
+
12
+ def self.id(object)
13
+ id = Mapping.id_by_object object
14
+ id ? id.to_s : nil
15
+ end
16
+
17
+ def self.reference(object)
18
+ Proxy.new object
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,30 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # Value class for object ids.
7
+ class Id
8
+
9
+ attr_accessor :storage_name
10
+ attr_accessor :object_id
11
+
12
+ def initialize(id_or_storage_name = nil, object_id = nil)
13
+ @storage_name, @object_id = id_or_storage_name.is_a?(String) ?
14
+ (object_id.is_a?(String) ? [ id_or_storage_name, object_id ] : id_or_storage_name.split(":")) :
15
+ [ nil, nil ]
16
+ end
17
+
18
+ def ==(other)
19
+ other.is_a?(self.class) && @storage_name == other.storage_name && @object_id == other.object_id
20
+ end
21
+
22
+ def to_s
23
+ "#{@storage_name}:#{@object_id}"
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,45 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # Injects the given properties into the given object.s
7
+ class Injector
8
+
9
+ attr_reader :object
10
+
11
+ def initialize(object, object_hash)
12
+ @object, @object_hash = object, object_hash
13
+ end
14
+
15
+ def perform
16
+ clear_instance_variables
17
+ write_properties
18
+ write_relations
19
+ end
20
+
21
+ private
22
+
23
+ def clear_instance_variables
24
+ @object.instance_variables.each do |name|
25
+ @object.send :remove_instance_variable, name
26
+ end
27
+ end
28
+
29
+ def write_properties
30
+ (@object_hash[:properties] || { }).each do |name, value|
31
+ @object.instance_variable_set :"@#{name}", value
32
+ end
33
+ end
34
+
35
+ def write_relations
36
+ (@object_hash[:relations] || { }).each do |name, value|
37
+ @object.instance_variable_set :"@#{name}", value
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,55 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # Inspect an object and returns it's class and it's properties
7
+ class Inspector
8
+
9
+ attr_reader :object
10
+ attr_reader :object_hash
11
+
12
+ def initialize(object)
13
+ @object = object
14
+ @object_hash = { }
15
+ end
16
+
17
+ def perform
18
+ read_class
19
+ read_properties
20
+ read_relations
21
+ end
22
+
23
+ private
24
+
25
+ def read_class
26
+ @object_hash[:class] = @object.class.to_s
27
+ end
28
+
29
+ def read_properties
30
+ @object_hash[:properties] = { }
31
+ read_instance_variables do |key, value|
32
+ @object_hash[:properties][key] = value unless value.is_a?(GOM::Object::Proxy)
33
+ end
34
+ end
35
+
36
+ def read_relations
37
+ @object_hash[:relations] = { }
38
+ read_instance_variables do |key, value|
39
+ @object_hash[:relations][key] = value if value.is_a?(GOM::Object::Proxy)
40
+ end
41
+ end
42
+
43
+ def read_instance_variables
44
+ @object.instance_variables.each do |name|
45
+ key = name.to_s.sub(/^@/, "").to_sym
46
+ value = @object.instance_variable_get name
47
+ yield key, value
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,61 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # Provides a mapping between objects and ids
7
+ class Mapping
8
+
9
+ def initialize
10
+ @map = { }
11
+ end
12
+
13
+ def put(object, id)
14
+ @map[object] = id
15
+ end
16
+
17
+ def object_by_id(id)
18
+ @map.respond_to?(:key) ? @map.key(id) : @map.index(id)
19
+ end
20
+
21
+ def id_by_object(object)
22
+ @map[object]
23
+ end
24
+
25
+ def remove_by_id(id)
26
+ @map.delete object_by_id(id)
27
+ end
28
+
29
+ def remove_by_object(object)
30
+ @map.delete object
31
+ end
32
+
33
+ def self.singleton
34
+ @mapping ||= self.new
35
+ end
36
+
37
+ def self.put(object, id)
38
+ self.singleton.put object, id
39
+ end
40
+
41
+ def self.object_by_id(id)
42
+ self.singleton.object_by_id id
43
+ end
44
+
45
+ def self.id_by_object(object)
46
+ self.singleton.id_by_object object
47
+ end
48
+
49
+ def self.remove_by_id(id)
50
+ self.singleton.remove_by_id id
51
+ end
52
+
53
+ def self.remove_by_object(object)
54
+ self.singleton.remove_by_object object
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # The proxy that fetches an object if it's needed and simply passes method calls to it.
7
+ class Proxy
8
+
9
+ def initialize(object_or_id)
10
+ @object, @id = object_or_id.is_a?(GOM::Object::Id) ?
11
+ [ nil, object_or_id ] :
12
+ [ object_or_id, nil ]
13
+ end
14
+
15
+ def object
16
+ fetch_object unless @object
17
+ @object
18
+ end
19
+
20
+ def id
21
+ fetch_id unless @id
22
+ @id
23
+ end
24
+
25
+ def method_missing(method_name, *arguments, &block)
26
+ fetch_object unless @object
27
+ @object.send method_name, *arguments, &block
28
+ end
29
+
30
+ private
31
+
32
+ def fetch_object
33
+ @object = GOM::Storage::Fetcher.new(@id).object
34
+ end
35
+
36
+ def fetch_id
37
+ @id = GOM::Object::Mapping.id_by_object @object
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end