medea 0.5.4 → 0.6.0

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/.rvmrc ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # adapted from: http://rvm.beginrescueend.com/workflow/rvmrc/
4
+
5
+ ruby_string="1.9.2"
6
+ gemset_name="medea"
7
+
8
+ if rvm list strings | grep -q "${ruby_string}" ; then
9
+
10
+ # Load or create the specified environment
11
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
12
+ && -s "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}" ]] ; then
13
+ \. "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}"
14
+ else
15
+ rvm --create "${ruby_string}@${gemset_name}"
16
+ fi
17
+
18
+ else
19
+
20
+ # Notify the user to install the desired interpreter before proceeding.
21
+ echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
22
+
23
+ fi
24
+
25
+ #turn on the prompt in PS1
26
+ #RVM prompt
27
+ if [ -z "$RVM_PROMPT" ]
28
+ then
29
+ RVM_PROMPT=true
30
+ PS1="[\$(/usr/local/bin/rvm-prompt v g)] $PS1"
31
+ fi
32
+
data/lib/medea.rb CHANGED
@@ -5,6 +5,8 @@ module Medea
5
5
  require 'medea/inheritable_attributes'
6
6
  require 'medea/active_model_methods'
7
7
  require 'medea/meta_properties'
8
+ require 'medea/jason_base'
9
+ require 'medea/jason_blob'
8
10
  require 'medea/jasonobject'
9
11
  require 'medea/jasondeferredquery'
10
12
  require 'medea/jasonlistproperty'
