gom 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +70 -0
- data/lib/gom/object.rb +4 -1
- data/lib/gom/object/builder.rb +47 -0
- data/lib/gom/object/cached_builder.rb +42 -0
- data/lib/gom/object/collection.rb +72 -0
- data/lib/gom/object/draft.rb +34 -0
- data/lib/gom/object/inspector.rb +29 -15
- data/lib/gom/spec/acceptance/adapter_with_stateful_storage.rb +74 -5
- data/lib/gom/spec/acceptance/read_only_adapter_with_stateless_storage.rb +20 -0
- data/lib/gom/storage.rb +6 -2
- data/lib/gom/storage/adapter.rb +11 -4
- data/lib/gom/storage/configuration.rb +30 -1
- data/lib/gom/storage/configuration/view.rb +20 -0
- data/lib/gom/storage/configuration/view/class.rb +28 -0
- data/lib/gom/storage/configuration/view/map_reduce.rb +29 -0
- data/lib/gom/storage/fetcher.rb +9 -34
- data/lib/gom/storage/saver.rb +5 -7
- data/spec/acceptance/object_spec.rb +1 -0
- data/spec/fake_adapter.rb +55 -10
- data/spec/lib/gom/object/builder_spec.rb +51 -0
- data/spec/lib/gom/object/cached_builder_spec.rb +43 -0
- data/spec/lib/gom/object/collection_spec.rb +158 -0
- data/spec/lib/gom/object/draft_spec.rb +59 -0
- data/spec/lib/gom/object/inspector_spec.rb +8 -10
- data/spec/lib/gom/storage/adapter_spec.rb +7 -33
- data/spec/lib/gom/storage/configuration/view/class_spec.rb +17 -0
- data/spec/lib/gom/storage/configuration/view/map_reduce_spec.rb +21 -0
- data/spec/lib/gom/storage/configuration_spec.rb +29 -0
- data/spec/lib/gom/storage/fetcher_spec.rb +7 -38
- data/spec/lib/gom/storage/remover_spec.rb +7 -9
- data/spec/lib/gom/storage/saver_spec.rb +17 -33
- data/spec/lib/gom/storage_spec.rb +33 -9
- data/spec/storage.configuration +10 -0
- metadata +22 -6
- data/lib/gom/object/injector.rb +0 -45
- data/spec/lib/gom/object/injector_spec.rb +0 -51
data/README.rdoc
CHANGED
@@ -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
|
data/lib/gom/object.rb
CHANGED
@@ -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
|
data/lib/gom/object/inspector.rb
CHANGED
@@ -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
|
-
|
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
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
26
|
-
@
|
25
|
+
def initialize_draft
|
26
|
+
@draft = Draft.new
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
@
|
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
|
-
|
36
|
+
properties[key] = value if self.class.property_value?(value)
|
33
37
|
end
|
38
|
+
@draft.properties = properties
|
34
39
|
end
|
35
40
|
|
36
|
-
def
|
37
|
-
|
41
|
+
def set_relations
|
42
|
+
relations = { }
|
38
43
|
read_instance_variables do |key, value|
|
39
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|