medea 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,236 +1,238 @@
1
- #Medea/JasonObject - Written by Michael Jensen
2
-
3
- module Medea
4
- require 'rest_client'
5
- require 'json'
6
-
7
- class JasonObject
8
-
9
- #include JasonDB
10
-
11
- #meta-programming interface for lists
12
- include ClassLevelInheritableAttributes
13
- inheritable_attributes :owned
14
- @owned = false
15
-
16
- def self.create_member_list list_name, list_class, list_type
17
- list = {}
18
- list = self.class_variable_get :@@lists if self.class_variable_defined? :@@lists
19
- list[list_name] = [list_class, list_type]
20
- self.class_variable_set :@@lists, list
21
-
22
- define_method(list_name) do
23
- #puts "Looking at the #{list_name.to_s} list, which is full of #{list_type.name}s"
24
- JasonListProperty.new self, list_name, list_class, list_type
25
- end
26
- end
27
-
28
- def self.has_many list_name, list_class
29
- create_member_list list_name, list_class, :reference
30
- end
31
-
32
- def self.owns_many list_name, list_class
33
- create_member_list list_name, list_class, :value
34
-
35
- #also modify the items in the list so that they know that they're owned
36
- #list_type.class_variable_set :@@owner, self
37
- list_class.owned = true
38
- end
39
-
40
- #end meta
41
-
42
- #Here we're going to put the "query" interface
43
-
44
- #create a JasonDeferredQuery with no conditions, other than HTTP_X_CLASS=self.name
45
- #if mode is set to :eager, we create the JasonDeferredQuery, invoke it's execution and then return it
46
- def JasonObject.all(mode=:lazy)
47
- JasonDeferredQuery.new self
48
- end
49
-
50
- #returns the JasonObject by directly querying the URL
51
- #if mode is :lazy, we return a GHOST, if mode is :eager, we return a STALE JasonObject
52
- def JasonObject.get_by_key(key, mode=:eager)
53
- return self.new key, mode
54
- end
55
-
56
- #here we will capture:
57
- #members_of(object) (where object is an instance of a class that this class can be a member of)
58
- #find_by_<property>(value)
59
- #Will return a JasonDeferredQuery for this class with the appropriate data filter set
60
- def JasonObject.method_missing(name, *args, &block)
61
- q = JasonDeferredQuery.new self
62
- if name =~ /^members_of$/
63
- #use the type and key of the first arg (being a JasonObject)
64
- return q.members_of args[0]
65
- elsif name =~ /^find_by_(.*)$/
66
- #use the property name from the name variable, and the value from the first arg
67
- q.add_data_filter $1, args[0]
68
-
69
- return q
70
- else
71
- #no method!
72
- super
73
- end
74
- end
75
- #end query interface
76
-
77
- #"flexihash" access interface
78
- def []=(key, value)
79
- @__jason_data ||= {}
80
- @__jason_data[key] = value
81
- end
82
-
83
- def [](key)
84
- @__jason_data[key]
85
- end
86
-
87
- #The "Magic" component of candy (https://github.com/SFEley/candy), repurposed to make this a
88
- # "weak object" that can take any attribute.
89
- # Assigning any attribute will add it to the object's hash (and then be POSTed to JasonDB on the next save)
90
- def method_missing(name, *args, &block)
91
- load if @__jason_state == :ghost
92
- if name =~ /(.*)=$/ # We're assigning
93
- @__jason_state = :dirty if @__jason_state == :stale
94
- self[$1] = args[0]
95
- elsif name =~ /(.*)\?$/ # We're asking
96
- (self[$1] ? true : false)
97
- else
98
- self[name.to_s]
99
- end
100
- end
101
- #end "flexihash" access
102
-
103
- def initialize key = nil, mode = :eager
104
- if key
105
- @__id = key
106
- if mode == :eager
107
- load
108
- else
109
- @__jason_state = :ghost
110
- end
111
- else
112
- @__jason_state = :new
113
- @__jason_data = {}
114
- end
115
- end
116
-
117
- def jason_key
118
- #TODO: Replace this string with a guid generator of some kind
119
- @__id ||= "p#{Time.now.nsec.to_s}"
120
- end
121
-
122
- def jason_state
123
- @__jason_state
124
- end
125
-
126
- def jason_etag
127
- @__jason_etag ||= ""
128
- end
129
-
130
- def jason_parent
131
- @__jason_parent ||= nil
132
- end
133
-
134
- def jason_parent_list
135
- @__jason_parent_list ||= nil
136
- end
137
-
138
- def jason_parent_list= value
139
- @__jason_parent_list = value
140
- end
141
-
142
- def jason_parent= parent
143
- @__jason_parent = parent
144
- end
145
-
146
- #object persistence methods
147
-
148
- #POSTs the current values of this object back to JasonDB
149
- #on successful post, sets state to STALE and updates eTag
150
- def save!
151
- #no changes? no save!
152
- return if @__jason_state == :stale or @__jason_state == :ghost
153
-
154
-
155
- payload = self.to_json
156
- post_headers = {
157
- :content_type => 'application/json',
158
-
159
- "X-KEY" => self.jason_key,
160
- "X-CLASS" => self.class.name
161
- #also want to add the eTag here!
162
- #may also want to add any other indexable fields that the user specifies?
163
- }
164
- post_headers["IF-MATCH"] = @__jason_etag if @__jason_state == :dirty
165
-
166
- if self.class.owned
167
- #the parent object needs to be defined!
168
- raise "#{self.class.name} cannot be saved without setting a parent and list!" unless self.jason_parent && self.jason_parent_list
169
- post_headers["X-PARENT"] = self.jason_parent.jason_key
170
- url = "#{JasonDB::db_auth_url}#{self.jason_parent.class.name}/#{self.jason_parent.jason_key}/#{self.jason_parent_list}/#{self.jason_key}"
171
- post_headers["X-LIST"] = self.jason_parent_list
172
- else
173
- url = JasonDB::db_auth_url + self.class.name + "/" + self.jason_key
174
- end
175
-
176
-
177
- #puts "Posted to JasonDB!"
178
-
179
- #puts "Saving to #{url}"
180
- response = RestClient.post url, payload, post_headers
181
-
182
- if response.code == 201
183
- #save successful!
184
- #store the new eTag for this object
185
- #puts response.raw_headers
186
- #@__jason_etag = response.headers[:location] + ":" + response.headers[:content_md5]
187
- else
188
- raise "POST failed! Could not save object"
189
- end
190
-
191
- @__jason_state = :stale
192
- end
193
-
194
- def delete!
195
- url = "#{JasonDB::db_auth_url}#{self.class.name}/#{self.jason_key}"
196
- response = RestClient.delete url
197
- raise "DELETE failed!" unless response.code == 201
198
- end
199
-
200
- #end object persistence
201
-
202
- #converts the data hash (that is, @__jason_data) to JSON format
203
- def to_json
204
- JSON.generate(@__jason_data)
205
- end
206
-
207
- private
208
-
209
- #fetches the data from the JasonDB
210
- def load
211
- #because this object might be owned by another, we need to search by key.
212
- #not passing a format to the query is a shortcut to getting just the object.
213
- url = "#{JasonDB::db_auth_url}@0.content?"
214
- params = [
215
- "VERSION0",
216
- "FILTER=HTTP_X_CLASS:#{self.class.name}",
217
- "FILTER=HTTP_X_KEY:#{self.jason_key}"
218
- ]
219
-
220
- url << params.join("&")
221
-
222
- #puts " = Retrieving #{self.class.name} at #{url}"
223
- response = RestClient.get url
224
- @__jason_data = JSON.parse response
225
- @__jason_etag = response.headers[:etag]
226
- @__jason_state = :stale
227
- end
228
-
229
- def lazy_load meta
230
- #TODO Implement lazy load
231
-
232
- @__jason_state = :ghost
233
- end
234
-
235
- end
236
- end
1
+ #Medea/JasonObject - Written by Michael Jensen
2
+
3
+ module Medea
4
+ require 'rest_client'
5
+ require 'json'
6
+ require 'uuidtools'
7
+
8
+ class JasonObject
9
+
10
+ #include JasonDB
11
+
12
+ #meta-programming interface for lists
13
+ include ClassLevelInheritableAttributes
14
+ inheritable_attributes :owned
15
+ @owned = false
16
+
17
+ def self.create_member_list list_name, list_class, list_type
18
+ list = {}
19
+ list = self.class_variable_get :@@lists if self.class_variable_defined? :@@lists
20
+ list[list_name] = [list_class, list_type]
21
+ self.class_variable_set :@@lists, list
22
+
23
+ define_method(list_name) do
24
+ #puts "Looking at the #{list_name.to_s} list, which is full of #{list_type.name}s"
25
+ JasonListProperty.new self, list_name, list_class, list_type
26
+ end
27
+ end
28
+
29
+ def self.has_many list_name, list_class
30
+ create_member_list list_name, list_class, :reference
31
+ end
32
+
33
+ def self.owns_many list_name, list_class
34
+ create_member_list list_name, list_class, :value
35
+
36
+ #also modify the items in the list so that they know that they're owned
37
+ #list_type.class_variable_set :@@owner, self
38
+ list_class.owned = true
39
+ end
40
+
41
+ #end meta
42
+
43
+ #Here we're going to put the "query" interface
44
+
45
+ #create a JasonDeferredQuery with no conditions, other than HTTP_X_CLASS=self.name
46
+ #if mode is set to :eager, we create the JasonDeferredQuery, invoke it's execution and then return it
47
+ def JasonObject.all(mode=:lazy)
48
+ JasonDeferredQuery.new self
49
+ end
50
+
51
+ #returns the JasonObject by directly querying the URL
52
+ #if mode is :lazy, we return a GHOST, if mode is :eager, we return a STALE JasonObject
53
+ def JasonObject.get_by_key(key, mode=:eager)
54
+ return self.new key, mode
55
+ end
56
+
57
+ #here we will capture:
58
+ #members_of(object) (where object is an instance of a class that this class can be a member of)
59
+ #find_by_<property>(value)
60
+ #Will return a JasonDeferredQuery for this class with the appropriate data filter set
61
+ def JasonObject.method_missing(name, *args, &block)
62
+ q = JasonDeferredQuery.new self
63
+ if name =~ /^members_of$/
64
+ #use the type and key of the first arg (being a JasonObject)
65
+ return q.members_of args[0]
66
+ elsif name =~ /^find_by_(.*)$/
67
+ #use the property name from the name variable, and the value from the first arg
68
+ q.add_data_filter $1, args[0]
69
+
70
+ return q
71
+ else
72
+ #no method!
73
+ super
74
+ end
75
+ end
76
+ #end query interface
77
+
78
+ #"flexihash" access interface
79
+ def []=(key, value)
80
+ @__jason_data ||= {}
81
+ @__jason_data[key] = value
82
+ end
83
+
84
+ def [](key)
85
+ @__jason_data[key]
86
+ end
87
+
88
+ #The "Magic" component of candy (https://github.com/SFEley/candy), repurposed to make this a
89
+ # "weak object" that can take any attribute.
90
+ # Assigning any attribute will add it to the object's hash (and then be POSTed to JasonDB on the next save)
91
+ def method_missing(name, *args, &block)
92
+ load if @__jason_state == :ghost
93
+ if name =~ /(.*)=$/ # We're assigning
94
+ @__jason_state = :dirty if @__jason_state == :stale
95
+ self[$1] = args[0]
96
+ elsif name =~ /(.*)\?$/ # We're asking
97
+ (self[$1] ? true : false)
98
+ else
99
+ self[name.to_s]
100
+ end
101
+ end
102
+ #end "flexihash" access
103
+
104
+ def initialize key = nil, mode = :eager
105
+ if key
106
+ @__id = key
107
+ if mode == :eager
108
+ load
109
+ else
110
+ @__jason_state = :ghost
111
+ end
112
+ else
113
+ @__jason_state = :new
114
+ @__jason_data = {}
115
+ end
116
+ end
117
+
118
+ def jason_key
119
+ #Generate a random UUID for this object.
120
+ #since jason urls must start with a letter, we'll use the first letter of the class name
121
+ @__id ||= "#{self.class.name[0].downcase}#{UUIDTools::UUID::random_create.to_s}"
122
+ end
123
+
124
+ def jason_state
125
+ @__jason_state
126
+ end
127
+
128
+ def jason_etag
129
+ @__jason_etag ||= ""
130
+ end
131
+
132
+ def jason_parent
133
+ @__jason_parent ||= nil
134
+ end
135
+
136
+ def jason_parent_list
137
+ @__jason_parent_list ||= nil
138
+ end
139
+
140
+ def jason_parent_list= value
141
+ @__jason_parent_list = value
142
+ end
143
+
144
+ def jason_parent= parent
145
+ @__jason_parent = parent
146
+ end
147
+
148
+ #object persistence methods
149
+
150
+ #POSTs the current values of this object back to JasonDB
151
+ #on successful post, sets state to STALE and updates eTag
152
+ def save!
153
+ #no changes? no save!
154
+ return if @__jason_state == :stale or @__jason_state == :ghost
155
+
156
+
157
+ payload = self.to_json
158
+ post_headers = {
159
+ :content_type => 'application/json',
160
+
161
+ "X-KEY" => self.jason_key,
162
+ "X-CLASS" => self.class.name
163
+ #also want to add the eTag here!
164
+ #may also want to add any other indexable fields that the user specifies?
165
+ }
166
+ post_headers["IF-MATCH"] = @__jason_etag if @__jason_state == :dirty
167
+
168
+ if self.class.owned
169
+ #the parent object needs to be defined!
170
+ raise "#{self.class.name} cannot be saved without setting a parent and list!" unless self.jason_parent && self.jason_parent_list
171
+ post_headers["X-PARENT"] = self.jason_parent.jason_key
172
+ url = "#{JasonDB::db_auth_url}#{self.jason_parent.class.name}/#{self.jason_parent.jason_key}/#{self.jason_parent_list}/#{self.jason_key}"
173
+ post_headers["X-LIST"] = self.jason_parent_list
174
+ else
175
+ url = JasonDB::db_auth_url + self.class.name + "/" + self.jason_key
176
+ end
177
+
178
+
179
+ #puts "Posted to JasonDB!"
180
+
181
+ #puts "Saving to #{url}"
182
+ response = RestClient.post url, payload, post_headers
183
+
184
+ if response.code == 201
185
+ #save successful!
186
+ #store the new eTag for this object
187
+ #puts response.raw_headers
188
+ #@__jason_etag = response.headers[:location] + ":" + response.headers[:content_md5]
189
+ else
190
+ raise "POST failed! Could not save object"
191
+ end
192
+
193
+ @__jason_state = :stale
194
+ end
195
+
196
+ def delete!
197
+ url = "#{JasonDB::db_auth_url}#{self.class.name}/#{self.jason_key}"
198
+ response = RestClient.delete url
199
+ raise "DELETE failed!" unless response.code == 201
200
+ end
201
+
202
+ #end object persistence
203
+
204
+ #converts the data hash (that is, @__jason_data) to JSON format
205
+ def to_json
206
+ JSON.generate(@__jason_data)
207
+ end
208
+
209
+ private
210
+
211
+ #fetches the data from the JasonDB
212
+ def load
213
+ #because this object might be owned by another, we need to search by key.
214
+ #not passing a format to the query is a shortcut to getting just the object.
215
+ url = "#{JasonDB::db_auth_url}@0.content?"
216
+ params = [
217
+ "VERSION0",
218
+ "FILTER=HTTP_X_CLASS:#{self.class.name}",
219
+ "FILTER=HTTP_X_KEY:#{self.jason_key}"
220
+ ]
221
+
222
+ url << params.join("&")
223
+
224
+ #puts " = Retrieving #{self.class.name} at #{url}"
225
+ response = RestClient.get url
226
+ @__jason_data = JSON.parse response
227
+ @__jason_etag = response.headers[:etag]
228
+ @__jason_state = :stale
229
+ end
230
+
231
+ def lazy_load meta
232
+ #TODO Implement lazy load
233
+
234
+ @__jason_state = :ghost
235
+ end
236
+
237
+ end
238
+ end