gom 0.1.0 → 0.2.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 (36) hide show
  1. data/README.rdoc +70 -0
  2. data/lib/gom/object.rb +4 -1
  3. data/lib/gom/object/builder.rb +47 -0
  4. data/lib/gom/object/cached_builder.rb +42 -0
  5. data/lib/gom/object/collection.rb +72 -0
  6. data/lib/gom/object/draft.rb +34 -0
  7. data/lib/gom/object/inspector.rb +29 -15
  8. data/lib/gom/spec/acceptance/adapter_with_stateful_storage.rb +74 -5
  9. data/lib/gom/spec/acceptance/read_only_adapter_with_stateless_storage.rb +20 -0
  10. data/lib/gom/storage.rb +6 -2
  11. data/lib/gom/storage/adapter.rb +11 -4
  12. data/lib/gom/storage/configuration.rb +30 -1
  13. data/lib/gom/storage/configuration/view.rb +20 -0
  14. data/lib/gom/storage/configuration/view/class.rb +28 -0
  15. data/lib/gom/storage/configuration/view/map_reduce.rb +29 -0
  16. data/lib/gom/storage/fetcher.rb +9 -34
  17. data/lib/gom/storage/saver.rb +5 -7
  18. data/spec/acceptance/object_spec.rb +1 -0
  19. data/spec/fake_adapter.rb +55 -10
  20. data/spec/lib/gom/object/builder_spec.rb +51 -0
  21. data/spec/lib/gom/object/cached_builder_spec.rb +43 -0
  22. data/spec/lib/gom/object/collection_spec.rb +158 -0
  23. data/spec/lib/gom/object/draft_spec.rb +59 -0
  24. data/spec/lib/gom/object/inspector_spec.rb +8 -10
  25. data/spec/lib/gom/storage/adapter_spec.rb +7 -33
  26. data/spec/lib/gom/storage/configuration/view/class_spec.rb +17 -0
  27. data/spec/lib/gom/storage/configuration/view/map_reduce_spec.rb +21 -0
  28. data/spec/lib/gom/storage/configuration_spec.rb +29 -0
  29. data/spec/lib/gom/storage/fetcher_spec.rb +7 -38
  30. data/spec/lib/gom/storage/remover_spec.rb +7 -9
  31. data/spec/lib/gom/storage/saver_spec.rb +17 -33
  32. data/spec/lib/gom/storage_spec.rb +33 -9
  33. data/spec/storage.configuration +10 -0
  34. metadata +22 -6
  35. data/lib/gom/object/injector.rb +0 -45
  36. data/spec/lib/gom/object/injector_spec.rb +0 -51
@@ -103,6 +103,76 @@ example, the call
103
103
 
104
104
  will return the instance variable <tt>@name</tt> ("Mr. Storyteller") from the author object.
105
105
 
106
+ == Views
107
+
108
+ Views are a kind of prepared queries to the data store. They are initialized during the setup and provide collections
109
+ of results at runtime. There are several kinds of views.
110
+
111
+ === Class views
112
+
113
+ There views simply provides a collection of all object of a specified class. They are defined at the storage
114
+ configuration.
115
+
116
+ storage_name:
117
+ adapter: filesystem
118
+ directory: /var/project-name/data
119
+ views:
120
+ users:
121
+ type: class
122
+ class: User
123
+
124
+ The example defines a class view for all objects of the class <tt>User</tt>. The result can be fetched via...
125
+
126
+ users = GOM::Storage.collection :storage_name, :users
127
+
128
+ Collections can be handled like (read-only) ruby-arrays and the data will be fetched by the first read access to that
129
+ array.
130
+
131
+ === Map/Reduce views
132
+
133
+ These views are also defined in the storage configuration.
134
+
135
+ storage_name:
136
+ adapter: couchdb
137
+ views:
138
+ active_user_count:
139
+ type: map_reduce
140
+ map:
141
+ function(document) {
142
+ if (document['model_class'] == 'User' && document['active']) {
143
+ emit(document['_id'], 1);
144
+ }
145
+ }
146
+ reduce:
147
+ function(keys, values, rereduce) {
148
+ return sum(values);
149
+ }
150
+
151
+ The example defined a map/reduce view that results in a single row with the count of all active users. This row can be
152
+ fetched by...
153
+
154
+ rows = GOM::Storage.collection :storage_name, :active_user_count
155
+ rows.first.value # => 123
156
+
157
+ If no <tt>reduce</tt> method is given, <tt>GOM</tt> will try to map the fetched data back to ruby-objects. The
158
+ definition would be...
159
+
160
+ storage_name:
161
+ adapter: couchdb
162
+ views:
163
+ active_users:
164
+ type: map_reduce
165
+ map:
166
+ function(document) {
167
+ if (document['model_class'] == 'User') {
168
+ emit(document['_id'], null);
169
+ }
170
+ }
171
+
172
+ ...and the fetch is done by...
173
+
174
+ active_users = GOM::Storage.collection :storage_name, :active_users
175
+
106
176
  == Development
