Class: RelaxDB::Document

Inherits:
Object
  • Object
show all
Defined in:
lib/relaxdb/document.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash = {}) ⇒ Document

Returns a new instance of Document.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/relaxdb/document.rb', line 60

def initialize(hash={})
  # The default _id will be overwritten if loaded from CouchDB
  self._id = UuidGenerator.uuid 
  
  @errors = Errors.new

  # Set default properties if this object has not known CouchDB
  unless hash["_rev"]
    properties.each do |prop|
     if methods.include?("set_default_#{prop}")
       send("set_default_#{prop}")
     end
    end
  end
  
  set_attributes(hash)
end

Instance Attribute Details

#errorsObject

Used to store validation messages



6
7
8
# File 'lib/relaxdb/document.rb', line 6

def errors
  @errors
end

Class Method Details

.after_save(callback) ⇒ Object



350
351
352
# File 'lib/relaxdb/document.rb', line 350

def self.after_save(callback)
  after_save_callbacks << callback
end

.after_save_callbacksObject



354
355
356
# File 'lib/relaxdb/document.rb', line 354

def self.after_save_callbacks
  @after_save_callbacks ||= []
end

.allObject



307
308
309
# File 'lib/relaxdb/document.rb', line 307

def self.all
  @all_delegator ||= AllDelegator.new(self.name)
end

.all_relationshipsObject



303
304
305
# File 'lib/relaxdb/document.rb', line 303

def self.all_relationships
  belongs_to_rels + has_one_rels + has_many_rels + references_many_rels
end

.before_save(callback) ⇒ Object

Callbacks - define these in a module and mix’em’in ?



335
336
337
# File 'lib/relaxdb/document.rb', line 335

def self.before_save(callback)
  before_save_callbacks << callback
end

.before_save_callbacksObject



339
340
341
# File 'lib/relaxdb/document.rb', line 339

def self.before_save_callbacks
  @before_save ||= []
end

.belongs_to(relationship, opts = {}) ⇒ Object



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/relaxdb/document.rb', line 274

def self.belongs_to(relationship, opts={})
  @belongs_to_rels ||= {}
  @belongs_to_rels[relationship] = opts

  define_method(relationship) do
    create_or_get_proxy(BelongsToProxy, relationship).target
  end
  
  define_method("#{relationship}=") do |new_target|
    create_or_get_proxy(BelongsToProxy, relationship).target = new_target
  end
  
  # Allows all writers to be invoked from the hash passed to initialize 
  define_method("#{relationship}_id=") do |id|
    instance_variable_set("@#{relationship}_id".to_sym, id)
  end

  # Allows belongs_to relationships to be used by the paginator
  define_method("#{relationship}_id") do
    instance_variable_get("@#{relationship}_id")
  end
  
end

.belongs_to_relsObject



298
299
300
301
# File 'lib/relaxdb/document.rb', line 298

def self.belongs_to_rels
  # Don't force clients to check that it's instantiated
  @belongs_to_rels ||= {}
end

.has_many(relationship, opts = {}) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/relaxdb/document.rb', line 239

def self.has_many(relationship, opts={})
  @has_many_rels ||= []
  @has_many_rels << relationship
  
  define_method(relationship) do
    create_or_get_proxy(HasManyProxy, relationship, opts)
  end
  
  define_method("#{relationship}=") do
    raise "You may not currently assign to a has_many relationship - may be implemented"
  end      
end

.has_many_relsObject



252
253
254
255
# File 'lib/relaxdb/document.rb', line 252

def self.has_many_rels
  # Don't force clients to check its instantiated
  @has_many_rels ||= []
end

.has_one(relationship) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/relaxdb/document.rb', line 257

def self.has_one(relationship)
  @has_one_rels ||= []
  @has_one_rels << relationship
  
  define_method(relationship) do      
    create_or_get_proxy(HasOneProxy, relationship).target
  end
  
  define_method("#{relationship}=") do |new_target|
    create_or_get_proxy(HasOneProxy, relationship).target = new_target
  end
end

.has_one_relsObject



270
271
272
# File 'lib/relaxdb/document.rb', line 270

def self.has_one_rels
  @has_one_rels ||= []      
end

.paginate_by(page_params, *view_keys) {|paginate_params| ... } ⇒ Object

Yields:

  • (paginate_params)


364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/relaxdb/document.rb', line 364

