medea 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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: []