107
177
 
108
178
  Development has been done test-driven and the code follows at most the Clean Code paradigms. Code smells has been
@@ -3,8 +3,11 @@ module GOM
3
3
 
4
4
  module Object
5
5
 
6
+ autoload :Builder, File.join(File.dirname(__FILE__), "object", "builder")
7
+ autoload :CachedBuilder, File.join(File.dirname(__FILE__), "object", "cached_builder")
8
+ autoload :Collection, File.join(File.dirname(__FILE__), "object", "collection")
9
+ autoload :Draft, File.join(File.dirname(__FILE__), "object", "draft")
6
10
  autoload :Id, File.join(File.dirname(__FILE__), "object", "id")
7
- autoload :Injector, File.join(File.dirname(__FILE__), "object", "injector")
8
11
  autoload :Inspector, File.join(File.dirname(__FILE__), "object", "inspector")
9
12
  autoload :Mapping, File.join(File.dirname(__FILE__), "object", "mapping")
10
13
  autoload :Proxy, File.join(File.dirname(__FILE__), "object", "proxy")
@@ -0,0 +1,47 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # Build an object out of the given draft.
7
+ class Builder
8
+
9
+ attr_accessor :draft
10
+ attr_writer :object
11
+
12
+ def initialize(draft, object = nil)
13
+ @draft, @object = draft, object
14
+ end
15
+
16
+ def object
17
+ initialize_object unless @object
18
+ set_properties
19
+ set_relations
20
+ @object
21
+ end
22
+
23
+ private
24
+
25
+ def initialize_object
26
+ klass = Object.const_get @draft.class_name
27
+ arity = [ klass.method(:new).arity, 0 ].max
28
+ @object = klass.new *([ nil ] * arity)
29
+ end
30
+
31
+ def set_properties
32
+ @draft.properties.each do |name, value|
33
+ @object.instance_variable_set :"@#{name}", value
34
+ end
35
+ end
36
+
37
+ def set_relations
38
+ @draft.relations.each do |name, value|
39
+ @object.instance_variable_set :"@#{name}", value
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # Build an object out of the given draft using Builder. Uses the object-id mapping
7
+ # for caching the results.
8
+ class CachedBuilder
9
+
10
+ attr_accessor :draft
11
+ attr_accessor :id
12
+
13
+ def initialize(draft, id = nil)
14
+ @draft, @id = draft, id
15
+ end
16
+
17
+ def object
18
+ check_mapping
19
+ build_object
20
+ set_mapping
21
+ @object
22
+ end
23
+
24
+ private
25
+
26
+ def check_mapping
27
+ @object = GOM::Object::Mapping.object_by_id @id
28
+ end
29
+
30
+ def build_object
31
+ @object = GOM::Object::Builder.new(@draft, @object).object
32
+ end
33
+
34
+ def set_mapping
35
+ GOM::Object::Mapping.put @object, @id
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,72 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # A class for a collection of objects.
7
+ class Collection
8
+
9
+ def initialize(fetcher)
10
+ @fetcher = fetcher
11
+ end
12
+
13
+ def total_count
14
+ @fetcher.total_count
15
+ end
16
+
17
+ def respond_to?(method_name)
18
+ (@object_proxies || @rows).respond_to?(method_name)
19
+ end
20
+
21
+ def method_missing(method_name, *arguments, &block)
22
+ load unless (@object_proxies || @rows)
23
+ (@object_proxies || @rows).send method_name, *arguments, &block
24
+ end
25
+
26
+ private
27
+
28
+ def load
29
+ if fetcher_has_drafts?
30
+ load_object_proxies_from_drafts
31
+ elsif fetcher_has_ids?
32
+ load_object_proxies_from_ids
33
+ elsif fetcher_has_rows?
34
+ load_rows
35
+ else
36
+ raise NotImplementedError, "the collection fetcher doesn't provide drafts, ids nor rows."
37
+ end
38
+ end
39
+
40
+ def fetcher_has_drafts?
41
+ @fetcher.respond_to?(:drafts) && @fetcher.drafts
42
+ end
43
+
44
+ def load_object_proxies_from_drafts
45
+ @object_proxies = @fetcher.drafts.map do |draft|
46
+ GOM::Object::Proxy.new GOM::Object::CachedBuilder.new(draft).object
47
+ end
48
+ end
49
+
50
+ def fetcher_has_ids?
51
+ @fetcher.respond_to?(:ids) && @fetcher.ids
52
+ end
53
+
54
+ def load_object_proxies_from_ids
55
+ @object_proxies = @fetcher.ids.map do |id|
56
+ GOM::Object::Proxy.new id
57
+ end
58
+ end
59
+
60
+ def fetcher_has_rows?
61
+ @fetcher.respond_to?(:rows) && @fetcher.rows
62
+ end
63
+
64
+ def load_rows
65
+ @rows = @fetcher.rows
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,34 @@
1
+
2
+ module GOM
3
+
4
+ module Object
5
+
6
+ # A draft for an object
7
+ class Draft
8
+
9
+ attr_accessor :id
10
+ attr_accessor :class_name
11
+ attr_writer :properties
12
+ attr_writer :relations
13
+
14
+ def initialize(id = nil, class_name = nil, properties = { }, relations = { })
15
+ @id, @class_name, @properties, @relations = id, class_name, properties, relations
16
+ end
17
+
18
+ def properties
19
+ @properties || { }
20
+ end
21
+
22
+ def relations
23
+ @relations || { }
24
+ end
25
+
26
+ def ==(other)
27
+ id == other.id && class_name == other.class_name && properties == other.properties && relations == other.relations
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -6,38 +6,44 @@ module GOM
6
6
  # Inspect an object and returns it's class and it's properties