def self.paginate_by(page_params, *view_keys)
  paginate_params = PaginateParams.new
  yield paginate_params
  raise paginate_params.error_msg if paginate_params.invalid? 
  
  paginator = Paginator.new(paginate_params, page_params)
              
  design_doc_name = self.name
  view = SortedByView.new(design_doc_name, *view_keys)
  query = Query.new(design_doc_name, view.view_name)
  query.merge(paginate_params)
  
  docs = view.query(query)
  docs.reverse! if paginate_params.order_inverted?
  
  paginator.add_next_and_prev(docs, design_doc_name, view.view_name, view_keys)
  
  docs
end

.propertiesObject



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

def self.properties
  # Ensure that classes that don't define their own properties still function as CouchDB objects
  @properties ||= [:_id, :_rev]
end

.property(prop, opts = {}) ⇒ Object

Define properties and property methods



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/relaxdb/document.rb', line 10

def self.property(prop, opts={})
  # Class instance varibles are not inherited, so the default properties must be explicitly listed 
  # Perhaps a better solution exists. Revise. I think extlib contains a solution for this...
  @properties ||= [:_id, :_rev]
  @properties << prop

  define_method(prop) do
    instance_variable_get("@#{prop}".to_sym)        
  end

  define_method("#{prop}=") do |val|
    instance_variable_set("@#{prop}".to_sym, val)
  end
  
  if opts[:default]
    define_method("set_default_#{prop}") do
      default = opts[:default]
      default = default.is_a?(Proc) ? default.call : default
      instance_variable_set("@#{prop}".to_sym, default)
    end
  end
  
  if opts[:validator]
    define_method("validate_#{prop}") do |prop_val|
      opts[:validator].call(prop_val)
    end
  end
  
  if opts[:validation_msg]
    define_method("#{prop}_validation_msg") do
      opts[:validation_msg]
    end
  end
  
end

.references_many(relationship, opts = {}) ⇒ Object

Deprecated. This method was experimental and will be removed once multi key GETs are available in CouchDB.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/relaxdb/document.rb', line 213

def self.references_many(relationship, opts={})
  # Treat the representation as a standard property 
  properties << relationship
  
  # Keep track of the relationship so peers can be disassociated on destroy
  @references_many_rels ||= []
  @references_many_rels << relationship
 
  define_method(relationship) do
    array_sym = "@#{relationship}".to_sym
    instance_variable_set(array_sym, []) unless instance_variable_defined? array_sym

    create_or_get_proxy(RelaxDB::ReferencesManyProxy, relationship, opts)
  end

  define_method("#{relationship}=") do |val|
    # Sharp edge - do not invoke this method
    instance_variable_set("@#{relationship}".to_sym, val)
  end           
end

.references_many_relsObject



234
235
236
237
# File 'lib/relaxdb/document.rb', line 234

def self.references_many_rels
  # Don't force clients to check its instantiated
  @references_many_rels ||= []
end

Instance Method Details

#==(other) ⇒ Object

Returns true if CouchDB considers other to be the same as self



207
208
209
# File 'lib/relaxdb/document.rb', line 207

def ==(other)
  other && _id == other._id
end

#add_denormalised_data(data, relationship, opts) ⇒ Object

quick n’ dirty denormalisation - explicit denormalisation will probably become a permanent fixture of RelaxDB, but quite likely in a different form to this one



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

def add_denormalised_data(data, relationship, opts)
  obj = send(relationship)
  if obj
    opts[:denormalise].each do |prop_name|
      val = obj.send(prop_name)
      data["#{relationship}_#{prop_name}"] = val
    end
  end
end

#after_saveObject



358
359
360
361
362
# File 'lib/relaxdb/document.rb', line 358

def after_save
  self.class.after_save_callbacks.each do |callback|
    callback.is_a?(Proc) ? callback.call(self) : send(callback)
  end
end

#before_saveObject



343
344
345
346
347
348
# File 'lib/relaxdb/document.rb', line 343

def before_save
  self.class.before_save_callbacks.each do |callback|
    resp = callback.is_a?(Proc) ? callback.call(self) : send(callback)
    return false unless resp
  end
end

#create_or_get_proxy(klass, relationship, opts = nil) ⇒ Object



196
197
198
199
200
201
202
203
204
# File 'lib/relaxdb/document.rb', line 196

