Class CloudKit::Store

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

A functional storage interface with HTTP semantics and pluggable adapters.

Included modules

  1. ResponseHelpers
  2. CloudKit::Util

Public class methods

new (options)

Initialize a new Store. All resources in a Store are automatically versioned.

Options

  • :collections - Array of resource collections to manage.

Example

store = CloudKit::Store.new(:collections => [:foos, :bars])

See also: Response

[show source]
    # File lib/cloudkit/store.rb, line 19
19:     def initialize(options)
20:       CloudKit.setup_storage_adapter unless CloudKit.storage_adapter
21:       @collections = options[:collections]
22:     end

Public instance methods

delete (uri, options={})

Delete the resource specified by the URI. Requires the :etag option.

[show source]
     # File lib/cloudkit/store.rb, line 109
109:     def delete(uri, options={})
110:       methods = methods_for_uri(uri)
111:       return status_405(methods) unless methods.include?('DELETE')
112:       return invalid_entity_type unless @collections.include?(uri.collection_type)
113:       return etag_required       unless options[:etag]
114:       resource = CloudKit::Resource.first(options.excluding(:etag).merge(:uri => uri.string))
115:       return status_404 unless (resource && (resource.remote_user == options[:remote_user]))
116:       return status_410 if resource.deleted?
117:       return status_412 if resource.etag != options[:etag]
118: 
119:       resource.delete
120:       archived_resource = resource.previous_version
121:       return json_meta_response(archived_resource.uri.string, archived_resource.etag, resource.last_modified)
122:     end
get (uri, options={})

Retrieve a resource or collection of resources based on a URI.

Parameters

  • uri - URI of the resource or collection to retrieve.
  • options - See below.

Options

  • :remote_user - Optional. Scopes the dataset if provided.
  • :limit - Optional. Default is unlimited. Limit the number of records returned by a collection request.
  • :offset - Optional. Start the list of resources in a collection at offset (0-based).
  • :any - Optional. Not a literal “:any”, but any key or keys that are top level JSON keys. This is a starting point for future JSONPath/JSONQuery support.

URI Types

/cloudkit-meta
/{collection}
/{collection}/_resolved
/{collection}/{uuid}
/{collection}/{uuid}/versions
/{collection}/{uuid}/versions/_resolved
/{collection}/{uuid}/versions/{etag}

Examples

get('/cloudkit-meta')
get('/foos')
get('/foos', :remote_user => 'coltrane')
get('/foos', :limit => 100, :offset => 200)
get('/foos/123')
get('/foos/123/versions')
get('/foos/123/versions/abc')

See also: REST API

[show source]
    # File lib/cloudkit/store.rb, line 56
56:     def get(uri, options={})
57:       return invalid_entity_type                        if !valid_collection_type?(uri.collection_type)
58:       return meta                                       if uri.meta_uri?
59:       return resource_collection(uri, options)          if uri.resource_collection_uri?
60:       return resolved_resource_collection(uri, options) if uri.resolved_resource_collection_uri?
61:       return resource(uri, options)                     if uri.resource_uri?
62:       return version_collection(uri, options)           if uri.version_collection_uri?
63:       return resolved_version_collection(uri, options)  if uri.resolved_version_collection_uri?
64:       return resource_version(uri, options)             if uri.resource_version_uri?
65:       status_404
66:     end
head (uri, options={})

Retrieve the same items as the get method, minus the content/body. Using this method on a single resource URI performs a slight optimization due to the way CloudKit stores its ETags and Last-Modified information on write.

[show source]
    # File lib/cloudkit/store.rb, line 72
72:     def head(uri, options={})
73:       return invalid_entity_type unless @collections.include?(uri.collection_type)
74:       if uri.resource_uri? || uri.resource_version_uri?
75:         # ETag and Last-Modified are already stored for single items, so a slight
76:         # optimization can be made for HEAD requests.
77:         result = CloudKit::Resource.first(options.merge(:uri => uri.string))
78:         return status_404.head unless result
79:         return status_410.head if result.deleted?
80:         return response(200, '', result.etag, result.last_modified)
81:       else
82:         get(uri, options).head
83:       end
84:     end
http_methods ()

