Class: CouchPillow::Document

Inherits:
Object
  • Object
show all
Extended by:
Attributive
Defined in:
lib/couchpillow/document.rb

Constant Summary collapse

RESERVED_KEYS =
%i[_id _type _created_at _updated_at]
DEFAULT_TYPE =
"couchpillow".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Attributive

attribute, attributes, inherited

Constructor Details

#initialize(hash = {}, id = "#{self.class.doc_type}::#{SecureRandom.hex}") ⇒ Document

Returns a new instance of Document.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/couchpillow/document.rb', line 29

def initialize hash = {}, id = "#{self.class.doc_type}::#{SecureRandom.hex}"
  @data = self.class.symbolize(hash)

  @id = id
  time = Time.now.utc
  @data[:_created_at] ||= time
  @data[:_updated_at] = time

  @futures = []

  rename!
  whitelist!
  assign_defaults!
  auto_convert!
end

Instance Attribute Details

#idObject (readonly)

Returns the value of attribute id.



7
8
9
# File 'lib/couchpillow/document.rb', line 7

def id
  @id
end

Class Method Details

.db(conn) ⇒ Object

Set a DB connection. Overrides the default CouchPillow.db connection for the first time this method gets called. Subsequent calls will set secondary connections, which will only be used for write only.

Example:

db primary_db # use for both read and write
db backup_db1 # write only
db backup_db2 # write only


282
283
284
285
286
287
288
289
290
# File 'lib/couchpillow/document.rb', line 282

def self.db conn
  # set the primary db connection
  @primary_db ||= conn

  # insert as backup db connections
  if conn && @primary_db && conn != @primary_db
    secondary_dbs << conn
  end
end

.get(id) ⇒ Object

Get a Document given an id.

Returns:

  • nil if not found or Document is of a different type.



246
247
248
249
250
251
252
# File 'lib/couchpillow/document.rb', line 246

def self.get id
  result = default_db.get(id) and
    type = result[:_type] || result["_type"] and
    type == doc_type and
    new(result, id) or
    nil
end

.rename(from, to) ⇒ Object

Rename an existing key to a new key. This is invoked right after initialize.

Raises:

  • (ArgumentError)


258
259
260
261
262
263
# File 'lib/couchpillow/document.rb', line 258

def self.rename from, to
  raise ArgumentError, "Cannot rename reserved keys" if
    RESERVED_KEYS.include?(from) || RESERVED_KEYS.include?(to)

  rename_keys << [from.to_s.to_sym, to.to_s.to_sym]
end

.type(value) ⇒ Object

Sets the type of this Document.



268
269
270
# File 'lib/couchpillow/document.rb', line 268

def self.type value
  @type = value.to_s
end

Instance Method Details

#[](key) ⇒ Object



46
47
48
# File 'lib/couchpillow/document.rb', line 46

def [] key
  @data[key.to_s.to_sym]
end

#[]=(key, value) ⇒ Object



51
52
53
# File 'lib/couchpillow/document.rb', line 51

def []= key, value
  @data[key.to_s.to_sym] = value
end

#assign_defaults!Object

Assign default values.



204
205
206
207
208
# File 'lib/couchpillow/document.rb', line 204

def assign_defaults!
  self.class.attributes.each do |k, attr|
    @data[k] = attr.trigger_default_directive if !has?(k) && attr.has_default?
  end
end

#auto_convert!Object

Auto convert.



213
214
215
216
217
# File 'lib/couchpillow/document.rb', line 213

def auto_convert!
  self.class.attributes.each do |k, attr|
    @data[k] = attr.trigger_auto_convert_directive(@data[k]) if has?(k)
  end
end

#delete!Object

Delete this document from the server.



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/couchpillow/document.rb', line 84

def delete!
  result = self.class.default_db.delete @id

  unless self.class.secondary_dbs.empty?
    @futures << Celluloid::Future.new do
      self.class.secondary_dbs.each do |db|
        db.delete @id
      end
    end
  end

  result
end