@@ -0,0 +1,134 @@
1
+ module Medea
2
+ class JasonBase
3
+ #meta-programming interface for lists
4
+ include ClassLevelInheritableAttributes
5
+ inheritable_attributes :owned
6
+ @owned = false
7
+
8
+
9
+ #returns the JasonObject by directly querying the URL
10
+ #if mode is :lazy, we return a GHOST, if mode is :eager, we return a STALE JasonObject
11
+ def self.get_by_key(key, mode=:eager)
12
+ return self.new key, mode
13
+ end
14
+
15
+ #the resolve method takes a key and returns the JasonObject that has that key
16
+ #This is useful when you have the key, but not the class
17
+ def self.resolve(key, mode=:lazy)
18
+ q = JasonDeferredQuery.new :filters => {:VERSION0 => nil, :FILTER => {:HTTP_X_KEY => key, :HTTP_X_ACTION => :POST}}
19
+ q.filters[:FILTER] ||= {}
20
+ q.filters[:FILTER][:HTTP_X_KEY] = key
21
+ resp = JSON.parse(RestClient.get(q.to_url))
22
+ if resp.has_key? "1"
23
+ #this is the object, figure out its class
24
+ resp["1"]["POST_TO"] =~ /([^\/]+)\/#{key}/
25
+ begin
26
+ result = Kernel.const_get($1).get_by_key key, :lazy
27
+ if result["1"].has_key? "CONTENT"
28
+ result.instance_variable_set(:@__jason_data, result["1"]["CONTENT"])
29
+ result.instance_variable_set(:@__jason_state, :stale)
30
+ end
31
+ if mode == :eager
32
+ result.send(:load)
33
+ end
34
+ rescue
35
+ nil
36
+ end
37
+ end
38
+ end
39
+
40
+ def ==(other)
41
+ return false if not other.is_a? JasonBase
42
+ jason_key == other.jason_key
43
+ end
44
+
45
+ def jason_key
46
+ #Generate a random UUID for this object.
47
+ #since jason urls must start with a letter, we'll use the first letter of the class name
48
+ @__id ||= "#{self.class.name[0].chr.downcase}#{UUIDTools::UUID::random_create.to_s}"
49
+ end
50
+
51
+ def jason_state
52
+ @__jason_state
53
+ end
54
+
55
+ def jason_etag
56
+ @__jason_etag ||= ""
57
+ end
58
+
59
+ def jason_parent
60
+ @__jason_parent ||= nil
61
+ if @__jason_parent == nil && @__jason_parent_key
62
+ #key is set but parent not? load the parent
63
+ @__jason_parent = JasonObject.resolve @__jason_parent_key
64
+ end
65
+ @__jason_parent
66
+ end
67
+
68
+ def jason_parent= parent
69
+ @__jason_parent = parent
70
+ @__jason_parent_key = parent.jason_key
71
+ end
72
+
73
+ def jason_parent_key
74
+ @__jason_parent_key ||= nil
75
+ end
76
+
77
+ def jason_parent_key= value
78
+ @__jason_parent_key = value
79
+ #reset the parent here?
80
+ @__jason_parent = nil
81
+ end
82
+
83
+ def jason_parent_list
84
+ @__jason_parent_list ||= nil
85
+ end
86
+
87
+ def jason_parent_list= value
88
+ @__jason_parent_list = value
89
+ end
90
+
91
+ def delete! cascade=false
92
+ #TODO: Put this into some kind of async method or have JasonDB able to set flags on many records at once
93
+ #This will be REALLY REALLY slowww!
94
+ if cascade && (self.class.class_variable_defined? :@@lists)
95
+ @@lists.keys.each do |list_name|
96
+ #for each list that I have
97
+ list = send(list_name)
98
+ list.each do |item|
99
+ #remove each item from the list, deleting it if possible
100
+ list.remove! item, true
101
+ end
102
+ end
103
+ end
104
+ persist_changes :delete
105
+ end
106
+
107
+ def build_url
108
+ url = "#{JasonDB::db_auth_url}@0.content?"
109
+ params = [
110
+ "VERSION0",
111
+ "FILTER=HTTP_X_KEY:#{self.jason_key}",
112
+ "FILTER=HTTP_X_CLASS:#{self.class.name}"
113
+ ]
114
+
115
+ url << params.join("&")
116
+ url
117
+ end
118
+
119
+
120
+ #fetches the data from the JasonDB
121
+ def load_from_jasondb
122
+ #because this object might be owned by another, we need to search by key.
123
+ #not passing a format to the query is a shortcut to getting just the object.
124
+ url = build_url()
125
+
126
+ response = RestClient.get url
127
+ @__jason_data = JSON.parse response
128
+ @__jason_etag = response.headers[:etag]
129
+ @__jason_state = :stale
130
+ end
131
+
132
+
133
+ end
134
+ end
@@ -0,0 +1,110 @@
1
+ module Medea
2
+ class JasonBlob < JasonBase
3
+ attr_accessor :parent, :attachment_name
4
+
5
+ def initialize initialiser=nil, mode=:eager
6
+ if initialiser
7
+ if initialiser.is_a? Hash
8
+ @parent = initialiser[:parent]
9
+ @attachment_name = initialiser[:name]
10
+ @__jason_state = :ghost
11
+ if initialiser[:content]
12
+ self.contents = initialiser[:content]
13
+ @__jason_state = :new
14
+ end
15
+ return
16
+ end
17
+ @__id = initialiser
18
+ if mode == :eager
19
+ load_from_jasondb
20
+ else
21
+ @__jason_state = :ghost
22
+ end
23
+ else
24
+ @__jason_state = :new
25
+ @__jason_data = nil
26
+ end
27
+ end
28
+
29
+ def to_url
30
+ "#{@parent.to_url}/#{@attachment_name}"
31
+ end
32
+
33
+ def load_from_jasondb
34
+ #because this object might be owned by another, we need to search by key.
35
+ #not passing a format to the query is a shortcut to getting just the object.
36
+ url = to_url
37
+ response = nil
38
+ begin
39
+ response = RestClient.get url
40
+
41
+ #don't JSON parse blob data!
42
+ @__jason_data = response
43
+ @__jason_etag = response.headers[:etag]
44
+ @__jason_state = :stale
45
+ rescue
46
+ #an exception here means a bad url or a 404.
47
+ #404 simply means no data yet for this attachment
48
+ @__jason_state = :new
49
+ @__jason_data = nil
50
+ return
51
+ end
52
+ end
53
+
54
+ def contents
55
+ load_from_jasondb if @__jason_state == :ghost
56
+ @__jason_data
57
+ end
58
+
59
+ def contents= data
60
+ @__jason_data = data
61
+ @__jason_state = :dirty
62
+ end
63
+
64
+ def set_content_type type=nil
65
+ if type
66
+ @content_type = type
67
+ elsif contents.is_a? IO
68
+ @content_type = image_type(contents)
69
+ elsif contents.is_a? String
70
+ @content_type = "text/plain"
71
+ else
72
+ @content_type = "application/octet-stream"
73
+ end
74
+ end
75
+
76
+ def image_type(file)
77
+ case IO.read(file, 10)
78
+ when /^GIF8/; 'image/gif'
79
+ when /^\x89PNG/; 'image/png'
80
+ when /^\xff\xd8\xff\xe0\x00\x10JFIF/; 'image/jpeg'
81
+ when /^\xff\xd8\xff\xe1(.*){2}Exif/; 'image/jpeg'
82
+ else 'unknown'
83
+ end
84
+ end
85
+
86
+ def save!
87
+ return if @__jason_state == :stale or @__jason_state == :ghost
88
+ set_content_type
89
+ #write the contents of @__jason_data to parent url/attachment name
90
+ post_headers = {
91
+ :content_type => @content_type,
92
+ :length => contents.size,
93
+ "X-KEY" => self.jason_key,
94
+ "X-CLASS" => self.class.name,
95
+ "X-PARENT" => @parent.jason_key,
96
+ "X_LIST" => @attachment_name
97
+ #also want to add the eTag here!
98
+ #may also want to add any other indexable fields that the user specifies?
99
+ }
100
+
101
+ resp = RestClient.post to_url, contents, post_headers
102
+
103
+ if resp.code == 201
104
+ true
105
+ else
106
+ false
107
+ end
108
+ end
109
+ end
110
+ end
@@ -5,21 +5,15 @@ module Medea
5
5
  require 'json'
