medea 0.2.3 → 0.2.4

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