#doc_typeObject

Helper to get the type of this Document. Can’t really name this ‘type`. Need to avoid name conflict with Ruby’s own ‘type` method.



163
164
165
# File 'lib/couchpillow/document.rb', line 163

def doc_type
  self.class.doc_type
end

#has?(key) ⇒ Boolean

Check if this Document has the key

Returns:



141
142
143
# File 'lib/couchpillow/document.rb', line 141

def has? key
  @data.has_key?(key)
end

#rename!Object

Rename the keys in this Document as specified by the rename directive.



184
185
186
187
188
189
190
# File 'lib/couchpillow/document.rb', line 184

def rename!
  self.class.rename_keys.each do |from, to|
    @data.has_key?(from) and
      @data[to] = @data[from] and
      @data.delete(from)
  end
end

#save!(opts = {}) ⇒ Object

Save this document to the server



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/couchpillow/document.rb', line 58

def save! opts = {}
  whitelist!
  sort!
  timestamp!
  validate!
  to_save = @data.merge({
    :_type => self.class.doc_type
  })

  # write to all connections
  result = self.class.default_db.set(@id, to_save, opts)

  unless self.class.secondary_dbs.empty?
    @futures << Celluloid::Future.new do
      self.class.secondary_dbs.each do |db|
        db.set(@id, to_save, opts)
      end
    end
  end

  result
end

#sort!Object

Sort keys on this document.



237
238
239
# File 'lib/couchpillow/document.rb', line 237

def sort!
  @data = @data.sort.to_h
end

#to_hashObject

Convert this Document to a Hash



155
156
157
# File 'lib/couchpillow/document.rb', line 155

def to_hash
  { :_id => @id, :_type => self.class.doc_type }.merge!(@data)
end

#to_json(*a) ⇒ Object

Convert this Document to a JSON string



148
149
150
# File 'lib/couchpillow/document.rb', line 148

def to_json *a
  to_hash.to_json(*a)
end

#update(hash) ⇒ Object

Updates the attributes in the document. Existing attributes will be overwritten and new ones will be added. Any other existing attributes that are not present in the hash will be ignored.



129
130
131
132
133
134
135
136
# File 'lib/couchpillow/document.rb', line 129

def update hash
  hash.each do |k,v|
    @data[k.to_sym] = v
  end
  rename!
  whitelist!
  auto_convert!
end

#update!Object

Attempt to update this Document. Fails if this Document does not yet exist in the database.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/couchpillow/document.rb', line 102

def update!
  whitelist!
  sort!
  timestamp!
  validate!
  to_save = @data.merge({
    :_type => self.class.doc_type
  })

  result = self.class.default_db.replace @id, to_save

  unless self.class.secondary_dbs.empty?
    @futures << Celluloid::Future.new do
      self.class.secondary_dbs.each do |db|
        db.replace @id, to_save
      end
    end
  end

  result
end

#validate!Object

Go through each attribute, and validate the values. Validation also perform auto-conversion if auto-conversion is enabled for that attribute.



223
224
225
226
227
228
229
230
231
232
# File 'lib/couchpillow/document.rb', line 223

def validate!
  self.class.attributes.each do |k, attr|
    if has?(k)
      @data[k] = attr.validate(@data[k])
    else
      @data[k] = attr.trigger_default_directive if attr.has_default?
      raise ValidationError, "Attribute '#{k}' is required" if attr.required? && !has?(k)
    end
  end
end

#waitObject

Blocks until all pending tasks has completed. Returns the result of those tasks in an array.



171
172
173
174
175
176
177
178
179
# File 'lib/couchpillow/document.rb', line 171

def wait
  result = []
  until @futures.empty?
    f = @futures.shift
    result << f.value
  end

  result
end

#whitelist!Object

Cleanup the @data hash so it only contains relevant fields.



195
196
197
198
199
# File 'lib/couchpillow/document.rb', line 195

def whitelist!
  @data.delete_if do |k, v|
    !self.class.attributes.has_key?(k)
  end
end