gom 0.1.0

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