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