7
7
  class Inspector
8
8
 
9
- attr_reader :object
10
- attr_reader :object_hash
9
+ attr_accessor :object
11
10
 
12
11
  def initialize(object)
13
12
  @object = object
14
- @object_hash = { }
15
13
  end
16
14
 
17
- def perform
18
- read_class
19
- read_properties
20
- read_relations
15
+ def draft
16
+ initialize_draft
17
+ set_class
18
+ set_properties
19
+ set_relations
20
+ @draft
21
21
  end
22
22
 
23
23
  private
24
24
 
25
- def read_class
26
- @object_hash[:class] = @object.class.to_s
25
+ def initialize_draft
26
+ @draft = Draft.new
27
27
  end
28
28
 
29
- def read_properties
30
- @object_hash[:properties] = { }
29
+ def set_class
30
+ @draft.class_name = @object.class.to_s
31
+ end
32
+
33
+ def set_properties
34
+ properties = { }
31
35
  read_instance_variables do |key, value|
32
- @object_hash[:properties][key] = value unless value.is_a?(GOM::Object::Proxy)
36
+ properties[key] = value if self.class.property_value?(value)
33
37
  end
38
+ @draft.properties = properties
34
39
  end
35
40
 
36
- def read_relations
37
- @object_hash[:relations] = { }
41
+ def set_relations
42
+ relations = { }
38
43
  read_instance_variables do |key, value|
39
- @object_hash[:relations][key] = value if value.is_a?(GOM::Object::Proxy)
44
+ relations[key] = value if self.class.relation_value?(value)
40
45
  end
46
+ @draft.relations = relations
41
47
  end
42
48
 
43
49
  def read_instance_variables
@@ -48,6 +54,14 @@ module GOM
48
54
  end
49
55
  end
50
56
 
57
+ def self.property_value?(value)
58
+ !relation_value?(value)
59
+ end
60
+
61
+ def self.relation_value?(value)
62
+ value.is_a?(GOM::Object::Proxy)
63
+ end
64
+
51
65
  end
52
66
 
53
67
  end
