Class: Clouder::Entity

Inherits:
OpenStruct
  • Object
show all
Defined in:
lib/clouder/entity.rb

Overview

This is the base class to be used when accessing resources in a CloudKit server. It contains all the basic persistence methods and attributes. See the entity_spec.rb file for sample usages.

A Note class should be declared as follows:

class Note < Clouder::Entity
  uri "http://localhost:9292/notes"
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = {}) ⇒ Entity

Constructs a new, unsaved object. If attributes are passed in a hash the new object is initialized with the corresponding attributes and values.

note = Note.new # => new, empty
note = Note.new(:text => "Ready note", :author => "Myself") # => new, with attributes set


144
145
146
147
# File 'lib/clouder/entity.rb', line 144

def initialize(attributes = {})
  @id, @etag, @last_modified, @deleted = nil
  build(attributes)
end

Instance Attribute Details

#etagObject (readonly)

ETag, a UUID



122
123
124
# File 'lib/clouder/entity.rb', line 122

def etag
  @etag
end

#idObject (readonly)

Unique object id - not an URI, just a UUID



119
120
121
# File 'lib/clouder/entity.rb', line 119

def id
  @id
end

#last_modifiedObject (readonly)

Last modified timestamp



125
126
127
# File 'lib/clouder/entity.rb', line 125

def last_modified
  @last_modified
end

Class Method Details

.all(options = {}) ⇒ Object

If options is nil, returns an array containing all URIs of existing objects for this class. Sort order is from the most recent to the oldest.

For other results, options can be:

:resolved

If true, returns full objects instead of URIs.

:offset

A positive integer, starting at 0, offsetting the result.

:limit

A positive integer, limiting the results.

All options can be combined.

Note.all 
Note.all(:resolved => true)
Note.all(:offset => 20, :limit => 10)
Note.all(:resolved => true, :limit => 20, :offset => 10)


41
42
43
44
45
# File 'lib/clouder/entity.rb', line 41

def all(options = {})
  uri = options[:resolved] ? File.join(@uri, "_resolved") : @uri
  result = Rest.get(Rest.paramify_url(uri, options))
  options[:resolved] ? result["documents"].map { |d| new(d) } : result["uris"]
end

.create(hsh = {}) ⇒ Object

Creates and saves an object with the attributes and values passed as a hash. Returns the newly created object.

note = Note.create(:text => "My note", :author => "John Doe")


51
52
53
54
55
# File 'lib/clouder/entity.rb', line 51

def create(hsh = {})
  obj = self.new(hsh || {})
  obj.save   
  obj
end

.get(id_or_uri) ⇒ Object

Retrieves an existing object with the given id or uri. Returns nil if the object is not found.

note = Note.get("ce655c90-cf09-012b-cd41-0016cb91f13d")


61
62
63
64
65
66
67
68
69
# File 'lib/clouder/entity.rb', line 61

def get(id_or_uri)
  uri = uri_from_id(id_or_uri)
  document = Rest.get(uri)
  new({'uri' => uri, 'etag' => document.headers[:etag], 
      'last_modified' => document.headers[:last_modified],
      'document' => document })
rescue RestClient::ResourceNotFound
  nil
end

.id_from_uri(uri) ⇒ Object

Extracts object ids from absolute or partial URIs.

Note.id_from_uri("http://localhost:9292/notes/ce655c90-cf09-012b-cd41-0016cb91f13d") 
=> "ce655c90-cf09-012b-cd41-0016cb91f13d"


84
85
86
87
88
89
90
91
92
# File 'lib/clouder/entity.rb', line 84

def id_from_uri(uri)
  id = URI.parse(uri)
  # /notes/abc
  if id.path[0,1] == "/"
    id.path.split("/")[2]
  else
    id.to_s
  end
end

.options(uri = self.uri) ⇒ Object

Returns an array of allowed HTTP methods to be requested at uri. If uri is nil, the class URI is queried.

Note.options # => [ "GET", "HEAD", "POST", "OPTIONS" ]


75
76
77
78
# File 'lib/clouder/entity.rb', line 75

def options(uri = self.uri)
  doc = Rest.custom(:options, uri)
  doc["allow"].to_s.split(",").map { |s| s.strip }
end

.uri(address = nil) ⇒ Object

If address is passed, sets the URI for the target class. If nothing is passed, returns the current URI for the target class.

Note.uri "http://localhost:8989/notes" # changes the old URI
Note.uri # => "http://localhost:8989/notes"


22
23
24
# File 'lib/clouder/entity.rb', line 22

