Class CloudKit::Resource

  1. lib/cloudkit/store/resource.rb
Parent: Object

A CloudKit::Resource represents a “resource” in the REST/HTTP sense of the word. It encapsulates a JSON document and its metadata such as its URI, ETag, Last-Modified date, remote user, and historical versions.

Attributes

etag [R]
json [R]
last_modified [R]
remote_user [R]
uri [R]

Public class methods

all (spec={})

Find all resources with the given properties. Expects a hash specifying the search parameters. Returns an array of Resources.

[show source]
     # File lib/cloudkit/store/resource.rb, line 144
144:     def self.all(spec={})
145:       CloudKit.storage_adapter.query { |q|
146:         spec.keys.each { |k|
147:           q.add_condition(k.to_s, :eql, escape(spec[k]))
148:         }
149:       }.reverse.map { |hash| build_from_hash(hash) }
150:     end
create (uri, json, remote_user=nil)

Create a new resource. Intializes and saves in one step.

Parameters

  • uri - A CloudKit::URI for the resource or its parent collection.
  • json - A string representing a valid JSON object.
  • remote_user - Optional. The URI for the user creating the resource.
[show source]
     # File lib/cloudkit/store/resource.rb, line 130
130:     def self.create(uri, json, remote_user=nil)
131:       resource = new(uri, json, remote_user)
132:       resource.save
133:       resource
134:     end
current (spec={})

Find all current resources with the given properties. Expectes a hash specifying the search parameters. Returns an array of Resources.

[show source]
     # File lib/cloudkit/store/resource.rb, line 138
138:     def self.current(spec={})
139:       all({:deleted => false, :archived => false}.merge(spec))
140:     end
first (spec)

Find the first matching resource or nil. Expects a hash specifying the search parameters.

[show source]
     # File lib/cloudkit/store/resource.rb, line 154
154:     def self.first(spec)
155:       all(spec)[0]
156:     end
new (uri, json, remote_user=nil, options={})

Initialize a new instance of a resource.

Parameters

  • uri - A CloudKit::URI for the resource or its parent collection.
  • json - A string representing a valid JSON object.
  • remote_user - Optional. The URI for the user creating the resource.
  • options - Optional. A hash of other internal properties to set, mostly for internal use.
[show source]
    # File lib/cloudkit/store/resource.rb, line 17
17:     def initialize(uri, json, remote_user=nil, options={})
18:       load_from_options(options.merge(
19:         :uri         => uri,
20:         :json        => json,
21:         :remote_user => remote_user))
22:     end

Protected class methods

build_from_hash (data)
[show source]
     # File lib/cloudkit/store/resource.rb, line 182
182:     def self.build_from_hash(data)
183:       new(
184:         data['uri'],
185:         data['json'],
186:         data['remote_user'],
187:         {}.filter_merge!(
188:           :etag                 => data['etag'],
189:           :last_modified        => data['last_modified'],
190:           :resource_reference   => data['resource_reference'],
191:           :collection_reference => data['collection_reference'],
192:           :id                   => data[:pk],
193:           :deleted              => data['deleted'],
194:           :archived             => data['archived']))
195:     end
escape (value)
[show source]
     # File lib/cloudkit/store/resource.rb, line 216
216:     def self.escape(value)
217:       case value
218:       when TrueClass
219:         "true"
220:       when FalseClass
221:         "false"
222:       when NilClass
223:         "null"
224:       when Fixnum, Bignum, Float
225:         value.to_s
226:       when Array, Hash
227:         JSON.generate(value) # temporary bug fix prior to JSONQuery support
228:       else
229:         value
230:       end
231:     end
unescape (value)
[show source]
     # File lib/cloudkit/store/resource.rb, line 237
237:     def self.unescape(value)
238:       case value
239:       when "true"
240:         true
241:       when "false"
242:         false
243:       when "null"
244:         nil
245:       else
246:         value
247:       end
248:     end
wrap_uri (uri)
[show source]
     # File lib/cloudkit/store/resource.rb, line 201
201:     def self.wrap_uri(uri)
202:       case uri
203:         when CloudKit::URI; uri
204:         else CloudKit::URI.new(uri)
205:       end
206:     end

Public instance methods

archived? ()

Returns true if the resource is archived i.e. not the current version.

[show source]
     # File lib/cloudkit/store/resource.rb, line 110
110:     def archived?
111:       @archived
112:     end
current? ()

Returns true if the resource is not archived and not deleted.

[show source]
     # File lib/cloudkit/store/resource.rb, line 115
115:     def current?
116:       !@deleted && !@archived
117:     end
delete ()

Delete the given resource. This is a soft delete, archiving the previous resource and inserting a deleted resource placeholder at the old URI. Raises HistoricalIntegrityViolation for attempts to delete resources that are not current.

[show source]
    # File lib/cloudkit/store/resource.rb, line 66
66:     def delete
67:       raise HistoricalIntegrityViolation unless current?
68:       transaction do
69:         original_uri = @uri
70:         record = CloudKit.storage_adapter[@id]
71:         record['uri'] = "#{@uri.string}/versions/#{@etag}"
72:         record['archived'] = escape(true)
73:         @uri = wrap_uri(record['uri'])
74:         @archived = unescape(record['archived'])
75:         CloudKit.storage_adapter[@id] = record
76:         self.class.new(original_uri, @json, @remote_user, {:deleted => true}).save
77:       end
78:       reload
79:     end
deleted? ()

Returns true if the resource has been deleted.

[show source]
     # File lib/cloudkit/store/resource.rb, line 105
105:     def deleted?
106:       @deleted
107:     end
parsed_json ()

Returns and caches the parsed JSON representation for this resource.