Return the list of HTTP methods supported by this Store.

[show source]
     # File lib/cloudkit/store.rb, line 182
182:     def http_methods
183:       ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS']
184:     end
implements? (http_method)

Return true if this store implements a given HTTP method.

[show source]
     # File lib/cloudkit/store.rb, line 177
177:     def implements?(http_method)
178:       http_methods.include?(http_method.upcase)
179:     end
meta_methods ()

Return the list of methods allowed for the cloudkit-meta URI.

[show source]
     # File lib/cloudkit/store.rb, line 142
142:     def meta_methods
143:       @meta_methods ||= http_methods.excluding('POST', 'PUT', 'DELETE')
144:     end
methods_for_uri (uri)

Return a list of allowed methods for a given URI.

[show source]
     # File lib/cloudkit/store.rb, line 131
131:     def methods_for_uri(uri)
132:       return meta_methods                         if uri.meta_uri?
133:       return resource_collection_methods          if uri.resource_collection_uri?
134:       return resolved_resource_collection_methods if uri.resolved_resource_collection_uri?
135:       return resource_methods                     if uri.resource_uri?
136:       return version_collection_methods           if uri.version_collection_uri?
137:       return resolved_version_collection_methods  if uri.resolved_version_collection_uri?
138:       return resource_version_methods             if uri.resource_version_uri?
139:     end
options (uri)

Build a response containing the allowed methods for a given URI.

[show source]
     # File lib/cloudkit/store.rb, line 125
125:     def options(uri)
126:       methods = methods_for_uri(uri)
127:       allow(methods)
128:     end
post (uri, options={})

Create a resource in a given collection.

[show source]
     # File lib/cloudkit/store.rb, line 100
100:     def post(uri, options={})
101:       methods = methods_for_uri(uri)
102:       return status_405(methods) unless methods.include?('POST')
103:       return invalid_entity_type unless @collections.include?(uri.collection_type)
104:       return data_required       unless options[:json]
105:       create_resource(uri, options)
106:     end
put (uri, options={})

Update or create a resource at the specified URI. If the resource already exists, an :etag option is required.

[show source]
    # File lib/cloudkit/store.rb, line 88
88:     def put(uri, options={})
89:       methods = methods_for_uri(uri)
90:       return status_405(methods) unless methods.include?('PUT')
91:       return invalid_entity_type unless @collections.include?(uri.collection_type)
92:       return data_required       unless options[:json]
93:       current_resource = resource(uri, options.excluding(:json, :etag, :remote_user))
94:       return update_resource(uri, options) if current_resource.status == 200
95:       return current_resource if current_resource.status == 410
96:       create_resource(uri, options)
97:     end
resolve_uris (uris)

Return an array containing the response for each URI in a list.

[show source]
     # File lib/cloudkit/store.rb, line 187
187:     def resolve_uris(uris) # TODO - remove if no longer needed
188:       result = []
189:       uris.each do |uri|
190:         result << get(uri)
191:       end
192:       result
193:     end
resolved_resource_collection_methods ()

Return the list of methods allowed on a resolved resource collection.

[show source]
     # File lib/cloudkit/store.rb, line 152
152:     def resolved_resource_collection_methods
153:       @resolved_resource_collection_methods ||= http_methods.excluding('POST', 'PUT', 'DELETE')
154:     end
resolved_version_collection_methods ()

Return the list of methods allowed on a resolved version history collection.

[show source]
     # File lib/cloudkit/store.rb, line 167
167:     def resolved_version_collection_methods
168:       @resolved_version_collection_methods ||= http_methods.excluding('POST', 'PUT', 'DELETE')
169:     end
resource_collection_methods ()

Return the list of methods allowed for a resource collection.

[show source]
     # File lib/cloudkit/store.rb, line 147
147:     def resource_collection_methods
148:       @resource_collection_methods ||= http_methods.excluding('PUT', 'DELETE')
149:     end
resource_methods ()

Return the list of methods allowed on an individual resource.

[show source]
     # File lib/cloudkit/store.rb, line 157