6
6
  require 'uuidtools'
7
7
 
8
- class JasonObject
8
+ class JasonObject < JasonBase
9
9
 
10
10
  include Medea::ActiveModelMethods
11
11
  if defined? ActiveModel
12
12
  extend ActiveModel::Naming
13
13
  end
14
- #include JasonDB
15
-
16
- #meta-programming interface for lists
17
- include ClassLevelInheritableAttributes
18
- inheritable_attributes :owned
19
- @owned = false
20
14
 
21
15
  include JasonObjectMetaProperties
22
-
16
+ attr_accessor :attachments
23
17
  #end meta
24
18
 
25
19
  #Here we're going to put the "query" interface
@@ -30,12 +24,6 @@ module Medea
30
24
  JasonDeferredQuery.new :class => self, :filters => {:VERSION0 => nil, :FILTER => {:HTTP_X_CLASS => self, :HTTP_X_ACTION => :POST}}
31
25
  end
32
26
 
33
- #returns the JasonObject by directly querying the URL
34
- #if mode is :lazy, we return a GHOST, if mode is :eager, we return a STALE JasonObject
35
- def JasonObject.get_by_key(key, mode=:eager)
36
- return self.new key, mode
37
- end
38
-
39
27
  #here we will capture:
40
28
  #members_of(object) (where object is an instance of a class that this class can be a member of)
41
29
  #find_by_<property>(value)
@@ -58,38 +46,12 @@ module Medea
58
46
  end
59
47
  #end query interface
60
48
 