def uri(address = nil)
  address ? @uri = address : @uri
end

.uri_from_id(id) ⇒ Object

Composes a full URI from an object id or partial URI.

Note.uri_from_id("/notes/ce655c90-cf09-012b-cd41-0016cb91f13d") 
=> "http://localhost:9292/notes/ce655c90-cf09-012b-cd41-0016cb91f13d" 

Note.uri_from_id("ce655c90-cf09-012b-cd41-0016cb91f13d") 
=> "http://localhost:9292/notes/ce655c90-cf09-012b-cd41-0016cb91f13d"


101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/clouder/entity.rb', line 101

def uri_from_id(id)
  url = URI.parse(id)
  if url.absolute?
    url.to_s
  else
    # /notes/1234
    if url.path[0,1] == "/"
      (URI.parse(self.uri) + url).to_s
    # 1234
    else
      File.join("#{self.uri}", id)
    end
  end
end

Instance Method Details

#deleteObject

Deletes an existing object. Its etag should match the etag in the database, otherwise the operation fails.

Returns true if save was successful, false otherwise.

note = Note.new("ce655c90-cf09-012b-cd41-0016cb91f13d")
note.delete # => true


172
173
174
175
176
177
178
179
# File 'lib/clouder/entity.rb', line 172

def delete
  Rest.delete uri, "If-Match" => etag
  @deleted = true
  freeze
  true
rescue RestClient::RequestFailed
  false
end

#deleted?Boolean

true if object was deleted, false otherwise.

Returns:

  • (Boolean)


187
# File 'lib/clouder/entity.rb', line 187

def deleted?; @deleted end

#inspectObject



224
225
226
# File 'lib/clouder/entity.rb', line 224

def inspect
  "#<#{self.class.name} uri=#{uri}, id=#{id}, etag=#{@etag}, last_modified=#{@last_modified}, #{@table.inspect}>"
end

#new?Boolean

true if object was not saved yet, false otherwise.

Returns:

  • (Boolean)


182
183
184
# File 'lib/clouder/entity.rb', line 182

def new?
  @uri == nil and @etag == nil and @last_modified == nil
end

#optionsObject

Returns an array of allowed HTTP methods to be requested for this object.

note = Note.new("ce655c90-cf09-012b-cd41-0016cb91f13d") # => existing object
note.options # => [ "DELETE", "GET", "HEAD", "PUT", "OPTIONS" ]


220
221
222
# File 'lib/clouder/entity.rb', line 220

def options
  self.class.options(uri)
end

#pathObject

Partial URI for the object (without the protocol, hostname)

=> "/notes/ce655c90-cf09-012b-cd41-0016cb91f13d"


135
136
137
# File 'lib/clouder/entity.rb', line 135

def path
  URI.parse(uri).path
end

#saveObject

Saves a new or existing object. If the object already exists, then its etag should match the etag in the database, otherwise the operation fails.

Returns true if save was successful, false otherwise.

note = Note.new(:text => "Ready note", :author => "Myself")
note.save # => true


156
157
158
159
160
161
162
163
164
# File 'lib/clouder/entity.rb', line 156

def save
  result = new? ? Rest.post(collection_uri, @table) : Rest.put(uri, @table, "If-Match" => etag)
  @id, @etag, @last_modified = result.values_at("uri", "etag", "last_modified")
  @id = self.class.id_from_uri(@id)
  @last_modified = Time.parse(@last_modified)
  true
rescue RestClient::RequestFailed
  false
end

#uriObject

Full URI for the object

=> "http://localhost:9292/notes/ce655c90-cf09-012b-cd41-0016cb91f13d"


129
130
131
# File 'lib/clouder/entity.rb', line 129

def uri
  @uri ||= self.class.uri_from_id(id) if id
end

#versions(options = {}) ⇒ Object

Retrieves older versions of the object. Sort order is from the current version to the oldest one. The options parameter works as in all.

note = Note.new("ce655c90-cf09-012b-cd41-0016cb91f13d")
older_versions = note.versions(:resolved, :limit => 3)


195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/clouder/entity.rb', line 195

def versions(options = {})
  if uri
    url = File.join(uri, "versions")
    if options[:etag]
      url = File.join(url, options[:etag])
      self.class.get(url)
    else
      url = options[:resolved] ? File.join(url, "_resolved") : url
      result = Rest.get(Rest.paramify_url(url, options))
      if options[:resolved]
        result["documents"].map { |d| self.class.new(d) }
      else
        result["uris"]
      end
    end
  else
    []
  end
end