157:     def resource_methods
158:       @resource_methods ||= http_methods.excluding('POST')
159:     end
resource_version_methods ()

Return the list of methods allowed on a resource version.

[show source]
     # File lib/cloudkit/store.rb, line 172
172:     def resource_version_methods
173:       @resource_version_methods ||= http_methods.excluding('POST', 'PUT', 'DELETE')
174:     end
storage_adapter ()
[show source]
     # File lib/cloudkit/store.rb, line 195
195:     def storage_adapter
196:       CloudKit.storage_adapter
197:     end
version ()

Return the version number of this Store.

[show source]
     # File lib/cloudkit/store.rb, line 200
200:     def version; 1; end
version_collection_methods ()

Return the list of methods allowed on a version history collection.

[show source]
     # File lib/cloudkit/store.rb, line 162
162:     def version_collection_methods
163:       @version_collection_methods ||= http_methods.excluding('POST', 'PUT', 'DELETE')
164:     end

Protected instance methods

build_etag (data)

Build an ETag for a collection. ETags are generated on write as an optimization for GETs. This method is used for collections of resources where the optimization is not practical.

[show source]
     # File lib/cloudkit/store.rb, line 340
340:     def build_etag(data)
341:       Digest::MD5.hexdigest(data.to_s)
342:     end
bundle_collection_result (uri, options, result)

Bundle a collection of results as a list of URIs for the response.

[show source]
     # File lib/cloudkit/store.rb, line 296
296:     def bundle_collection_result(uri, options, result)
297:       total  = result.size
298:       offset = options[:offset].try(:to_i) || 0
299:       max    = options[:limit] ? offset + options[:limit].to_i : total
300:       list   = result.to_a[offset...max].map{|r| r.uri}
301:       json   = uri_list(list, total, offset)
302:       last_modified = result.first.try(:last_modified) if result.any?
303:       response(200, json, build_etag(json), last_modified)
304:     end
bundle_resolved_collection_result (uri, options, result)

Bundle a collection of results as a list of documents and the associated metadata (last_modified, uri, etag) that would have accompanied a response to their singular request.

[show source]
     # File lib/cloudkit/store.rb, line 309
309:     def bundle_resolved_collection_result(uri, options, result)
310:       total  = result.size
311:       offset = options[:offset].try(:to_i) || 0
312:       max    = options[:limit] ? offset + options[:limit].to_i : total
313:       list   = result.to_a[offset...max]
314:       json   = resource_list(list, total, offset)
315:       last_modified = result.first.last_modified if result.any?
316:       response(200, json, build_etag(json), last_modified)
317:     end
create_resource (uri, options)

Create a resource at the specified URI.

[show source]
     # File lib/cloudkit/store.rb, line 277
277:     def create_resource(uri, options)
278:       JSON.parse(options[:json]) rescue (return status_422)
279:       resource = CloudKit::Resource.create(uri, options[:json], options[:remote_user])
280:       json_create_response(resource.uri.string, resource.etag, resource.last_modified)
281:     end
meta ()

Return the list of collections managed by this Store.

[show source]
     # File lib/cloudkit/store.rb, line 205
205:     def meta
206:       json = JSON.generate(:uris => @collections.map{|t| "/#{t}"})
207:       response(200, json, build_etag(json))
208:     end
resolved_resource_collection (uri, options)

Return all documents and their associated metadata for the given collection URI.

[show source]
     # File lib/cloudkit/store.rb, line 223
223:     def resolved_resource_collection(uri, options)
224:       result = CloudKit::Resource.current(
225:         options.excluding(:offset, :limit).merge(
226:           :collection_reference => uri.collection_uri_fragment))
227:       bundle_resolved_collection_result(uri, options, result)
228:     end
resolved_version_collection (uri, options)

Return all document versions and their associated metadata for a given resource including the current version. Sorted by Last-Modified date in descending order.

[show source]
     # File lib/cloudkit/store.rb, line 257