61
- #the resolve method takes a key and returns the JasonObject that has that key
62
- #This is useful when you have the key, but not the class
63
- def JasonObject.resolve(key, mode=:lazy)
64
- q = JasonDeferredQuery.new :filters => {:VERSION0 => nil, :FILTER => {:HTTP_X_KEY => key, :HTTP_X_ACTION => :POST}}
65
- q.filters[:FILTER] ||= {}
66
- q.filters[:FILTER][:HTTP_X_KEY] = key
67
- resp = JSON.parse(RestClient.get(q.to_url))
68
- if resp.has_key? "1"
69
- #this is the object, figure out its class
70
- resp["1"]["POST_TO"] =~ /([^\/]+)\/#{key}/
71
- begin
72
- result = Kernel.const_get($1).get_by_key key, :lazy
73
- if result["1"].has_key? "CONTENT"
74
- result.instance_variable_set(:@__jason_data, result["1"]["CONTENT"])
75
- result.instance_variable_set(:@__jason_state, :stale)
76
- end
77
- if mode == :eager
78
- result.send(:load)
79
- end
80
- rescue
81
- nil
82
- end
83
- end
84
- end
85
-
86
- def ==(other)
87
- return false if not other.is_a? JasonObject
88
- jason_key == other.jason_key
89
- end
90
-
91
49
  #"flexihash" access interface
92
50
  def []=(key, value)
51
+ if @attachments.keys.include? key.to_sym
52
+ @attachments[key.to_sym] = Medea::JasonBlob.new({:parent => self, :name => key, :content => value})
53
+ return
54
+ end
93
55
  @__jason_data ||= {}
94
56
  @__jason_state = :dirty if jason_state == :stale
95
57
 
@@ -97,6 +59,14 @@ module Medea
97
59
  end
98
60
 
99
61
  def [](key)
62
+ if @attachments.keys.include? key.to_sym
63
+ if not @attachments[key.to_sym]
64
+ #retrieve the JasonBlob for this key
65
+ @attachments[key.to_sym] = Medea::JasonBlob.new({:parent => self, :name => key})
66
+ end
67
+
68
+ return @attachments[key.to_sym].contents
69
+ end
100
70
  @__jason_data[key]
101
71
  end
102
72
 
@@ -104,7 +74,7 @@ module Medea
104
74
  # "weak object" that can take any attribute.
105
75
  # Assigning any attribute will add it to the object's hash (and then be POSTed to JasonDB on the next save)
106
76
  def method_missing(name, *args, &block)
107
- load if @__jason_state == :ghost
77
+ load_from_jasondb if @__jason_state == :ghost
108
78
  field = name.to_s
109
79
  if field =~ /(.*)=$/ # We're assigning
110
80
  self[$1] = args[0]
@@ -130,6 +100,13 @@ module Medea
130
100
  end
131
101
 
132
102
  def initialize initialiser = nil, mode = :eager
103
+ @attachments = {}
104
+ if self.class.class_variable_defined? :@@attachments
105
+ (self.class.class_variable_get :@@attachments).each do |k|
106
+ @attachments[k] = nil
107
+ end
108
+ end
109
+
133
110
  if initialiser
134
111
  if initialiser.is_a? Hash
135
112
  @__jason_state = :new
@@ -137,7 +114,7 @@ module Medea
137
114
  else
138
115
  @__id = initialiser
139
116
  if mode == :eager
140
- load
117
+ load_from_jasondb
141
118
  else
142
119
  @__jason_state = :ghost
143
120
  end
@@ -148,54 +125,13 @@ module Medea
148
125
  end
149
126
  end
150
127
 
151
- def jason_key
152
- #Generate a random UUID for this object.
153
- #since jason urls must start with a letter, we'll use the first letter of the class name
154
- @__id ||= "#{self.class.name[0].chr.downcase}#{UUIDTools::UUID::random_create.to_s}"
155
- end
156
-
157
128
  def to_s
158
129
  jason_key
159
130
  end
160
131
 
