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.
- data/Rakefile +21 -20
- data/VERSION +1 -1
- data/lib/medea.rb +7 -7
- data/lib/medea/inheritable_attributes.rb +30 -30
- data/lib/medea/jasondb.rb +20 -20
- data/lib/medea/jasondeferredquery.rb +124 -124
- data/lib/medea/jasonlistproperty.rb +101 -101
- data/lib/medea/jasonobject.rb +238 -236
- metadata +23 -9
- data/assets.file +0 -0
- data/assets.http +0 -0
- data/index.html +0 -78
- data/medea-0.2.2.gem +0 -0
- data/medea.gemspec +0 -55
data/lib/medea/jasonobject.rb
CHANGED
@@ -1,236 +1,238 @@
|
|
1
|
-
#Medea/JasonObject - Written by Michael Jensen
|
2
|
-
|
3
|
-
module Medea
|
4
|
-
require 'rest_client'
|
5
|
-
require 'json'
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
list =
|
19
|
-
list
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@__jason_data
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# "
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
if
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
@
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
#
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
if
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
post_headers["X-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
#puts "
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
#
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
#
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
"
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
@
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|