257:     def resolved_version_collection(uri, options)
258:       found = CloudKit::Resource.first(
259:         options.excluding(:offset, :limit).merge(
260:           :uri => uri.current_resource_uri))
261:       return status_404 unless found
262:       result = CloudKit::Resource.all(
263:         options.excluding(:offset, :limit).merge(
264:           :resource_reference => uri.current_resource_uri,
265:           :deleted            => false))
266:       bundle_resolved_collection_result(uri, options, result)
267:     end
resource (uri, options)

Return the resource for the given URI. Return 404 if not found or if protected and unauthorized, 410 if authorized but deleted.

[show source]
     # File lib/cloudkit/store.rb, line 232
232:     def resource(uri, options)
233:       if resource = CloudKit::Resource.first(options.merge!(:uri => uri.string))
234:         return status_410 if resource.deleted?
235:         return response(200, resource.json, resource.etag, resource.last_modified)
236:       end
237:       status_404
238:     end
resource_collection (uri, options)

Return a list of resource URIs for the given collection URI. Sorted by Last-Modified date in descending order.

[show source]
     # File lib/cloudkit/store.rb, line 212
212:     def resource_collection(uri, options)
213:       filter = options.excluding(:offset, :limit).merge(
214:         :deleted              => false,
215:         :collection_reference => uri.collection_uri_fragment,
216:         :archived             => false)
217:       result = CloudKit::Resource.current(filter)
218:       bundle_collection_result(uri.string, options, result)
219:     end
resource_list (list, total, offset)

Generate a JSON document list.

[show source]
     # File lib/cloudkit/store.rb, line 325
325:     def resource_list(list, total, offset)
326:       results = []
327:       list.each do |resource|
328:         results << {
329:           :uri           => resource.uri.string,
330:           :etag          => resource.etag,
331:           :last_modified => resource.last_modified,
332:           :document      => resource.json}
333:       end
334:       JSON.generate(:total => total, :offset => offset, :documents => results)
335:     end
resource_version (uri, options)

Return a specific version of a resource.

[show source]
     # File lib/cloudkit/store.rb, line 270
270:     def resource_version(uri, options)
271:       result = CloudKit::Resource.first(options.merge(:uri => uri.string))
272:       return status_404 unless result
273:       response(200, result.json, result.etag, result.last_modified)
274:     end
update_resource (uri, options)

Update the resource at the specified URI. Requires the :etag option.

[show source]
     # File lib/cloudkit/store.rb, line 284
284:     def update_resource(uri, options)
285:       JSON.parse(options[:json]) rescue (return status_422)
286:       resource = CloudKit::Resource.first(
287:         options.excluding(:json, :etag).merge(:uri => uri.string))
288:       return status_404    unless (resource && (resource.remote_user == options[:remote_user]))
289:       return etag_required unless options[:etag]
290:       return status_412    unless options[:etag] == resource.etag
291:       resource.update(options[:json])
292:       return json_meta_response(uri.string, resource.etag, resource.last_modified)
293:     end
uri_list (list, total, offset)

Generate a JSON URI list.

[show source]
     # File lib/cloudkit/store.rb, line 320
320:     def uri_list(list, total, offset)
321:       JSON.generate(:total => total, :offset => offset, :uris => list.map { |u| u.string })
322:     end
valid_collection_type? (collection_type)

Returns true if the collection type is valid for this Store.

[show source]
     # File lib/cloudkit/store.rb, line 345
345:     def valid_collection_type?(collection_type)
346:       @collections.include?(collection_type) || collection_type.to_s == 'cloudkit-meta'
347:     end
version_collection (uri, options)

Return a collection of URIs for all versions of a resource including the current version. Sorted by Last-Modified date in descending order.

[show source]
     # File lib/cloudkit/store.rb, line 242
242:     def version_collection(uri, options)
243:       found = CloudKit::Resource.first(
244:         options.excluding(:offset, :limit).merge(
245:           :uri => uri.current_resource_uri))
246:       return status_404 unless found
247:       result = CloudKit::Resource.all( # TODO - just use found.versions
248:         options.excluding(:offset, :limit).merge(
249:           :resource_reference => uri.current_resource_uri,
250:           :deleted            => false))
251:       bundle_collection_result(uri.string, options, result)
252:     end