@@ -24,6 +24,7 @@ shared_examples_for "an adapter connected to a stateful storage" do
24
24
  end
25
25
 
26
26
  after :each do
27
+ GOM::Storage.remove @related_object
27
28
  GOM::Storage.remove @object
28
29
  end
29
30
 
@@ -40,16 +41,20 @@ shared_examples_for "an adapter connected to a stateful storage" do
40
41
  end
41
42
 
42
43
  it "should also fetch the related object" do
43
- related_object = @object.instance_variable_get :@related_object
44
- related_object.should be_instance_of(GOM::Object::Proxy)
45
- related_object.object.should == @related_object
46
- related_object.object.instance_variable_get(:@number).should == 16
44
+ related_object_proxy = @object.instance_variable_get :@related_object
45
+ related_object_proxy.should be_instance_of(GOM::Object::Proxy)
46
+ related_object_proxy.object.instance_variable_get(:@number).should == 16
47
47
  end
48
48
 
49
49
  end
50
50
 
51
51
  describe "storing an object" do
52
52
 
53
+ after :each do
54
+ GOM::Storage.remove @related_object
55
+ GOM::Storage.remove @object
56
+ end
57
+
53
58
  it "should store the object" do
54
59
  GOM::Storage.store @object, :test_storage
55
60
  object = GOM::Storage.fetch GOM::Object.id(@object)
@@ -69,7 +74,6 @@ shared_examples_for "an adapter connected to a stateful storage" do
69
74
  it "should store the related object" do
70
75
  GOM::Storage.store @object, :test_storage
71
76
  related_object = GOM::Storage.fetch GOM::Object.id(@related_object)
72
- related_object.should == @related_object
73
77
  related_object.instance_variable_get(:@number).should == 16
74
78
  end
75
79
 
@@ -108,4 +112,69 @@ shared_examples_for "an adapter connected to a stateful storage" do
108
112
 
109
113
  end
110
114
 
115
+ describe "fetching a class collection" do
116
+
117
+ before :each do
118
+ @another_object = mock Object, :class => mock(Class, :to_s => "Test")
119
+ @another_object.instance_variable_set :@number, 17
120
+
121
+ GOM::Storage.store @object, :test_storage
122
+ GOM::Storage.store @another_object, :test_storage
123
+ end
124
+
125
+ after :each do
126
+ GOM::Storage.remove @another_object
127
+ GOM::Storage.remove @related_object
128
+ GOM::Storage.remove @object
129
+ end
130
+
131
+ it "should provide a collection of the class view" do
132
+ collection = GOM::Storage.collection :test_storage, :test_object_class_view
133
+ collection.should be_instance_of(GOM::Object::Collection)
134
+ end
135
+
136
+ it "should provide a collection of all objects of the class from a class view" do
137
+ collection = GOM::Storage.collection :test_storage, :test_object_class_view
138
+ collection.size.should > 0
139
+ collection.each do |object_proxy|
140
+ object_proxy.should be_instance_of(GOM::Object::Proxy)
141
+ [
142
+ @object.instance_variable_get(:@number),
143
+ @related_object.instance_variable_get(:@number)
144
+ ].should include(object_proxy.object.instance_variable_get(:@number))
145
+ [
146
+ @another_object.instance_variable_get(:@number)
147
+ ].should_not include(object_proxy.object.instance_variable_get(:@number))
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+ describe "fetching a map collection" do
154
+
155
+ before :each do
156
+ GOM::Storage.store @object, :test_storage
157
+ end
158
+
159
+ after :each do
160
+ GOM::Storage.remove @related_object
161
+ GOM::Storage.remove @object
162
+ end
163
+
164
+ it "should provide a collection" do
165
+ collection = GOM::Storage.collection :test_storage, :test_map_view
166
+ collection.should be_instance_of(GOM::Object::Collection)
167
+ end
168
+
169
+ it "should provide a collection of the objects emitted by the map reduce view" do
170
+ collection = GOM::Storage.collection :test_storage, :test_map_view
171
+ collection.size.should > 0
172
+ collection.each do |object_proxy|
173
+ object_proxy.should be_instance_of(GOM::Object::Proxy)
174
+ object_proxy.object.instance_variable_get(:@number).should == @object.instance_variable_get(:@number)
175
+ end
176
+ end
177
+
178
+ end
179
+
111
180
  end