[show source]
     # File lib/cloudkit/store/resource.rb, line 120
120:     def parsed_json
121:       @parsed_json ||= JSON.parse(@json)
122:     end
previous_version ()

Returns the most recent previous version of the given resource.

[show source]
     # File lib/cloudkit/store/resource.rb, line 100
100:     def previous_version
101:       @previous_version ||= previous_versions[0]
102:     end
previous_versions ()

Returns all previous versions of a resource, reverse ordered by Last-Modified date.

[show source]
    # File lib/cloudkit/store/resource.rb, line 95
95:     def previous_versions
96:       @previous_versions ||= versions[1..-1] rescue []
97:     end
save ()

Save the resource. If this is a new resource with only a resource collection URI, its full URI will be generated. ETags and Last-Modified dates are also generated upon saving. No manual reloads are required.

[show source]
    # File lib/cloudkit/store/resource.rb, line 27
27:     def save
28:       @id ||= '%064d' % CloudKit.storage_adapter.generate_unique_id
29:       @etag = UUID.generate unless @deleted
30:       @last_modified = Time.now.httpdate
31: 
32:       CloudKit.storage_adapter[@id] = {
33:         'uri'                  => @uri.cannonical_uri_string,
34:         'etag'                 => escape(@etag),
35:         'last_modified'        => @last_modified,
36:         'json'                 => @json,
37:         'deleted'              => escape(@deleted),
38:         'archived'             => escape(@archived),
39:         'remote_user'          => escape(@remote_user),
40:         'collection_reference' => @collection_reference ||= @uri.collection_uri_fragment,
41:         'resource_reference'   => @resource_reference ||= @uri.cannonical_uri_string
42:       }.merge(escape_values(parsed_json))
43:       reload
44:     end
update (json, remote_user=nil)

Update the json and optionally the remote_user for the current resource. Automatically archives the previous version, generating new ETag and Last-Modified dates. Raises HistoricalIntegrityViolation for attempts to modify resources that are not current.

[show source]
    # File lib/cloudkit/store/resource.rb, line 50
50:     def update(json, remote_user=nil)
51:       raise HistoricalIntegrityViolation unless current?
52:       transaction do
53:         record = CloudKit.storage_adapter[@id]
54:         record['uri'] = "#{@uri.string}/versions/#{@etag}"
55:         record['archived'] = escape(true)
56:         CloudKit.storage_adapter[@id] = record
57:         self.class.create(@uri, json, remote_user || @remote_user)
58:       end
59:       reload
60:     end
versions ()

Returns all versions of the given resource, reverse ordered by Last-Modified date, including the current version of the resource.

[show source]
    # File lib/cloudkit/store/resource.rb, line 83
83:     def versions
84:       # TODO make this a collection proxy, only loading the first, then the
85:       # rest as needed during iteration (possibly in chunks)
86:       return nil if @archived
87:       @versions ||= [self].concat(CloudKit.storage_adapter.query { |q|
88:         q.add_condition('resource_reference', :eql, @resource_reference)
89:         q.add_condition('archived', :eql, 'true')
90:       }.reverse.map { |hash| self.class.build_from_hash(hash) })
91:     end

Protected instance methods

escape (value)
[show source]
     # File lib/cloudkit/store/resource.rb, line 212
212:     def escape(value)
213:       self.class.escape(value)
214:     end
escape_values (hash)
[show source]
     # File lib/cloudkit/store/resource.rb, line 254
254:     def escape_values(hash)
255:       hash.inject({}) { |memo, pair| memo.merge({pair[0] => escape(pair[1])}) }
256:     end
load_from_options (opts)
[show source]
     # File lib/cloudkit/store/resource.rb, line 160
160:     def load_from_options(opts)
161:       options = symbolize_keys(opts)
162: 
163:       @uri                  = wrap_uri(options[:uri])
164:       @json                 = options[:json]
165:       @last_modified        = options[:last_modified]
166:       @resource_reference   = options[:resource_reference]
167:       @collection_reference = options[:collection_reference]
168:       @id                   = options[:id] || options[:pk] || nil
169:       @etag                 = unescape(options[:etag])
170:       @remote_user          = unescape(options[:remote_user])
171:       @archived             = unescape(options[:archived]) || false
172:       @deleted              = unescape(options[:deleted]) || false
173:     end
reload ()
[show source]
     # File lib/cloudkit/store/resource.rb, line 175
175:     def reload
176:       result = CloudKit.storage_adapter.query { |q|
177:         q.add_condition('uri', :eql, @resource_reference)
178:       }
179:       load_from_options(result[0])
180:     end
scope (key)
[show source]
     # File lib/cloudkit/store/resource.rb, line 208
208:     def scope(key)
209:       "#{@id}:#{key}"
210:     end
symbolize_keys (hash)
[show source]
     # File lib/cloudkit/store/resource.rb, line 250
250:     def symbolize_keys(hash)
251:       hash.inject({}) { |memo, pair| memo.merge({pair[0].to_sym => pair[1]}) }
252:     end
transaction () {|| ...}
[show source]
     # File lib/cloudkit/store/resource.rb, line 258
258:     def transaction
259:       open('.lock', 'w+') do |f|
260:         f.flock(File::LOCK_EX)
261:         begin 
262:           yield
263:         ensure
264:           f.flock(File::LOCK_UN)
265:         end
266:       end
267:     end
unescape (value)
[show source]
     # File lib/cloudkit/store/resource.rb, line 233
233:     def unescape(value)
234:       self.class.unescape(value)
235:     end
wrap_uri (uri)
[show source]
     # File lib/cloudkit/store/resource.rb, line 197
197:     def wrap_uri(uri)
198:       self.class.wrap_uri(uri)
199:     end