161
- def jason_state
162
- @__jason_state
163
- end
164
-
165
- def jason_etag
166
- @__jason_etag ||= ""
167
- end
168
-
169
- def jason_parent
170
- @__jason_parent ||= nil
171
- if @__jason_parent == nil && @__jason_parent_key
172
- #key is set but parent not? load the parent
173
- @__jason_parent = JasonObject.resolve @__jason_parent_key
174
- end
175
- @__jason_parent
176
- end
177
-
178
- def jason_parent= parent
179
- @__jason_parent = parent
180
- @__jason_parent_key = parent.jason_key
181
- end
182
-
183
- def jason_parent_key
184
- @__jason_parent_key ||= nil
185
- end
186
-
187
- def jason_parent_key= value
188
- @__jason_parent_key = value
189
- #reset the parent here?
190
- @__jason_parent = nil
191
- end
192
-
193
- def jason_parent_list
194
- @__jason_parent_list ||= nil
195
- end
196
-
197
- def jason_parent_list= value
198
- @__jason_parent_list = value
132
+ #converts the data hash (that is, @__jason_data) to JSON format
133
+ def serialise
134
+ JSON.generate(@__jason_data)
199
135
  end
200
136
 
201
137
  #object persistence methods
@@ -217,61 +153,26 @@ module Medea
217
153
  return false
218
154
  end
219
155
  end
156
+
220
157
  def save!
221
- #no changes? no save!
222
- return if @__jason_state == :stale or @__jason_state == :ghost
223
-
224
- persist_changes :post
225
- end
226
-
227
- def delete! cascade=false
228
- #TODO: Put this into some kind of async method or have JasonDB able to set flags on many records at once
229
- #This will be REALLY REALLY slowww!
230
- if cascade && (self.class.class_variable_defined? :@@lists)
231
- @@lists.keys.each do |list_name|
232
- #for each list that I have
233
- list = send(list_name)
234
- list.each do |item|
235
- #remove each item from the list, deleting it if possible
236
- list.remove! item, true
158
+ @attachments.each do |k, v|
159
+ if v
160
+ v.save!
237
161
  end
238
162
  end
239
- end
240
- persist_changes :delete
241
- end
242
163
 
243
- #end object persistence
164
+ #no changes? no save!
165
+ return if @__jason_state == :stale or @__jason_state == :ghost
244
166
 
245
- #converts the data hash (that is, @__jason_data) to JSON format
246
- def to_json
247
- JSON.generate(@__jason_data)
167
+ persist_changes :post
248
168
  end
249
169
 
250
- private
251
-
252
- #fetches the data from the JasonDB
253
- def load
254
- #because this object might be owned by another, we need to search by key.
255
- #not passing a format to the query is a shortcut to getting just the object.
256
- url = "#{JasonDB::db_auth_url}@0.content?"
257
- params = [
258
- "VERSION0",
259
- "FILTER=HTTP_X_KEY:#{self.jason_key}",
260
- "FILTER=HTTP_X_CLASS:#{self.class.name}"
261
- ]
262
-
263
- url << params.join("&")
264
- #url = "#{JasonDB::db_auth_url}#{self.class.name}/#{self.jason_key}"
265
-
266
- #puts " = Retrieving #{self.class.name} at #{url}"
267
- response = RestClient.get url
268
- @__jason_data = JSON.parse response
269
- @__jason_etag = response.headers[:etag]
270
- @__jason_state = :stale
170
+ def to_url
171
+ "#{JasonDB::db_auth_url}#{self.class.name}/#{self.jason_key}"
271
172
  end
272
173
 
273
174
  def persist_changes method = :post
274
- payload = self.to_json
175
+ payload = self.serialise
275
176
 
