Medea 0.2.26
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/Gemfile +4 -0
- data/README +0 -0
- data/Rakefile +2 -0
- data/VERSION +1 -0
- data/index.html +78 -0
- data/lib/medea.rb +10 -0
- data/lib/medea/active_model_methods.rb +24 -0
- data/lib/medea/inheritable_attributes.rb +31 -0
- data/lib/medea/jasondb.rb +20 -0
- data/lib/medea/jasondeferredquery.rb +147 -0
- data/lib/medea/jasonlistproperty.rb +131 -0
- data/lib/medea/jasonobject.rb +271 -0
- data/lib/medea/list_properties.rb +31 -0
- data/lib/medea/version.rb +3 -0
- data/lib/test.rb +31 -0
- data/lib/testjdq.rb +40 -0
- data/lib/testjlp.rb +30 -0
- data/lib/testmeta.rb +38 -0
- data/lib/testsublist.rb +46 -0
- data/medea.gemspec +23 -0
- metadata +128 -0
data/Gemfile
ADDED
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.25
|
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,10 @@
|
|
1
|
+
|
2
|
+
module Medea
|
3
|
+
require 'medea/inheritable_attributes'
|
4
|
+
require 'medea/active_model_methods'
|
5
|
+
require 'medea/list_properties'
|
6
|
+
require 'medea/jasonobject'
|
7
|
+
require 'medea/jasondeferredquery'
|
8
|
+
require 'medea/jasonlistproperty'
|
9
|
+
require 'medea/jasondb'
|
10
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Medea
|
2
|
+
module ActiveModelMethods
|
3
|
+
def to_model
|
4
|
+
self
|
5
|
+
end
|
6
|
+
|
7
|
+
def errors
|
8
|
+
obj = Object.new
|
9
|
+
def obj.[](key) [] end
|
10
|
+
def obj.full_messages() [] end
|
11
|
+
def obj.any?() false end
|
12
|
+
def obj.count() 0 end
|
13
|
+
obj
|
14
|
+
end
|
15
|
+
|
16
|
+
def persisted?
|
17
|
+
jason_state == :stale
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
#for some reason, in rails, @inheritable_attributes is set to be an empty hash here...
|
12
|
+
#check for this strange case and account for it.
|
13
|
+
@inheritable_attributes = [:inheritable_attributes] if @inheritable_attributes == {} ||
|
14
|
+
@inheritable_attributes == nil
|
15
|
+
@inheritable_attributes += args
|
16
|
+
args.each do |arg|
|
17
|
+
class_eval %(
|
18
|
+
class << self; attr_accessor :#{arg} end
|
19
|
+
)
|
20
|
+
end
|
21
|
+
@inheritable_attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
def inherited(subclass)
|
25
|
+
@inheritable_attributes.each do |inheritable_attribute|
|
26
|
+
instance_var = "@#{inheritable_attribute}"
|
27
|
+
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
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
|
+
def JasonDB::db_auth_url mode=:secure
|
5
|
+
config = Rails.configuration.database_configuration[Rails.env]
|
6
|
+
|
7
|
+
user = config["user"]
|
8
|
+
topic = config["topic"]
|
9
|
+
password = config["password"]
|
10
|
+
if config["jason_host"]
|
11
|
+
host = config["jason_host"]
|
12
|
+
else
|
13
|
+
host = "rest.jasondb.com"
|
14
|
+
end
|
15
|
+
protocol = "http"
|
16
|
+
protocol << "s" if mode == :secure
|
17
|
+
"#{protocol}://#{user}:#{password}@#{host}/#{topic}/"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Medea
|
2
|
+
class JasonDeferredQuery
|
3
|
+
require 'rest_client'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
attr_accessor :time_limit, :result_format, :type, :time_limit, :state, :contents, :filters
|
7
|
+
|
8
|
+
def initialize a_class, format=:search
|
9
|
+
self.type = a_class
|
10
|
+
self.filters = { :VERSION0 => nil }
|
11
|
+
if self.type
|
12
|
+
self.filters[:FILTER] = {:HTTP_X_CLASS => a_class.name.to_s}
|
13
|
+
end
|
14
|
+
self.result_format = format
|
15
|
+
self.time_limit = 0
|
16
|
+
self.state = :prefetch
|
17
|
+
self.contents = []
|
18
|
+
end
|
19
|
+
|
20
|
+
#Here we're going to put the "query" interface
|
21
|
+
|
22
|
+
#here we will capture:
|
23
|
+
#members_of(object) (where object is an instance of a class that this class can be a member of)
|
24
|
+
#members_of_<classname>(key)
|
25
|
+
#find_by_<property>(value)
|
26
|
+
#Will return a JasonDeferredQuery for this class with the appropriate data filter set
|
27
|
+
def method_missing(name, *args, &block)
|
28
|
+
#if we are postfetch, we throw away all our cached results
|
29
|
+
if self.state == :postfetch
|
30
|
+
self.state = :prefetch
|
31
|
+
self.contents = []
|
32
|
+
end
|
33
|
+
|
34
|
+
field = name.to_s
|
35
|
+
|
36
|
+
if field =~ /^members_of$/
|
37
|
+
#use the type and key of the first arg (being a JasonObject)
|
38
|
+
#args[0] must be a JasonObject (or child)
|
39
|
+
raise ArgumentError, "When looking for members, you must pass a JasonObject" unless args[0].is_a? JasonObject
|
40
|
+
|
41
|
+
self.filters[:DATA_FILTER] ||= {}
|
42
|
+
self.filters[:DATA_FILTER]["__member_of"] ||= []
|
43
|
+
self.filters[:DATA_FILTER]["__member_of"] << args[0].jason_key
|
44
|
+
elsif field =~ /^find_by_(.*)$/
|
45
|
+
#use the property name from the name variable, and the value from the first arg
|
46
|
+
add_data_filter $1, args[0].to_s
|
47
|
+
else
|
48
|
+
#no method!
|
49
|
+
super
|
50
|
+
return
|
51
|
+
end
|
52
|
+
#return self, so that we can chain up query refinements
|
53
|
+
self
|
54
|
+
end
|
55
|
+
#end query interface
|
56
|
+
|
57
|
+
def add_data_filter property, value
|
58
|
+
self.filters[:DATA_FILTER] ||= {}
|
59
|
+
self.filters[:DATA_FILTER][property] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_url
|
63
|
+
url = "#{JasonDB::db_auth_url}@#{self.time_limit}.#{self.result_format}?"
|
64
|
+
filter_array = []
|
65
|
+
self.filters.each do |name, val|
|
66
|
+
if not val
|
67
|
+
filter_array << name.to_s
|
68
|
+
next
|
69
|
+
else
|
70
|
+
#FILTER's value is a hash (to avoid dupes)
|
71
|
+
#DATA_FILTER's value is a hash
|
72
|
+
if val.is_a? Hash
|
73
|
+
#for each k/v in the hash, we want to add an entry to filter_array
|
74
|
+
val.each do |field ,value|
|
75
|
+
if value.is_a? Array
|
76
|
+
value.each do |i|
|
77
|
+
filter_array << "#{name.to_s}=#{URI.escape(field)}:#{URI.escape(i)}"
|
78
|
+
end
|
79
|
+
else
|
80
|
+
filter_array << "#{name.to_s}=#{URI.escape(field.to_s)}:#{URI.escape(value.to_s)}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
url + filter_array.join("&")
|
88
|
+
end
|
89
|
+
|
90
|
+
#array access interface
|
91
|
+
def [](index)
|
92
|
+
execute_query unless self.state == :postfetch
|
93
|
+
self.contents[index]
|
94
|
+
end
|
95
|
+
|
96
|
+
def each(&block)
|
97
|
+
execute_query unless self.state == :postfetch
|
98
|
+
self.contents.each &block
|
99
|
+
end
|
100
|
+
|
101
|
+
def count
|
102
|
+
execute_query unless self.state == :postfetch
|
103
|
+
self.contents.count
|
104
|
+
end
|
105
|
+
|
106
|
+
def include?(item)
|
107
|
+
execute_query unless self.state == :postfetch
|
108
|
+
self.contents.each do |i|
|
109
|
+
return true if i.jason_key == item.jason_key
|
110
|
+
end
|
111
|
+
false
|
112
|
+
end
|
113
|
+
#end array interface
|
114
|
+
|
115
|
+
def execute_query
|
116
|
+
#hit the URL
|
117
|
+
#fill self.contents with :ghost versions of JasonObjects
|
118
|
+
begin
|
119
|
+
#puts " = Executing #{type.name} deferred query! (#{to_url})"
|
120
|
+
result = JSON.parse(RestClient.get to_url)
|
121
|
+
self.contents = []
|
122
|
+
#results are in a hash, their keys are just numbers
|
123
|
+
result.keys.each do |k|
|
124
|
+
if k =~ /^[0-9]+$/
|
125
|
+
#this is a result! get the key
|
126
|
+
/\/([^\/]*)\/([^\/]*)$/.match result[k]["POST_TO"]
|
127
|
+
#$1 is the class name, $2 is the key
|
128
|
+
item = type.new($2, :lazy)
|
129
|
+
if result[k].has_key?("CONTENT") && result[k]["CONTENT"] != ""
|
130
|
+
item.instance_variable_set(:@__jason_data, result[k]["CONTENT"])
|
131
|
+
item.instance_variable_set(:@__jason_state, :stale)
|
132
|
+
end
|
133
|
+
if result[k].has_key?("HTTP_X_PARENT") && result[k]["HTTP_X_PARENT"] != ""
|
134
|
+
item.jason_parent_key = result[k]["HTTP_X_PARENT"]
|
135
|
+
end
|
136
|
+
self.contents << item
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
self.state = :postfetch
|
141
|
+
result
|
142
|
+
rescue
|
143
|
+
self.contents = []
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,131 @@
|
|
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 = :search
|
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" => @list_name.to_s,
|
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 remove! member
|
81
|
+
raise RuntimeError, "You can only remove an item if you are accessing this list from an object." unless @parent.is_a? JasonObject
|
82
|
+
raise ArgumentError, "You can only remove #{@type.name} items from this collection!" unless member.is_a? @type
|
83
|
+
raise ArgumentError, "This item (#{member.jason_key}) doesn't exist in the list you're trying to remove it from!" unless self.include? member
|
84
|
+
|
85
|
+
if @list_type == :value
|
86
|
+
member.jason_parent = nil
|
87
|
+
member.jason_parent_list = nil
|
88
|
+
member.save!
|
89
|
+
elsif @list_type == :reference
|
90
|
+
|
91
|
+
#send DELETE to JasonDB::db_auth_url/a_class.name/
|
92
|
+
url = "#{JasonDB::db_auth_url}#{@type.name}/#{@parent.jason_key}/#{@list_name}/#{member.jason_key}"
|
93
|
+
|
94
|
+
response = RestClient.delete url
|
95
|
+
|
96
|
+
if response.code == 201
|
97
|
+
#delete successful!
|
98
|
+
else
|
99
|
+
raise "DELETE failed! Could not remove membership"
|
100
|
+
end
|
101
|
+
else
|
102
|
+
#parent is a JasonObject, but this list is something other than :value or :reference??
|
103
|
+
raise "Invalid list type or trying to remove an item from a subquery list!"
|
104
|
+
end
|
105
|
+
|
106
|
+
@state = :prefetch
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_url
|
110
|
+
url = "#{JasonDB::db_auth_url}@#{@time_limit}.#{@result_format}?"
|
111
|
+
params = ["VERSION0"]
|
112
|
+
|
113
|
+
params << "FILTER=HTTP_X_CLASS:#{@list_name.to_s}"
|
114
|
+
|
115
|
+
if @parent.is_a? JasonObject
|
116
|
+
params << "FILTER=HTTP_X_PARENT:#{@parent.jason_key}"
|
117
|
+
else # @parent.is_a? JasonListProperty ##(or DeferredQuery?)
|
118
|
+
#we can get the insecure url here, because it will be resolved and executed at JasonDB - on a secure subnet.
|
119
|
+
|
120
|
+
#subquery = "<%@LANGUAGE=\"URL\" #{@parent.to_url}%>"
|
121
|
+
#puts " = Fetching subquery stupidly. (#{@parent.to_url})"
|
122
|
+
|
123
|
+
subquery = (RestClient.get @parent.to_url).strip
|
124
|
+
#puts " = Result: #{subquery}"
|
125
|
+
params << URI.escape("FILTER={HTTP_X_PARENT:#{subquery}}", Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
126
|
+
end
|
127
|
+
|
128
|
+
url << params.join("&")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,271 @@
|
|
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 Medea::ActiveModelMethods
|
11
|
+
#include JasonDB
|
12
|
+
|
13
|
+
#meta-programming interface for lists
|
14
|
+
include ClassLevelInheritableAttributes
|
15
|
+
inheritable_attributes :owned
|
16
|
+
@owned = false
|
17
|
+
|
18
|
+
include JasonObjectListProperties
|
19
|
+
|
20
|
+
#end meta
|
21
|
+
|
22
|
+
#Here we're going to put the "query" interface
|
23
|
+
|
24
|
+
#create a JasonDeferredQuery with no conditions, other than HTTP_X_CLASS=self.name
|
25
|
+
#if mode is set to :eager, we create the JasonDeferredQuery, invoke it's execution and then return it
|
26
|
+
def JasonObject.all(mode=:lazy)
|
27
|
+
JasonDeferredQuery.new self
|
28
|
+
end
|
29
|
+
|
30
|
+
#returns the JasonObject by directly querying the URL
|
31
|
+
#if mode is :lazy, we return a GHOST, if mode is :eager, we return a STALE JasonObject
|
32
|
+
def JasonObject.get_by_key(key, mode=:eager)
|
33
|
+
return self.new key, mode
|
34
|
+
end
|
35
|
+
|
36
|
+
#here we will capture:
|
37
|
+
#members_of(object) (where object is an instance of a class that this class can be a member of)
|
38
|
+
#find_by_<property>(value)
|
39
|
+
#Will return a JasonDeferredQuery for this class with the appropriate data filter set
|
40
|
+
def JasonObject.method_missing(name, *args, &block)
|
41
|
+
q = JasonDeferredQuery.new self
|
42
|
+
if name.to_s =~ /^members_of$/
|
43
|
+
#use the type and key of the first arg (being a JasonObject)
|
44
|
+
return q.members_of args[0]
|
45
|
+
elsif name.to_s =~ /^find_by_(.*)$/
|
46
|
+
#use the property name from the name variable, and the value from the first arg
|
47
|
+
q.add_data_filter $1, args[0]
|
48
|
+
|
49
|
+
return q
|
50
|
+
else
|
51
|
+
#no method!
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
#end query interface
|
56
|
+
|
57
|
+
#the resolve method takes a key and returns the JasonObject that has that key
|
58
|
+
#This is useful when you have the key, but not the class
|
59
|
+
def JasonObject.resolve(key, mode=:lazy)
|
60
|
+
q = JasonDeferredQuery.new nil
|
61
|
+
q.filters[:FILTER] ||= {}
|
62
|
+
q.filters[:FILTER][:HTTP_X_KEY] = key
|
63
|
+
resp = JSON.parse(RestClient.get(q.to_url))
|
64
|
+
if resp.has_key? "1"
|
65
|
+
#this is the object, figure out its class
|
66
|
+
resp["1"]["POST_TO"] =~ /([^\/]+)\/#{key}/
|
67
|
+
begin
|
68
|
+
result = Kernel.const_get($1).get_by_key key, :lazy
|
69
|
+
if result["1"].has_key? "CONTENT"
|
70
|
+
result.instance_variable_set(:@__jason_data, result["1"]["CONTENT"])
|
71
|
+
result.instance_variable_set(:@__jason_state, :stale)
|
72
|
+
end
|
73
|
+
if mode == :eager
|
74
|
+
result.send(:load)
|
75
|
+
end
|
76
|
+
rescue
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#"flexihash" access interface
|
83
|
+
def []=(key, value)
|
84
|
+
@__jason_data ||= {}
|
85
|
+
@__jason_state = :dirty if jason_state == :stale
|
86
|
+
|
87
|
+
@__jason_data[key] = value
|
88
|
+
end
|
89
|
+
|
90
|
+
def [](key)
|
91
|
+
@__jason_data[key]
|
92
|
+
end
|
93
|
+
|
94
|
+
#The "Magic" component of candy (https://github.com/SFEley/candy), repurposed to make this a
|
95
|
+
# "weak object" that can take any attribute.
|
96
|
+
# Assigning any attribute will add it to the object's hash (and then be POSTed to JasonDB on the next save)
|
97
|
+
def method_missing(name, *args, &block)
|
98
|
+
load if @__jason_state == :ghost
|
99
|
+
field = name.to_s
|
100
|
+
if field =~ /(.*)=$/ # We're assigning
|
101
|
+
self[$1] = args[0]
|
102
|
+
elsif field =~ /(.*)\?$/ # We're asking
|
103
|
+
(self[$1] ? true : false)
|
104
|
+
else
|
105
|
+
self[field]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
#end "flexihash" access
|
109
|
+
|
110
|
+
def initialize key = nil, mode = :eager
|
111
|
+
if key
|
112
|
+
@__id = key
|
113
|
+
if mode == :eager
|
114
|
+
load
|
115
|
+
else
|
116
|
+
@__jason_state = :ghost
|
117
|
+
end
|
118
|
+
else
|
119
|
+
@__jason_state = :new
|
120
|
+
@__jason_data = {}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def jason_key
|
125
|
+
#Generate a random UUID for this object.
|
126
|
+
#since jason urls must start with a letter, we'll use the first letter of the class name
|
127
|
+
@__id ||= "#{self.class.name[0].chr.downcase}#{UUIDTools::UUID::random_create.to_s}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_s
|
131
|
+
jason_key
|
132
|
+
end
|
133
|
+
|
134
|
+
def jason_state
|
135
|
+
@__jason_state
|
136
|
+
end
|
137
|
+
|
138
|
+
def jason_etag
|
139
|
+
@__jason_etag ||= ""
|
140
|
+
end
|
141
|
+
|
142
|
+
def jason_parent
|
143
|
+
@__jason_parent ||= nil
|
144
|
+
if @__jason_parent == nil && @__jason_parent_key
|
145
|
+
#key is set but parent not? load the parent
|
146
|
+
@__jason_parent = JasonObject.resolve @__jason_parent_key
|
147
|
+
end
|
148
|
+
@__jason_parent
|
149
|
+
end
|
150
|
+
|
151
|
+
def jason_parent= parent
|
152
|
+
@__jason_parent = parent
|
153
|
+
@__jason_parent_key = parent.jason_key
|
154
|
+
end
|
155
|
+
|
156
|
+
def jason_parent_key
|
157
|
+
@__jason_parent_key ||= nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def jason_parent_key= value
|
161
|
+
@__jason_parent_key = value
|
162
|
+
#reset the parent here?
|
163
|
+
@__jason_parent = nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def jason_parent_list
|
167
|
+
@__jason_parent_list ||= nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def jason_parent_list= value
|
171
|
+
@__jason_parent_list = value
|
172
|
+
end
|
173
|
+
|
174
|
+
#object persistence methods
|
175
|
+
|
176
|
+
#POSTs the current values of this object back to JasonDB
|
177
|
+
#on successful post, sets state to STALE and updates eTag
|
178
|
+
def save!
|
179
|
+
#no changes? no save!
|
180
|
+
return if @__jason_state == :stale or @__jason_state == :ghost
|
181
|
+
|
182
|
+
|
183
|
+
payload = self.to_json
|
184
|
+
post_headers = {
|
185
|
+
:content_type => 'application/json',
|
186
|
+
|
187
|
+
"X-KEY" => self.jason_key,
|
188
|
+
"X-CLASS" => self.class.name
|
189
|
+
#also want to add the eTag here!
|
190
|
+
#may also want to add any other indexable fields that the user specifies?
|
191
|
+
}
|
192
|
+
post_headers["IF-MATCH"] = @__jason_etag if @__jason_state == :dirty
|
193
|
+
|
194
|
+
if self.class.owned
|
195
|
+
#the parent object needs to be defined!
|
196
|
+
raise "#{self.class.name} cannot be saved without setting a parent and list!" unless self.jason_parent && self.jason_parent_list
|
197
|
+
post_headers["X-PARENT"] = self.jason_parent.jason_key
|
198
|
+
#url = "#{JasonDB::db_auth_url}#{self.jason_parent.class.name}/#{self.jason_parent.jason_key}/#{self.jason_parent_list}/#{self.jason_key}"
|
199
|
+
post_headers["X-LIST"] = self.jason_parent_list
|
200
|
+
#override the class to be the list name. Much simpler to search on.
|
201
|
+
post_headers["X-CLASS"] = self.jason_parent_list
|
202
|
+
end
|
203
|
+
url = JasonDB::db_auth_url + self.class.name + "/" + self.jason_key
|
204
|
+
|
205
|
+
#puts "Posted to JasonDB!"
|
206
|
+
|
207
|
+
#puts "Saving to #{url}"
|
208
|
+
response = RestClient.post url, payload, post_headers
|
209
|
+
|
210
|
+
if response.code == 201
|
211
|
+
#save successful!
|
212
|
+
#store the new eTag for this object
|
213
|
+
#puts response.raw_headers
|
214
|
+
#@__jason_etag = response.headers[:location] + ":" + response.headers[:content_md5]
|
215
|
+
else
|
216
|
+
raise "POST failed! Could not save object"
|
217
|
+
end
|
218
|
+
|
219
|
+
@__jason_state = :stale
|
220
|
+
end
|
221
|
+
|
222
|
+
def delete!
|
223
|
+
url = "#{JasonDB::db_auth_url}#{self.class.name}/#{self.jason_key}"
|
224
|
+
response = RestClient.delete url
|
225
|
+
raise "DELETE failed!" unless response.code == 201
|
226
|
+
end
|
227
|
+
|
228
|
+
#end object persistence
|
229
|
+
|
230
|
+
#converts the data hash (that is, @__jason_data) to JSON format
|
231
|
+
def to_json
|
232
|
+
JSON.generate(@__jason_data)
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
#fetches the data from the JasonDB
|
238
|
+
def load
|
239
|
+
#because this object might be owned by another, we need to search by key.
|
240
|
+
#not passing a format to the query is a shortcut to getting just the object.
|
241
|
+
url = "#{JasonDB::db_auth_url}@0.content?"
|
242
|
+
params = [
|
243
|
+
"VERSION0",
|
244
|
+
"FILTER=HTTP_X_KEY:#{self.jason_key}"
|
245
|
+
]
|
246
|
+
|
247
|
+
if not self.class.owned
|
248
|
+
#if the class is owned, we don't want to filter by class name (X-CLASS will be the list name)
|
249
|
+
#if it isn't owned, it is safe to filter by X-CLASS
|
250
|
+
#if this item is "had" rather than owned, we MUST filter by class, otherwise we get the references.
|
251
|
+
params << "FILTER=HTTP_X_CLASS:#{self.class.name}"
|
252
|
+
end
|
253
|
+
|
254
|
+
url << params.join("&")
|
255
|
+
#url = "#{JasonDB::db_auth_url}#{self.class.name}/#{self.jason_key}"
|
256
|
+
|
257
|
+
#puts " = Retrieving #{self.class.name} at #{url}"
|
258
|
+
response = RestClient.get url
|
259
|
+
@__jason_data = JSON.parse response
|
260
|
+
@__jason_etag = response.headers[:etag]
|
261
|
+
@__jason_state = :stale
|
262
|
+
end
|
263
|
+
|
264
|
+
def lazy_load meta
|
265
|
+
#TODO Implement lazy load
|
266
|
+
|
267
|
+
@__jason_state = :ghost
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module JasonObjectListProperties
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(MetaListProperties)
|
4
|
+
end
|
5
|
+
|
6
|
+
module MetaListProperties
|
7
|
+
def create_member_list list_name, list_class, list_type
|
8
|
+
list = {}
|
9
|
+
list = self.class_variable_get :@@lists if self.class_variable_defined? :@@lists
|
10
|
+
list[list_name] = [list_class, list_type]
|
11
|
+
self.send(:class_variable_set, "@@lists", list)
|
12
|
+
|
13
|
+
define_method(list_name) do
|
14
|
+
#puts "Looking at the #{list_name.to_s} list, which is full of #{list_type.name}s"
|
15
|
+
Medea::JasonListProperty.new self, list_name, list_class, list_type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_many list_name, list_class
|
20
|
+
create_member_list list_name, list_class, :reference
|
21
|
+
end
|
22
|
+
|
23
|
+
def owns_many list_name, list_class
|
24
|
+
create_member_list list_name, list_class, :value
|
25
|
+
|
26
|
+
#also modify the items in the list so that they know that they're owned
|
27
|
+
#list_type.class_variable_set :@@owner, self
|
28
|
+
list_class.owned = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/test.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$: << "~/Projects/Medea/lib"
|
2
|
+
require 'rubygems'
|
3
|
+
require 'medea'
|
4
|
+
|
5
|
+
class Person < Medea::JasonObject
|
6
|
+
end
|
7
|
+
|
8
|
+
mikey = Person.new
|
9
|
+
puts "state: #{mikey.jason_state}"
|
10
|
+
mikey.name = "Michael"
|
11
|
+
mikey.age = 21
|
12
|
+
mikey.location = {:longitude => -30.123213, :latitude => 130.1231458}
|
13
|
+
puts mikey.jason_key
|
14
|
+
puts mikey.to_json
|
15
|
+
mikey.save!
|
16
|
+
|
17
|
+
puts "state: #{mikey.jason_state}"
|
18
|
+
|
19
|
+
puts "Changing name => Bob"
|
20
|
+
mikey.name = "Bob"
|
21
|
+
|
22
|
+
puts "state: #{mikey.jason_state}"
|
23
|
+
puts mikey.to_json
|
24
|
+
mikey.save!
|
25
|
+
puts "state: #{mikey.jason_state}"
|
26
|
+
|
27
|
+
puts "Enter a Person key to retrieve: "
|
28
|
+
id = gets.strip
|
29
|
+
|
30
|
+
person = Person.new id
|
31
|
+
puts person.to_json
|
data/lib/testjdq.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$: << "~/Projects/Medea/lib"
|
2
|
+
require 'medea'
|
3
|
+
|
4
|
+
class Person < Medea::JasonObject
|
5
|
+
end
|
6
|
+
|
7
|
+
class Company < Medea::JasonObject
|
8
|
+
has_many :employees, Person
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "Lets make a person!"
|
12
|
+
p = Person.new
|
13
|
+
puts "Name?"
|
14
|
+
p.name = gets.strip
|
15
|
+
puts "Age?"
|
16
|
+
p.age = gets.strip.to_i
|
17
|
+
puts "OK - Saving"
|
18
|
+
p.save!
|
19
|
+
|
20
|
+
puts "", "Lets make a company!"
|
21
|
+
c = Company.new
|
22
|
+
puts "Name?"
|
23
|
+
c.name = gets.strip
|
24
|
+
puts "Address?"
|
25
|
+
c.address = gets.strip
|
26
|
+
puts "OK - Saving"
|
27
|
+
c.save!
|
28
|
+
|
29
|
+
puts "", "Making #{p.name} a member of #{c.name}"
|
30
|
+
c.employees.add! p
|
31
|
+
puts "OK - Saving"
|
32
|
+
p.save!
|
33
|
+
|
34
|
+
puts "", "Now querying for Persons that are members of #{c.name}"
|
35
|
+
r = c.employees
|
36
|
+
puts "Query: #{r.to_url}"
|
37
|
+
puts "Got #{r.count} items:"
|
38
|
+
r.each do |p|
|
39
|
+
puts p.name
|
40
|
+
end
|
data/lib/testjlp.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$: << "~/Projects/Medea/lib"
|
2
|
+
require 'medea'
|
3
|
+
|
4
|
+
class Person < Medea::JasonObject
|
5
|
+
has_many :followees, Person
|
6
|
+
end
|
7
|
+
|
8
|
+
p = Person.get_by_key "p6c75c02f-ed93-4ee8-9746-3603e915be23"
|
9
|
+
|
10
|
+
puts p.followees.to_url
|
11
|
+
puts p.followees.count
|
12
|
+
|
13
|
+
puts "Let's make a new person!"
|
14
|
+
|
15
|
+
p1 = Person.new
|
16
|
+
puts "Name?"
|
17
|
+
p1.name = gets.strip
|
18
|
+
if p1.name != ""
|
19
|
+
puts "Saving..."
|
20
|
+
p1.save!
|
21
|
+
|
22
|
+
puts "Making #{p.name} follow #{p1.name}..."
|
23
|
+
p.followees.add! p1
|
24
|
+
end
|
25
|
+
puts "Done!"
|
26
|
+
list = p.followees
|
27
|
+
puts "#{p.name} now following #{list.count} users:"
|
28
|
+
list.each do |f|
|
29
|
+
puts " - #{f.jason_key}: #{f.name}\n"
|
30
|
+
end
|
data/lib/testmeta.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
$: << "~/Projects/Medea/lib"
|
2
|
+
require 'medea'
|
3
|
+
|
4
|
+
class Message < Medea::JasonObject
|
5
|
+
end
|
6
|
+
|
7
|
+
class User < Medea::JasonObject
|
8
|
+
owns_many :messages, Message
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "Enter an id, or blank to make a new user:"
|
12
|
+
id = gets.strip
|
13
|
+
if id == ""
|
14
|
+
u = User.new
|
15
|
+
puts "User's name?"
|
16
|
+
u.name = gets.strip
|
17
|
+
|
18
|
+
puts "Saving"
|
19
|
+
u.save!
|
20
|
+
else
|
21
|
+
u = User.get_by_key id
|
22
|
+
puts "#{u.name} has posted #{u.messages.count} messages"
|
23
|
+
end
|
24
|
+
|
25
|
+
while true
|
26
|
+
puts "Enter a message (blank to stop):"
|
27
|
+
message = gets.strip
|
28
|
+
break if message == ""
|
29
|
+
m = Message.new
|
30
|
+
m.message = message
|
31
|
+
m.from = u.name
|
32
|
+
u.messages.add! m
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "Fetching messages..."
|
36
|
+
u.messages.each do |e|
|
37
|
+
puts " - #{e.message}\n"
|
38
|
+
end
|
data/lib/testsublist.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
$: << "~/Projects/Medea/lib"
|
2
|
+
require 'medea'
|
3
|
+
|
4
|
+
class Message < Medea::JasonObject; end
|
5
|
+
|
6
|
+
class User < Medea::JasonObject
|
7
|
+
owns_many :messages, Message
|
8
|
+
has_many :followees, User
|
9
|
+
end
|
10
|
+
|
11
|
+
u1 = User.new
|
12
|
+
u1.name = "Fred"
|
13
|
+
u1.save!
|
14
|
+
|
15
|
+
u2 = User.new
|
16
|
+
u2.name = "George"
|
17
|
+
u2.save!
|
18
|
+
|
19
|
+
u1.followees.add! u2
|
20
|
+
u1.followees.add! (User.get_by_key "p438639000")
|
21
|
+
u1.followees.add! u1
|
22
|
+
|
23
|
+
m1 = Message.new
|
24
|
+
m1.from = u2.name
|
25
|
+
m1.message = "Hello! This is George"
|
26
|
+
u2.messages.add! m1
|
27
|
+
|
28
|
+
m3 = Message.new
|
29
|
+
m3.from = u1.name
|
30
|
+
m3.message = "George sent me here, hope it's fun!"
|
31
|
+
u1.messages.add! m3
|
32
|
+
|
33
|
+
m2 = Message.new
|
34
|
+
m2.from = u2.name
|
35
|
+
m2.message = "Man, this is a long day!"
|
36
|
+
u2.messages.add! m2
|
37
|
+
|
38
|
+
puts "#{u2.name} has posted #{u2.messages.count} messages"
|
39
|
+
|
40
|
+
puts "#{u1.name} is following #{u1.followees.count} users"
|
41
|
+
puts "#{u1.name}'s timeline has #{u1.followees.messages.count} messages in it"
|
42
|
+
|
43
|
+
u1.followees.messages.each do |m|
|
44
|
+
puts "#{m.from}:"
|
45
|
+
puts " #{m.message}"
|
46
|
+
end
|
data/medea.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "medea/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "Medea"
|
7
|
+
s.version = Medea::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Michael Jensen"]
|
10
|
+
s.email = ["michaelj@jasondb.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Simple wrapper for persisting objects to JasonDB}
|
13
|
+
s.description = %q{Simple wrapper for persisting objects to JasonDB}
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency "json"
|
21
|
+
s.add_dependency "rest-client"
|
22
|
+
s.add_dependency "uuidtools"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: Medea
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 35
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 26
|
10
|
+
version: 0.2.26
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Michael Jensen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-05 00:00:00 +11:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rest-client
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: uuidtools
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
description: Simple wrapper for persisting objects to JasonDB
|
64
|
+
email:
|
65
|
+
- michaelj@jasondb.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files: []
|
71
|
+
|
72
|
+
files:
|
73
|
+
- Gemfile
|
74
|
+
- README
|
75
|
+
- Rakefile
|
76
|
+
- VERSION
|
77
|
+
- index.html
|
78
|
+
- lib/medea.rb
|
79
|
+
- lib/medea/active_model_methods.rb
|
80
|
+
- lib/medea/inheritable_attributes.rb
|
81
|
+
- lib/medea/jasondb.rb
|
82
|
+
- lib/medea/jasondeferredquery.rb
|
83
|
+
- lib/medea/jasonlistproperty.rb
|
84
|
+
- lib/medea/jasonobject.rb
|
85
|
+
- lib/medea/list_properties.rb
|
86
|
+
- lib/medea/version.rb
|
87
|
+
- lib/test.rb
|
88
|
+
- lib/testjdq.rb
|
89
|
+
- lib/testjlp.rb
|
90
|
+
- lib/testmeta.rb
|
91
|
+
- lib/testsublist.rb
|
92
|
+
- medea.gemspec
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: ""
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.3.7
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Simple wrapper for persisting objects to JasonDB
|
127
|
+
test_files: []
|
128
|
+
|