Class: RelaxDB::Document
- Inherits:
-
Object
- Object
- RelaxDB::Document
- Includes:
- Validators
- Defined in:
- lib/relaxdb/document.rb
Direct Known Subclasses
Instance Attribute Summary collapse
-
#errors ⇒ Object
Used to store validation messages.
-
#save_list ⇒ Object
A call issued to save_all will save this object and the contents of the save_list.
-
#validation_skip_list ⇒ Object
Attribute symbols added to this list won’t be validated on save.
Class Method Summary collapse
-
.add_derived_prop(prop, deriver) ⇒ Object
See derived_properties_spec.rb for usage.
- .after_save(callback) ⇒ Object
- .after_save_callbacks ⇒ Object
- .all(params = {}) ⇒ Object
- .all_relationships ⇒ Object
-
.before_save(callback) ⇒ Object
Callbacks - define these in a module and mix’em’in ?.
- .before_save_callbacks ⇒ Object
- .belongs_to(relationship, opts = {}) ⇒ Object (also: references)
-
.create_all_by_class_view ⇒ Object
Create a view allowing all instances of a particular class to be retreived.
- .create_validation_msg(att, validation_msg) ⇒ Object
- .create_validator(att, v) ⇒ Object
- .create_views(chain) ⇒ Object
- .has_many(relationship, opts = {}) ⇒ Object
- .has_many_rels ⇒ Object
- .has_one(relationship) ⇒ Object
- .has_one_rels ⇒ Object
- .inherited(subclass) ⇒ Object
- .property(prop, opts = {}) ⇒ Object
-
.references_many(relationship, opts = {}) ⇒ Object
If you’re using this method, read the specs and make sure you understand how it can be used and how it shouldn’t be used.
- .references_many_rels ⇒ Object
- .up_chain ⇒ Object
-
.view_by(*atts) ⇒ Object
Creates the corresponding view and stores it in CouchDB Adds by_ and paginate_by_ methods to the class.
Instance Method Summary collapse
-
#==(other) ⇒ Object
Returns true if CouchDB considers other to be the same as self.
- #after_save ⇒ Object
- #before_save ⇒ Object
- #conflicted ⇒ Object
- #create_or_get_proxy(klass, relationship, opts = nil) ⇒ Object
-
#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.
-
#initialize(hash = {}) ⇒ Document
constructor
A new instance of Document.
- #inspect ⇒ Object (also: #to_s)
- #new_document? ⇒ Boolean (also: #new_record?, #unsaved?)
- #on_update_conflict ⇒ Object
- #post_save ⇒ Object
- #pre_save ⇒ Object
-
#save ⇒ Object
Not yet sure of final implemention for hooks - may lean more towards DM than AR.
- #save! ⇒ Object
-
#save_all ⇒ Object
save_all and save_all! are untested.
- #save_all! ⇒ Object
- #save_to_couch ⇒ Object
- #set_attributes(data) ⇒ Object
- #set_timestamps ⇒ Object
- #to_json ⇒ Object
- #to_param ⇒ Object (also: #id)
- #update_conflict? ⇒ Boolean
- #validate_att(att_name, att_val) ⇒ Object
- #validates? ⇒ Boolean (also: #validate)
-
#write_derived_props(source) ⇒ Object
The rationale for rescuing the send below is that the lambda for a derived property shouldn’t need to concern itself with checking the validity of the underlying property.
Methods included from Validators
Constructor Details
#initialize(hash = {}) ⇒ Document
Returns a new instance of Document.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/relaxdb/document.rb', line 117 def initialize(hash={}) unless hash["_id"] self._id = UuidGenerator.uuid end @errors = Errors.new @save_list = [] @validation_skip_list = [] # Set default properties if this object isn't being loaded from CouchDB unless hash["_rev"] properties.each do |prop| if methods.include?("set_default_#{prop}") send("set_default_#{prop}") end end end @set_derived_props = hash["_rev"] ? false : true set_attributes(hash) @set_derived_props = true end |
Instance Attribute Details
#errors ⇒ Object
Used to store validation messages
8 9 10 |
# File 'lib/relaxdb/document.rb', line 8 def errors @errors end |
#save_list ⇒ Object
A call issued to save_all will save this object and the contents of the save_list. This allows secondary object to be saved at the same time as this object.
13 14 15 |
# File 'lib/relaxdb/document.rb', line 13 def save_list @save_list end |
#validation_skip_list ⇒ Object
Attribute symbols added to this list won’t be validated on save
16 17 18 |
# File 'lib/relaxdb/document.rb', line 16 def validation_skip_list @validation_skip_list end |
Class Method Details
.add_derived_prop(prop, deriver) ⇒ Object
See derived_properties_spec.rb for usage
90 91 92 93 94 |
# File 'lib/relaxdb/document.rb', line 90 def self.add_derived_prop(prop, deriver) source, writer = deriver[0], deriver[1] derived_prop_writers[source] ||= {} derived_prop_writers[source][prop] = writer end |
.after_save(callback) ⇒ Object
510 511 512 |
# File 'lib/relaxdb/document.rb', line 510 def self.after_save(callback) after_save_callbacks << callback end |
.after_save_callbacks ⇒ Object
514 515 516 |
# File 'lib/relaxdb/document.rb', line 514 def self.after_save_callbacks @after_save_callbacks ||= [] end |
.all(params = {}) ⇒ Object
461 462 463 |
# File 'lib/relaxdb/document.rb', line 461 def self.all params = {} AllDelegator.new self.name, params end |
.all_relationships ⇒ Object
457 458 459 |
# File 'lib/relaxdb/document.rb', line 457 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 ?
492 493 494 |
# File 'lib/relaxdb/document.rb', line 492 def self.before_save(callback) before_save_callbacks << callback end |
.before_save_callbacks ⇒ Object
496 497 498 |
# File 'lib/relaxdb/document.rb', line 496 def self.before_save_callbacks @before_save ||= [] end |
.belongs_to(relationship, opts = {}) ⇒ Object Also known as: references
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 |
# File 'lib/relaxdb/document.rb', line 422 def self.belongs_to(relationship, opts={}) 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 write_derived_props(relationship) if @set_derived_props 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) write_derived_props(relationship) if @set_derived_props id end define_method("#{relationship}_id") do instance_variable_get("@#{relationship}_id") end create_validator(relationship, opts[:validator]) if opts[:validator] # Untested below create_validation_msg(relationship, opts[:validation_msg]) if opts[:validation_msg] end |
.create_all_by_class_view ⇒ Object
Create a view allowing all instances of a particular class to be retreived
562 563 564 565 566 567 |
# File 'lib/relaxdb/document.rb', line 562 def self.create_all_by_class_view if RelaxDB.create_views? view = ViewCreator.all view.save unless view.exists? end end |
.create_validation_msg(att, validation_msg) ⇒ Object
79 80 81 82 83 84 85 86 87 |
# File 'lib/relaxdb/document.rb', line 79 def self.create_validation_msg(att, validation_msg) if validation_msg.is_a?(Proc) validation_msg.arity == 1 ? define_method("#{att}_validation_msg") { |att_val| validation_msg.call(att_val) } : define_method("#{att}_validation_msg") { |att_val| validation_msg.call(att_val, self) } else define_method("#{att}_validation_msg") { validation_msg } end end |
.create_validator(att, v) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/relaxdb/document.rb', line 66 def self.create_validator(att, v) method_name = "validate_#{att}" if v.is_a? Proc v.arity == 1 ? define_method(method_name) { |att_val| v.call(att_val) } : define_method(method_name) { |att_val| v.call(att_val, self) } elsif instance_methods.include? "validator_#{v}" define_method(method_name) { |att_val| send("validator_#{v}", att_val, self) } else define_method(method_name) { |att_val| send(v, att_val) } end end |
.create_views(chain) ⇒ Object
583 584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/relaxdb/document.rb', line 583 def self.create_views chain # Capture the inheritance hierarchy of this class @hierarchy ||= [self] @hierarchy += chain @hierarchy.uniq! if RelaxDB.create_views? ViewCreator.all(@hierarchy).save __view_by_list__.each do |atts| ViewCreator.by_att_list(@hierarchy, *atts).save end end end |
.has_many(relationship, opts = {}) ⇒ Object
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'lib/relaxdb/document.rb', line 371 def self.has_many(relationship, opts={}) @has_many_rels ||= [] @has_many_rels << relationship if RelaxDB.create_views? target_class = opts[:class] || relationship.to_s.singularize.camel_case relationship_as_viewed_by_target = (opts[:known_as] || self.name.snake_case).to_s ViewCreator.has_n(self.name, relationship, target_class, relationship_as_viewed_by_target).save end define_method(relationship) do create_or_get_proxy(HasManyProxy, relationship, opts) end define_method("#{relationship}=") do |children| create_or_get_proxy(HasManyProxy, relationship, opts).children = children write_derived_props(relationship) if @set_derived_props children end end |
.has_many_rels ⇒ Object
392 393 394 395 |
# File 'lib/relaxdb/document.rb', line 392 def self.has_many_rels # Don't force clients to check its instantiated @has_many_rels ||= [] end |
.has_one(relationship) ⇒ Object
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/relaxdb/document.rb', line 397 def self.has_one(relationship) @has_one_rels ||= [] @has_one_rels << relationship if RelaxDB.create_views? target_class = relationship.to_s.camel_case relationship_as_viewed_by_target = self.name.snake_case ViewCreator.has_n(self.name, relationship, target_class, relationship_as_viewed_by_target).save end 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 write_derived_props(relationship) if @set_derived_props new_target end end |
.has_one_rels ⇒ Object
418 419 420 |
# File 'lib/relaxdb/document.rb', line 418 def self.has_one_rels @has_one_rels ||= [] end |
.inherited(subclass) ⇒ Object
569 570 571 572 573 574 |
# File 'lib/relaxdb/document.rb', line 569 def self.inherited subclass chain = subclass.up_chain while k = chain.pop k.create_views chain end end |
.property(prop, opts = {}) ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/relaxdb/document.rb', line 30 def self.property(prop, opts={}) 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] create_validator(prop, opts[:validator]) end if opts[:validation_msg] create_validation_msg(prop, opts[:validation_msg]) end if opts[:derived] add_derived_prop(prop, opts[:derived]) end end |
.references_many(relationship, opts = {}) ⇒ Object
If you’re using this method, read the specs and make sure you understand how it can be used and how it shouldn’t be used
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/relaxdb/document.rb', line 335 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 id_arr_sym = "@#{relationship}".to_sym if RelaxDB.create_views? target_class = opts[:class] relationship_as_viewed_by_target = opts[:known_as].to_s ViewCreator.references_many(self.name, relationship, target_class, relationship_as_viewed_by_target).save end define_method(relationship) do instance_variable_set(id_arr_sym, []) unless instance_variable_defined? id_arr_sym create_or_get_proxy(ReferencesManyProxy, relationship, opts) end define_method("#{relationship}_ids") do instance_variable_set(id_arr_sym, []) unless instance_variable_defined? id_arr_sym instance_variable_get(id_arr_sym) end define_method("#{relationship}=") do |val| # Don't invoke this method unless you know what you're doing instance_variable_set(id_arr_sym, val) end end |
.references_many_rels ⇒ Object
367 368 369 |
# File 'lib/relaxdb/document.rb', line 367 def self.references_many_rels @references_many_rels ||= [] end |
.up_chain ⇒ Object
576 577 578 579 580 581 |
# File 'lib/relaxdb/document.rb', line 576 def self.up_chain k = self kls = [k] kls << k while ((k = k.superclass) != RelaxDB::Document) kls end |
.view_by(*atts) ⇒ Object
Creates the corresponding view and stores it in CouchDB Adds by_ and paginate_by_ methods to the class
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/relaxdb/document.rb', line 528 def self.view_by *atts opts = atts.last.is_a?(Hash) ? atts.pop : {} __view_by_list__ << atts if RelaxDB.create_views? ViewCreator.by_att_list([self.name], *atts).save end by_name = "by_#{atts.join "_and_"}" .instance_eval do define_method by_name do |*params| view_name = "#{self.name}_#{by_name}" if params.empty? res = RelaxDB.rf_view view_name, opts elsif params[0].is_a? Hash res = RelaxDB.rf_view view_name, opts.merge(params[0]) else res = RelaxDB.rf_view(view_name, :key => params[0]).first end end end paginate_by_name = "paginate_by_#{atts.join "_and_"}" .instance_eval do define_method paginate_by_name do |params| view_name = "#{self.name}_#{by_name}" params[:attributes] = atts params = opts.merge params RelaxDB.paginate_view view_name, params end end end |
Instance Method Details
#==(other) ⇒ Object
Returns true if CouchDB considers other to be the same as self
329 330 331 |
# File 'lib/relaxdb/document.rb', line 329 def ==(other) other && _id == other._id end |
#after_save ⇒ Object
518 519 520 521 522 |
# File 'lib/relaxdb/document.rb', line 518 def after_save self.class.after_save_callbacks.each do |callback| callback.is_a?(Proc) ? callback.call(self) : send(callback) end end |
#before_save ⇒ Object
500 501 502 503 504 505 506 507 508 |
# File 'lib/relaxdb/document.rb', line 500 def before_save self.class.before_save_callbacks.each do |callback| resp = callback.is_a?(Proc) ? callback.call(self) : send(callback) if resp == false errors[:before_save] = :failed return false end end end |
#conflicted ⇒ Object
207 208 209 210 |
# File 'lib/relaxdb/document.rb', line 207 def conflicted @update_conflict = true on_update_conflict end |
#create_or_get_proxy(klass, relationship, opts = nil) ⇒ Object
318 319 320 321 322 323 324 325 326 |
# File 'lib/relaxdb/document.rb', line 318 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) instance_variable_set(proxy_sym, proxy) end 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
TODO: Current implemention may be inappropriate - causing CouchDB to try to JSON encode undefined. Ensure nil is serialized? See has_many_spec#should nullify its child relationships
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/relaxdb/document.rb', line 471 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 |
#inspect ⇒ Object Also known as: to_s
156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/relaxdb/document.rb', line 156 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, opts| id = instance_variable_get("@#{relationship}_id".to_sym) s << ", #{relationship}_id: #{id}" if id end s << ", errors: #{errors.inspect}" unless errors.empty? s << ", save_list: #{save_list.map { |o| o.inspect }.join ", " }" unless save_list.empty? s << ">" end |
#new_document? ⇒ Boolean Also known as: new_record?, unsaved?
297 298 299 |
# File 'lib/relaxdb/document.rb', line 297 def new_document? @_rev.nil? end |
#on_update_conflict ⇒ Object
212 213 214 215 |
# File 'lib/relaxdb/document.rb', line 212 def on_update_conflict # override with any behaviour you want to happen when # CouchDB returns DocumentConflict on an attempt to save end |
#post_save ⇒ Object
228 229 230 |
# File 'lib/relaxdb/document.rb', line 228 def post_save after_save end |
#pre_save ⇒ Object
221 222 223 224 225 226 |
# File 'lib/relaxdb/document.rb', line 221 def pre_save return false unless validates? return false unless before_save true end |
#save ⇒ Object
Not yet sure of final implemention for hooks - may lean more towards DM than AR
188 189 190 191 192 193 194 195 |
# File 'lib/relaxdb/document.rb', line 188 def save if pre_save && save_to_couch after_save self else false end end |
#save! ⇒ Object
241 242 243 244 245 246 247 248 249 |
# File 'lib/relaxdb/document.rb', line 241 def save! if save self elsif update_conflict? raise UpdateConflict, self else raise ValidationFailure, self.errors.to_json end end |
#save_all ⇒ Object
save_all and save_all! are untested
233 234 235 |
# File 'lib/relaxdb/document.rb', line 233 def save_all RelaxDB.bulk_save self, *save_list end |
#save_all! ⇒ Object
237 238 239 |
# File 'lib/relaxdb/document.rb', line 237 def save_all! RelaxDB.bulk_save! self, *save_list end |
#save_to_couch ⇒ Object
197 198 199 200 201 202 203 204 205 |
# File 'lib/relaxdb/document.rb', line 197 def save_to_couch begin resp = RelaxDB.db.put(_id, to_json) self._rev = JSON.parse(resp.body)["rev"] rescue HTTP_409 conflicted return false end end |
#set_attributes(data) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/relaxdb/document.rb', line 140 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, _on or _date try to convert it to a Time if [/_at$/, /_on$/, /_date$/, /_time$/].inject(nil) { |i, r| i ||= (key =~ r) } val = Time.parse(val).utc 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 send("#{key}=".to_sym, val) if methods.include? "#{key}=" end end |
#set_timestamps ⇒ Object
308 309 310 311 312 313 314 315 316 |
# File 'lib/relaxdb/document.rb', line 308 def now = Time.now if new_document? && respond_to?(:created_at) # Don't override it if it's already been set @created_at = now if @created_at.nil? end @updated_at = now if respond_to?(:updated_at) end |
#to_json ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/relaxdb/document.rb', line 173 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 end properties.each do |prop| prop_val = instance_variable_get("@#{prop}".to_sym) data["#{prop}"] = prop_val if prop_val end data["relaxdb_class"] = self.class.name data.to_json end |
#to_param ⇒ Object Also known as: id
303 304 305 |
# File 'lib/relaxdb/document.rb', line 303 def to_param self._id end |
#update_conflict? ⇒ Boolean
217 218 219 |
# File 'lib/relaxdb/document.rb', line 217 def update_conflict? @update_conflict end |
#validate_att(att_name, att_val) ⇒ Object
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/relaxdb/document.rb', line 273 def validate_att(att_name, att_val) begin success = send("validate_#{att_name}", att_val) rescue => e RelaxDB.logger.warn "Validating #{att_name} with #{att_val} raised #{e}" succes = false end unless success if methods.include? "#{att_name}_validation_msg" begin @errors[att_name] = send("#{att_name}_validation_msg", att_val) rescue => e RelaxDB.logger.warn "Validation_msg for #{att_name} with #{att_val} raised #{e}" @errors[att_name] = "validation_msg_exception:invalid:#{att_val}" end elsif @errors[att_name].nil? # Only set a validation message if a validator hasn't already set one @errors[att_name] = "invalid:#{att_val}" end end success end |
#validates? ⇒ Boolean Also known as: validate
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/relaxdb/document.rb', line 251 def validates? props = properties - validation_skip_list prop_vals = props.map { |prop| instance_variable_get("@#{prop}") } rels = self.class.belongs_to_rels.keys - validation_skip_list rel_vals = rels.map { |rel| instance_variable_get("@#{rel}_id") } att_names = props + rels att_vals = prop_vals + rel_vals total_success = true att_names.each_index do |i| att_name, att_val = att_names[i], att_vals[i] if methods.include? "validate_#{att_name}" total_success &= validate_att(att_name, att_val) end end total_success end |
#write_derived_props(source) ⇒ Object
The rationale for rescuing the send below is that the lambda for a derived property shouldn’t need to concern itself with checking the validity of the underlying property. Nor, IMO, should clients be exposed to the possibility of a writer raising an exception.
102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/relaxdb/document.rb', line 102 def write_derived_props(source) writers = self.class.derived_prop_writers writers = writers && writers[source] if writers writers.each do |prop, writer| current_val = send(prop) begin send("#{prop}=", writer.call(current_val, self)) rescue => e RelaxDB.logger.error "Deriving #{prop} from #{source} raised #{e}" end end end end |