medea 0.2.2
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 +0 -0
- data/Rakefile +20 -0
- data/VERSION +1 -0
- data/assets.file +0 -0
- data/assets.http +0 -0
- data/index.html +78 -0
- data/lib/medea.rb +8 -0
- data/lib/medea/inheritable_attributes.rb +28 -0
- data/lib/medea/jasondb.rb +17 -0
- data/lib/medea/jasondeferredquery.rb +125 -0
- data/lib/medea/jasonlistproperty.rb +102 -0
- data/lib/medea/jasonobject.rb +236 -0
- data/medea-0.2.1.gem +0 -0
- data/medea.gemspec +55 -0
- metadata +102 -0
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = "medea"
|
7
|
+
s.summary = "Simple wrapper for persisting objects to JasonDB"
|
8
|
+
s.email = "michaelj@jasondb.com"
|
9
|
+
s.homepage = "https://github.com/rob-linton/Medea"
|
10
|
+
s.description = "Simple wrapper for persisting objects to JasonDB"
|
11
|
+
s.authors = ["Michael Jensen"]
|
12
|
+
s.files = FileList["[A-Z]*", "{lib}/medea.rb", "{lib}/medea/*"]
|
13
|
+
s.files.exclude '{lib}/test*'
|
14
|
+
s.add_dependency 'json'
|
15
|
+
s.add_dependency 'rest-client'
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
19
|
+
end
|
20
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.2
|
data/assets.file
ADDED
File without changes
|
data/assets.http
ADDED
File without changes
|
data/index.html
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
7
|
+
|
8
|
+
<title>rob-linton/Medea @ GitHub</title>
|
9
|
+
|
10
|
+
<style type="text/css">
|
11
|
+
body {
|
12
|
+
margin-top: 1.0em;
|
13
|
+
background-color: #28904b;
|
14
|
+
font-family: "Helvetica,Arial,FreeSans";
|
15
|
+
color: #ffffff;
|
16
|
+
}
|
17
|
+
#container {
|
18
|
+
margin: 0 auto;
|
19
|
+
width: 700px;
|
20
|
+
}
|
21
|
+
h1 { font-size: 3.8em; color: #d76fb4; margin-bottom: 3px; }
|
22
|
+
h1 .small { font-size: 0.4em; }
|
23
|
+
h1 a { text-decoration: none }
|
24
|
+
h2 { font-size: 1.5em; color: #d76fb4; }
|
25
|
+
h3 { text-align: center; color: #d76fb4; }
|
26
|
+
a { color: #d76fb4; }
|
27
|
+
.description { font-size: 1.2em; margin-bottom: 30px; margin-top: 30px; font-style: italic;}
|
28
|
+
.download { float: right; }
|
29
|
+
pre { background: #000; color: #fff; padding: 15px;}
|
30
|
+
hr { border: 0; width: 80%; border-bottom: 1px solid #aaa}
|
31
|
+
.footer { text-align:center; padding-top:30px; font-style: italic; }
|
32
|
+
</style>
|
33
|
+
|
34
|
+
</head>
|
35
|
+
|
36
|
+
<body>
|
37
|
+
<a href="http://github.com/rob-linton/Medea"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
|
38
|
+
|
39
|
+
<div id="container">
|
40
|
+
|
41
|
+
<div class="download">
|
42
|
+
<a href="http://github.com/rob-linton/Medea/zipball/master">
|
43
|
+
<img border="0" width="90" src="http://github.com/images/modules/download/zip.png"></a>
|
44
|
+
<a href="http://github.com/rob-linton/Medea/tarball/master">
|
45
|
+
<img border="0" width="90" src="http://github.com/images/modules/download/tar.png"></a>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<h1><a href="http://github.com/rob-linton/Medea">Medea</a>
|
49
|
+
<span class="small">by <a href="http://github.com/rob-linton">rob-linton</a></span></h1>
|
50
|
+
|
51
|
+
<div class="description">
|
52
|
+
Jasondb library for Ruby
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<p>This is the main site for the Ruby interface to Jasondb. </p><h2>Contact</h2>
|
56
|
+
<p>robl@jasondb.com
|
57
|
+
|
58
|
+
|
59
|
+
<h2>Download</h2>
|
60
|
+
<p>
|
61
|
+
You can download this project in either
|
62
|
+
<a href="http://github.com/rob-linton/Medea/zipball/master">zip</a> or
|
63
|
+
<a href="http://github.com/rob-linton/Medea/tarball/master">tar</a> formats.
|
64
|
+
</p>
|
65
|
+
<p>You can also clone the project with <a href="http://git-scm.com">Git</a>
|
66
|
+
by running:
|
67
|
+
<pre>$ git clone git://github.com/rob-linton/Medea</pre>
|
68
|
+
</p>
|
69
|
+
|
70
|
+
<div class="footer">
|
71
|
+
get the source code on GitHub : <a href="http://github.com/rob-linton/Medea">rob-linton/Medea</a>
|
72
|
+
</div>
|
73
|
+
|
74
|
+
</div>
|
75
|
+
|
76
|
+
|
77
|
+
</body>
|
78
|
+
</html>
|
data/lib/medea.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#This module allows for an attribute to be defined on a superclass and carry down into sub-classes with its default.
|
2
|
+
#Taken from http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
|
3
|
+
|
4
|
+
module ClassLevelInheritableAttributes
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def inheritable_attributes(*args)
|
11
|
+
@inheritable_attributes ||= [:inheritable_attributes]
|
12
|
+
@inheritable_attributes += args
|
13
|
+
args.each do |arg|
|
14
|
+
class_eval %(
|
15
|
+
class << self; attr_accessor :#{arg} end
|
16
|
+
)
|
17
|
+
end
|
18
|
+
@inheritable_attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
def inherited(subclass)
|
22
|
+
@inheritable_attributes.each do |inheritable_attribute|
|
23
|
+
instance_var = "@#{inheritable_attribute}"
|
24
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JasonDB
|
2
|
+
#jason_url here doesn't include the http[s]:// part, but does include the domain and a trailing '/'
|
3
|
+
#( so it's "rest.jasondb.com/<domain>/" )
|
4
|
+
attr_accessor :jason_url, :user, :password
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
def JasonDB::db_auth_url mode=:secure
|
9
|
+
user = "michael"
|
10
|
+
jason_url = "rest.jasondb.com/medea-test/"
|
11
|
+
password = "password"
|
12
|
+
protocol = "http"
|
13
|
+
protocol << "s" if mode == :secure
|
14
|
+
"#{protocol}://#{user}:#{password}@#{jason_url}"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Medea
|
2
|
+
class JasonDeferredQuery
|
3
|
+
require 'rest_client'
|
4
|
+
|
5
|
+
attr_accessor :time_limit, :result_format, :type, :result_format, :time_limit, :state, :contents, :filters
|
6
|
+
|
7
|
+
def initialize a_class, format=:json
|
8
|
+
@type = a_class
|
9
|
+
@filters = {:FILTER => {:HTTP_X_CLASS => a_class.name.to_s}}
|
10
|
+
@result_format = format
|
11
|
+
@time_limit = 0
|
12
|
+
@state = :prefetch
|
13
|
+
@contents = []
|
14
|
+
end
|
15
|
+
|
16
|
+
#Here we're going to put the "query" interface
|
17
|
+
|
18
|
+
#here we will capture:
|
19
|
+
#members_of(object) (where object is an instance of a class that this class can be a member of)
|
20
|
+
#members_of_<classname>(key)
|
21
|
+
#find_by_<property>(value)
|
22
|
+
#Will return a JasonDeferredQuery for this class with the appropriate data filter set
|
23
|
+
def method_missing(name, *args, &block)
|
24
|
+
#if we are postfetch, we throw away all our cached results
|
25
|
+
if self.state == :postfetch
|
26
|
+
self.state = :prefetch
|
27
|
+
self.contents = []
|
28
|
+
end
|
29
|
+
|
30
|
+
if name =~ /^members_of$/
|
31
|
+
#use the type and key of the first arg (being a JasonObject)
|
32
|
+
#args[0] must be a JasonObject (or child)
|
33
|
+
raise ArgumentError, "When looking for members, you must pass a JasonObject" unless args[0].is_a? JasonObject
|
34
|
+
|
35
|
+
self.filters[:DATA_FILTER] ||= {}
|
36
|
+
self.filters[:DATA_FILTER]["__member_of"] ||= []
|
37
|
+
self.filters[:DATA_FILTER]["__member_of"] << args[0].jason_key
|
38
|
+
elsif name =~ /^find_by_(.*)$/
|
39
|
+
#use the property name from the name variable, and the value from the first arg
|
40
|
+
add_data_filter $1, args[0].to_s
|
41
|
+
else
|
42
|
+
#no method!
|
43
|
+
super
|
44
|
+
return
|
45
|
+
end
|
46
|
+
#return self, so that we can chain up query refinements
|
47
|
+
self
|
48
|
+
end
|
49
|
+
#end query interface
|
50
|
+
|
51
|
+
def add_data_filter property, value
|
52
|
+
self.filters[:DATA_FILTER] ||= {}
|
53
|
+
self.filters[:DATA_FILTER][property] = value
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_url
|
57
|
+
url = "#{JasonDB::db_auth_url}@#{self.time_limit}.#{self.result_format}?"
|
58
|
+
filter_array = []
|
59
|
+
self.filters.each do |name, val|
|
60
|
+
if not val
|
61
|
+
filter_array << name.to_s
|
62
|
+
next
|
63
|
+
else
|
64
|
+
#FILTER's value is a hash (to avoid dupes)
|
65
|
+
#DATA_FILTER's value is a hash
|
66
|
+
if val.is_a? Hash
|
67
|
+
#for each k/v in the hash, we want to add an entry to filter_array
|
68
|
+
val.each do |field ,value|
|
69
|
+
if value.is_a? Array
|
70
|
+
value.each do |i|
|
71
|
+
filter_array << "#{name.to_s}=#{field}:#{i}"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
filter_array << "#{name.to_s}=#{field.to_s}:#{value.to_s}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
url + filter_array.join("&")
|
82
|
+
end
|
83
|
+
|
84
|
+
#array access interface
|
85
|
+
def [](index)
|
86
|
+
execute_query unless self.state == :postfetch
|
87
|
+
self.contents[index]
|
88
|
+
end
|
89
|
+
|
90
|
+
def each(&block)
|
91
|
+
execute_query unless self.state == :postfetch
|
92
|
+
self.contents.each &block
|
93
|
+
end
|
94
|
+
|
95
|
+
def count
|
96
|
+
execute_query unless self.state == :postfetch
|
97
|
+
self.contents.count
|
98
|
+
end
|
99
|
+
#end array interface
|
100
|
+
|
101
|
+
def execute_query
|
102
|
+
#hit the URL
|
103
|
+
#fill self.contents with :ghost versions of JasonObjects
|
104
|
+
begin
|
105
|
+
#puts " = Executing #{type.name} deferred query! (#{to_url})"
|
106
|
+
result = JSON.parse(RestClient.get to_url)
|
107
|
+
|
108
|
+
#results are in a hash, their keys are just numbers
|
109
|
+
result.keys.each do |k|
|
110
|
+
if k =~ /^[0-9]+$/
|
111
|
+
#this is a result! get the key
|
112
|
+
/\/([^\/]*)\/([^\/]*)$/.match result[k]["POST_TO"]
|
113
|
+
#$1 is the class name, $2 is the key
|
114
|
+
item = type.new($2, :lazy)
|
115
|
+
self.contents << item
|
116
|
+
end
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
self.contents = []
|
120
|
+
ensure
|
121
|
+
self.state = :postfetch
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Medea
|
2
|
+
require 'uri'
|
3
|
+
require 'rest_client'
|
4
|
+
class JasonListProperty < JasonDeferredQuery
|
5
|
+
|
6
|
+
attr_accessor :list_name, :parent, :list_type
|
7
|
+
|
8
|
+
def initialize parent, list_name, list_class, list_type
|
9
|
+
@type = list_class
|
10
|
+
@list_name = list_name
|
11
|
+
@list_type = list_type
|
12
|
+
@parent = parent
|
13
|
+
@result_format = :json
|
14
|
+
@time_limit = 0
|
15
|
+
@state = :prefetch
|
16
|
+
@contents = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing name, *args, &block
|
20
|
+
#is this a list property on the base class?
|
21
|
+
if (@type.class_variable_defined? :@@lists) && (@type.class_variable_get(:@@lists).has_key? name)
|
22
|
+
#if so, we'll just return a new ListProperty with my query as the parent
|
23
|
+
new_list_class, new_list_type = @type.class_variable_get(:@@lists)[name]
|
24
|
+
base_query = self.clone
|
25
|
+
base_query.result_format = :keylist
|
26
|
+
JasonListProperty.new base_query, name.to_sym, new_list_class, new_list_type
|
27
|
+
else
|
28
|
+
#no method, let JasonDeferredQuery handle it
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add! member, save=true
|
34
|
+
raise RuntimeError, "You can only add an item if you are accessing this list from an object." unless @parent.is_a? JasonObject
|
35
|
+
raise ArgumentError, "You can only add #{@type.name} items to this collection!" unless member.is_a? @type
|
36
|
+
|
37
|
+
if @list_type == :value
|
38
|
+
member.jason_parent = @parent
|
39
|
+
member.jason_parent_list = @list_name
|
40
|
+
elsif @list_type == :reference
|
41
|
+
|
42
|
+
#post to JasonDB::db_auth_url/a_class.name/
|
43
|
+
url = "#{JasonDB::db_auth_url}#{@type.name}/#{@parent.jason_key}/#{@list_name}/#{member.jason_key}"
|
44
|
+
post_headers = {
|
45
|
+
:content_type => 'application/json',
|
46
|
+
"X-CLASS" => @type.name,
|
47
|
+
"X-KEY" => member.jason_key,
|
48
|
+
"X-PARENT" => @parent.jason_key,
|
49
|
+
"X-LIST" => @list_name.to_s
|
50
|
+
}
|
51
|
+
content = {
|
52
|
+
"_id" => member.jason_key,
|
53
|
+
"_parent" => @parent.jason_key
|
54
|
+
}
|
55
|
+
#puts " = " + url
|
56
|
+
#puts " = #{post_headers}"
|
57
|
+
response = RestClient.post url, content.to_json, post_headers
|
58
|
+
|
59
|
+
if response.code == 201
|
60
|
+
#save successful!
|
61
|
+
#store the new eTag for this object
|
62
|
+
#puts response.raw_headers
|
63
|
+
#@__jason_etag = response.headers[:location] + ":" + response.headers[:content_md5]
|
64
|
+
else
|
65
|
+
raise "POST failed! Could not save membership"
|
66
|
+
end
|
67
|
+
else
|
68
|
+
#parent is a JasonObject, but this list is something other than :value or :reference??
|
69
|
+
raise "Invalid list type or trying to add an item to a subquery list!"
|
70
|
+
end
|
71
|
+
|
72
|
+
if member.jason_state == :new
|
73
|
+
#we want to save it? probably...
|
74
|
+
member.save! if save
|
75
|
+
end
|
76
|
+
|
77
|
+
@state = :prefetch
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_url
|
81
|
+
url = "#{JasonDB::db_auth_url}@#{@time_limit}.#{@result_format}?"
|
82
|
+
params = ["VERSION0"]
|
83
|
+
|
84
|
+
params << "FILTER=HTTP_X_CLASS:#{@type.name}"
|
85
|
+
|
86
|
+
if @parent.is_a? JasonObject
|
87
|
+
params << "FILTER=HTTP_X_PARENT:#{@parent.jason_key}"
|
88
|
+
else # @parent.is_a? JasonListProperty ##(or DeferredQuery?)
|
89
|
+
#we can get the insecure url here, because it will be resolved and executed at JasonDB - on a secure subnet.
|
90
|
+
|
91
|
+
#subquery = "<%@LANGUAGE=\"URL\" #{@parent.to_url}%>"
|
92
|
+
#puts " = Fetching subquery stupidly. (#{@parent.to_url})"
|
93
|
+
|
94
|
+
subquery = (RestClient.get @parent.to_url).strip
|
95
|
+
#puts " = Result: #{subquery}"
|
96
|
+
params << URI.escape("FILTER={HTTP_X_PARENT:#{subquery}}", Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
97
|
+
end
|
98
|
+
|
99
|
+
url << params.join("&")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,236 @@
|
|
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
|
data/medea-0.2.1.gem
ADDED
Binary file
|
data/medea.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{medea}
|
8
|
+
s.version = "0.2.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Michael Jensen"]
|
12
|
+
s.date = %q{2010-12-21}
|
13
|
+
s.description = %q{Simple wrapper for persisting objects to JasonDB}
|
14
|
+
s.email = %q{michaelj@jasondb.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"README",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"assets.file",
|
23
|
+
"assets.http",
|
24
|
+
"index.html",
|
25
|
+
"lib/medea.rb",
|
26
|
+
"lib/medea/inheritable_attributes.rb",
|
27
|
+
"lib/medea/jasondb.rb",
|
28
|
+
"lib/medea/jasondeferredquery.rb",
|
29
|
+
"lib/medea/jasonlistproperty.rb",
|
30
|
+
"lib/medea/jasonobject.rb",
|
31
|
+
"medea-0.2.1.gem",
|
32
|
+
"medea.gemspec"
|
33
|
+
]
|
34
|
+
s.homepage = %q{https://github.com/rob-linton/Medea}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.7}
|
38
|
+
s.summary = %q{Simple wrapper for persisting objects to JasonDB}
|
39
|
+
|
40
|
+
if s.respond_to? :specification_version then
|
41
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
42
|
+
s.specification_version = 3
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
46
|
+
s.add_runtime_dependency(%q<rest-client>, [">= 0"])
|
47
|
+
else
|
48
|
+
s.add_dependency(%q<json>, [">= 0"])
|
49
|
+
s.add_dependency(%q<rest-client>, [">= 0"])
|
50
|
+
end
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<json>, [">= 0"])
|
53
|
+
s.add_dependency(%q<rest-client>, [">= 0"])
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: medea
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 2
|
9
|
+
version: 0.2.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Michael Jensen
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-21 00:00:00 +11:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: json
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rest-client
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
description: Simple wrapper for persisting objects to JasonDB
|
47
|
+
email: michaelj@jasondb.com
|
48
|
+
executables: []
|
49
|
+
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files:
|
53
|
+
- README
|
54
|
+
files:
|
55
|
+
- README
|
56
|
+
- Rakefile
|
57
|
+
- VERSION
|
58
|
+
- assets.file
|
59
|
+
- assets.http
|
60
|
+
- index.html
|
61
|
+
- lib/medea.rb
|
62
|
+
- lib/medea/inheritable_attributes.rb
|
63
|
+
- lib/medea/jasondb.rb
|
64
|
+
- lib/medea/jasondeferredquery.rb
|
65
|
+
- lib/medea/jasonlistproperty.rb
|
66
|
+
- lib/medea/jasonobject.rb
|
67
|
+
- medea-0.2.1.gem
|
68
|
+
- medea.gemspec
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: https://github.com/rob-linton/Medea
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options:
|
75
|
+
- --charset=UTF-8
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 1.3.7
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Simple wrapper for persisting objects to JasonDB
|
101
|
+
test_files: []
|
102
|
+
|