276
177
  post_headers = {
277
178
  :content_type => 'application/json',
@@ -290,7 +191,7 @@ module Medea
290
191
  post_headers["X-PARENT"] = self.jason_parent.jason_key if self.jason_parent
291
192
  post_headers["X-LIST"] = self.jason_parent_list if self.jason_parent_list
292
193
 
293
- url = JasonDB::db_auth_url + self.class.name + "/" + self.jason_key
194
+ url = to_url()
294
195
 
295
196
  #puts "Saving to #{url}"
296
197
  if method == :post
@@ -313,5 +214,6 @@ module Medea
313
214
  @__jason_state = :stale
314
215
  end
315
216
 
217
+ #end object persistence
316
218
  end
317
219
  end
@@ -28,6 +28,14 @@ module JasonObjectMetaProperties
28
28
  list_class.owned = true
29
29
  end
30
30
 
31
+ def has_attachment attachment_name
32
+ attachments = []
33
+ attachments = self.send(:class_variable_get, :@@attachments) if self.class_variable_defined? :@@attachments
34
+ attachments << attachment_name
35
+ attachments.uniq!
36
+ self.send(:class_variable_set, "@@attachments", attachments)
37
+ end
38
+
31
39
  def key_field field_name
32
40
  #this field must be present to save, and it must be unique
33
41
  self.send(:class_variable_set, :@@key_field, field_name)
data/lib/medea/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Medea
2
- VERSION = "0.5.4"
2
+ VERSION = "0.6.0"
3
3
 
4
4
  #When the templates are changed, this version should be incremented
5
5
  #This version is used when uploading/updating the templates
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Jason Blob" do
4
+
5
+ class Update < Medea::JasonObject
6
+ has_attachment :avatar
7
+ end
8
+
9
+ before :each do
10
+ @update = Update.new
11
+ end
12
+
13
+ after :each do
14
+ @update.delete!
15
+ end
16
+
17
+ it "should be persisted" do
18
+ @update.avatar = "Here's some text!"
19
+ @update.save!
20
+ u2 = Update.get_by_key(@update.jason_key)
21
+ u2.avatar.should eq("Here's some text!")
22
+ u2.avatar.size.should eq("Here's some text!".size)
23
+ end
24
+
25
+ it "should work for larger images" do
26
+ f = File.new("./spec/test.jpg", "r")
27
+ @update.avatar = f
28
+ @update.save!
29
+ Update.get_by_key(@update.jason_key).avatar.size.should eq(f.size)
30
+ end
31
+ end
data/spec/test.jpg ADDED
Binary file
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
8
- - 4
9
- version: 0.5.4
7
+ - 6
8
+ - 0
9
+ version: 0.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Michael Jensen
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-01-19 00:00:00 +11:00
17
+ date: 2011-02-01 00:00:00 +11:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -79,6 +79,7 @@ extensions: []
79
79
  extra_rdoc_files: []
80
80
 
81
81
  files:
82
+ - .rvmrc
82
83
  - Gemfile
83
84
  - README
84
85
  - Rakefile
@@ -88,6 +89,8 @@ files:
88
89
  - lib/medea/active_model_methods.rb
89
90
  - lib/medea/dummy_logger.rb
90
91
  - lib/medea/inheritable_attributes.rb
92
+ - lib/medea/jason_base.rb
93
+ - lib/medea/jason_blob.rb
91
94
  - lib/medea/jasondb.rb
92
95
  - lib/medea/jasondeferredquery.rb
93
96
  - lib/medea/jasonlistproperty.rb
@@ -99,10 +102,12 @@ files:
99
102
  - lib/medea/version.rb
100
103
  - medea.gemspec
101
104
  - spec/deferred_query_spec.rb
105
+ - spec/jason_blob_spec.rb
102
106
  - spec/jason_object_spec.rb
103
107
  - spec/list_properties_spec.rb
104
108
  - spec/medea_spec.rb
105
109
  - spec/spec_helper.rb
110
+ - spec/test.jpg
106
111
  has_rdoc: true
107
112
  homepage: ""
108
113
  licenses: []