def create_or_get_proxy(klass, relationship, opts=nil)
  proxy_sym = "@proxy_#{relationship}".to_sym
  proxy = instance_variable_get(proxy_sym)
  unless proxy
    proxy = opts ? klass.new(self, relationship, opts) : klass.new(self, relationship)
  end
  instance_variable_set(proxy_sym, proxy)
  proxy     
end

#destroy!Object

destroy! nullifies all relationships with peers and children before deleting itself in CouchDB The nullification and deletion are not performed in a transaction



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/relaxdb/document.rb', line 314

def destroy!
  self.class.references_many_rels.each do |rel|
    send(rel).clear
  end
  
  self.class.has_many_rels.each do |rel|
    send(rel).clear
  end
  
  self.class.has_one_rels.each do |rel|
    send("#{rel}=".to_sym, nil)
  end
  
  # Implicitly prevent the object from being resaved by failing to update its revision
  RelaxDB.db.delete("#{_id}?rev=#{_rev}")
  self
end

#inspectObject



97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/relaxdb/document.rb', line 97

def inspect
  s = "#<#{self.class}:#{self.object_id}"
  properties.each do |prop|
    prop_val = instance_variable_get("@#{prop}".to_sym)
    s << ", #{prop}: #{prop_val.inspect}" if prop_val
  end
  self.class.belongs_to_rels.each do |relationship|
    id = instance_variable_get("@#{relationship}_id".to_sym)
    s << ", #{relationship}_id: #{id}" if id
  end
  s << ">"
end

#propertiesObject



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

def properties
  self.class.properties
end

#saveObject

Order changed as of 30/10/2008 to be consistent with ActiveRecord Not yet sure of final implemention for hooks - may lean more towards DM than AR



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/relaxdb/document.rb', line 141

def save
  return false unless validates?
  return false unless before_save
        
  set_created_at if unsaved? 
  
  resp = RelaxDB.db.put("#{_id}", to_json)
  self._rev = JSON.parse(resp.body)["rev"]

  after_save

  self
end

#set_attributes(data) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/relaxdb/document.rb', line 78

def set_attributes(data)
  data.each do |key, val|
    # Only set instance variables on creation - object references are resolved on demand

    # If the variable name ends in _at try to convert it to a Time
    if key =~ /_at$/
        val = Time.local(*ParseDate.parsedate(val)) rescue val
    end
    
    # Ignore param keys that don't have a corresponding writer
    # This allows us to comfortably accept a hash containing superflous data 
    # such as a params hash in a controller 
    if methods.include? "#{key}="
      send("#{key}=".to_sym, val)
    end
            
  end
end

#set_created_atObject



189
190
191
192
193
194
# File 'lib/relaxdb/document.rb', line 189

def set_created_at
  if methods.include? "created_at"
    # Don't override it if it's already been set
    @created_at = Time.now if @created_at.nil?
  end
end

#to_jsonObject



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/relaxdb/document.rb', line 110

def to_json
  data = {}
  self.class.belongs_to_rels.each do |relationship, opts|
    id = instance_variable_get("@#{relationship}_id".to_sym)
    data["#{relationship}_id"] = id if id
    if opts[:denormalise]
      add_denormalised_data(data, relationship, opts)
    end
  end
  properties.each do |prop|
    prop_val = instance_variable_get("@#{prop}".to_sym)
    data["#{prop}"] = prop_val if prop_val
  end
  data["class"] = self.class.name
  data.to_json      
end

#to_paramObject Also known as: id



184
185
186
# File 'lib/relaxdb/document.rb', line 184

def to_param
  self._id
end

#unsaved?Boolean Also known as: new_record?, new_document?

Hmm. Rename… never_saved? newnew?

Returns:

  • (Boolean)


178
179
180
# File 'lib/relaxdb/document.rb', line 178

def unsaved?
  @_rev.nil?
end

#validateObject



173
174
175
# File 'lib/relaxdb/document.rb', line 173

def validate
  true
end

#validates?Boolean

Returns:

  • (Boolean)


155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/relaxdb/document.rb', line 155

def validates?
  total_success = true
  properties.each do |prop|
    if methods.include? "validate_#{prop}"
      prop_val = instance_variable_get("@#{prop}")
      success = send("validate_#{prop}", prop_val) rescue false
      unless success
        if methods.include? "#{prop}_validation_msg"
          @errors["#{prop}".to_sym] = send("#{prop}_validation_msg")
        end
      end
      total_success &= success          
    end
  end
  total_success &= validate
  total_success
end