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 ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Dependencies are specified in medea.gemspec
4
+ gemspec
data/README ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.25
@@ -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>
@@ -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
@@ -0,0 +1,3 @@
1
+ module Medea
2
+ VERSION = "0.2.26"
3
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+