Class: RelaxDB::Document
- Inherits:
-
Object
- Object
- RelaxDB::Document
- Includes:
- Validators
- Defined in:
- lib/relaxdb/document.rb
Direct Known Subclasses
Instance Attribute Summary collapse
-
#data ⇒ Object
Not part of the public API - should only be used by clients with caution.
-
#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
-
.before_save(callback) ⇒ Object
Callbacks - define these in a module and mix’em’in ?.
- .before_save_callbacks ⇒ Object
-
.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
- .inherited(subclass) ⇒ Object
- .property(prop, opts = {}) ⇒ Object
- .references(relationship, opts = {}) ⇒ Object
- .up_chain ⇒ Object
-
.view_by(*atts) ⇒ Object
Creates the corresponding view, emitting 1 as the val Adds a by_ method to this class, but does not add a paginate method.
-
.view_docs_by(*atts) ⇒ Object
Creates the corresponding view, emitting the doc as the val 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
-
#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_timestamps ⇒ Object
- #to_json(*args) ⇒ 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.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/relaxdb/document.rb', line 137 def initialize(hash={}) @errors = Errors.new @save_list = [] @validation_skip_list = [] # hash.dup because assigning references properties and defaults both # modify the internal representation - @data. This messes with the # iterator below that assigns vals to @data. params = hash.dup # If there's no rev, it's a new document if hash["_rev"].nil? # Clients may use symbols as keys so convert all to strings first. @data = hash.map { |k,v| [k.to_s, v] }.to_hash else @data = hash end unless @data["_id"] @data["_id"] = UuidGenerator.uuid end # It's a new doc, set default properties. We only do this after ensuring # this obj first has an _id. unless @data["_rev"] default_methods = methods.select { |m| m =~ /__set_default/ } default_methods.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9 properties.each do |prop| if default_methods.include? "__set_default_#{prop}__".to_sym send("__set_default_#{prop}__") end end params.each do |key, val| send("#{key}=".to_sym, val) end @data["relaxdb_class"] = self.class.name end end |
Instance Attribute Details
#data ⇒ Object
Not part of the public API - should only be used by clients with caution. The data keys are Strings as this is what JSON.parse gives us.
20 21 22 |
# File 'lib/relaxdb/document.rb', line 20 def data @data end |
#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 objects 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
110 111 112 113 114 |
# File 'lib/relaxdb/document.rb', line 110 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
413 414 415 |
# File 'lib/relaxdb/document.rb', line 413 def self.after_save(callback) after_save_callbacks << callback end |
.after_save_callbacks ⇒ Object
417 418 419 |
# File 'lib/relaxdb/document.rb', line 417 def self.after_save_callbacks @after_save_callbacks ||= [] end |
.all(params = {}) ⇒ Object
382 383 384 |
# File 'lib/relaxdb/document.rb', line 382 def self.all params = {} AllDelegator.new self.name, params end |
.before_save(callback) ⇒ Object
Callbacks - define these in a module and mix’em’in ?
395 396 397 |
# File 'lib/relaxdb/document.rb', line 395 def self.before_save(callback) before_save_callbacks << callback end |
.before_save_callbacks ⇒ Object
399 400 401 |
# File 'lib/relaxdb/document.rb', line 399 def self.before_save_callbacks @before_save ||= [] end |
.create_all_by_class_view ⇒ Object
Create a view allowing all instances of a particular class to be retreived
494 495 496 |
# File 'lib/relaxdb/document.rb', line 494 def self.create_all_by_class_view ViewCreator.all.add_to_design_doc if RelaxDB.create_views? end |
.create_validation_msg(att, validation_msg) ⇒ Object
99 100 101 102 103 104 105 106 107 |
# File 'lib/relaxdb/document.rb', line 99 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") { |att_val| validation_msg } end end |
.create_validator(att, v) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/relaxdb/document.rb', line 82 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) } else v_meths = instance_methods.select { |m| m =~ /validator_/ } v_meths.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9 if v_meths.include? "validator_#{v}".to_sym 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 end |
.create_views(chain) ⇒ Object
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 |
# File 'lib/relaxdb/document.rb', line 512 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).add_to_design_doc __view_docs_by_list__.each do |atts| ViewCreator.docs_by_att_list(@hierarchy, *atts).add_to_design_doc end __view_by_list__.each do |atts| ViewCreator.by_att_list(@hierarchy, *atts).add_to_design_doc end end end |
.inherited(subclass) ⇒ Object
498 499 500 501 502 503 |
# File 'lib/relaxdb/document.rb', line 498 def self.inherited subclass chain = subclass.up_chain while k = chain.pop k.create_views chain end end |
.property(prop, opts = {}) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/relaxdb/document.rb', line 37 def self.property(prop, opts={}) properties << prop if prop.to_s =~ /_at$|_on$|_date$|_time$/ define_method(prop) do val = @data[prop.to_s] Time.parse(val).utc rescue val end else define_method(prop) do @data[prop.to_s] end end define_method("#{prop}=") do |val| @data[prop.to_s] = val end if opts[:default] define_method("__set_default_#{prop}__") do if @data[prop.to_s].nil? default = opts[:default] val = default.is_a?(Proc) ? default.call : default @data[prop.to_s] = val end 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(relationship, opts = {}) ⇒ Object
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/relaxdb/document.rb', line 353 def self.references(relationship, opts={}) references_rels[relationship] = opts define_method(relationship) do create_or_get_proxy(ReferencesProxy, relationship).target end define_method("#{relationship}=") do |new_target| create_or_get_proxy(ReferencesProxy, relationship).target = new_target write_derived_props(relationship) end # Allows all writers to be invoked from the hash passed to initialize define_method("#{relationship}_id=") do |id| @data["#{relationship}_id"] = id write_derived_props(relationship) id end define_method("#{relationship}_id") do @data["#{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 |
.up_chain ⇒ Object
505 506 507 508 509 510 |
# File 'lib/relaxdb/document.rb', line 505 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, emitting 1 as the val Adds a by_ method to this class, but does not add a paginate method.
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/relaxdb/document.rb', line 469 def self.view_by *atts opts = atts.last.is_a?(Hash) ? atts.pop : {} opts = opts.merge :reduce => false __view_by_list__ << atts if RelaxDB.create_views? ViewCreator.by_att_list([self.name], *atts).add_to_design_doc 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? ViewByDelegator.new(view_name, opts) elsif params[0].is_a? Hash ViewByDelegator.new(view_name, opts.merge(params[0])) else ViewByDelegator.new(view_name, opts.merge(:key => params[0])).load!.first end end end end |
.view_docs_by(*atts) ⇒ Object
Creates the corresponding view, emitting the doc as the val Adds by_ and paginate_by_ methods to the class
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/relaxdb/document.rb', line 431 def self.view_docs_by *atts opts = atts.last.is_a?(Hash) ? atts.pop : {} __view_docs_by_list__ << atts if RelaxDB.create_views? ViewCreator.docs_by_att_list([self.name], *atts).add_to_design_doc 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? RelaxDB.rf_view view_name, opts elsif params[0].is_a? Hash RelaxDB.rf_view view_name, opts.merge(params[0]) else 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
349 350 351 |
# File 'lib/relaxdb/document.rb', line 349 def ==(other) other && _id == other._id end |
#after_save ⇒ Object
421 422 423 424 425 |
# File 'lib/relaxdb/document.rb', line 421 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
403 404 405 406 407 408 409 410 411 |
# File 'lib/relaxdb/document.rb', line 403 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
223 224 225 226 |
# File 'lib/relaxdb/document.rb', line 223 def conflicted @update_conflict = true on_update_conflict end |
#create_or_get_proxy(klass, relationship, opts = nil) ⇒ Object
338 339 340 341 342 343 344 345 346 |
# File 'lib/relaxdb/document.rb', line 338 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
386 387 388 389 390 |
# File 'lib/relaxdb/document.rb', line 386 def destroy! # 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
178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/relaxdb/document.rb', line 178 def inspect s = "#<#{self.class}:#{self.object_id}" properties.each do |prop| prop_val = @data[prop.to_s] s << ", #{prop}: #{prop_val.inspect}" if prop_val end self.class.references_rels.each do |relationship, opts| id = @data["#{relationship}_id"] 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?
317 318 319 |
# File 'lib/relaxdb/document.rb', line 317 def new_document? self._rev.nil? end |
#on_update_conflict ⇒ Object
228 229 230 231 |
# File 'lib/relaxdb/document.rb', line 228 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
244 245 246 |
# File 'lib/relaxdb/document.rb', line 244 def post_save after_save end |
#pre_save ⇒ Object
237 238 239 240 241 242 |
# File 'lib/relaxdb/document.rb', line 237 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
204 205 206 207 208 209 210 211 |
# File 'lib/relaxdb/document.rb', line 204 def save if pre_save && save_to_couch after_save self else false end end |
#save! ⇒ Object
257 258 259 260 261 262 263 264 265 |
# File 'lib/relaxdb/document.rb', line 257 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
249 250 251 |
# File 'lib/relaxdb/document.rb', line 249 def save_all RelaxDB.bulk_save self, *save_list end |
#save_all! ⇒ Object
253 254 255 |
# File 'lib/relaxdb/document.rb', line 253 def save_all! RelaxDB.bulk_save! self, *save_list end |
#save_to_couch ⇒ Object
213 214 215 216 217 218 219 220 221 |
# File 'lib/relaxdb/document.rb', line 213 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_timestamps ⇒ Object
328 329 330 331 332 333 334 335 336 |
# File 'lib/relaxdb/document.rb', line 328 def now = Time.now if new_document? && respond_to?(:created_at) # Don't override it if it's already been set @data["created_at"] = now if @data["created_at"].nil? end @data["updated_at"] = now if respond_to?(:updated_at) end |
#to_json(*args) ⇒ Object
195 196 197 198 199 200 201 |
# File 'lib/relaxdb/document.rb', line 195 def to_json(*args) ref_rels = self.class.references_rels.map { |k, v| k.to_s } @data.delete_if { |k,v| ref_rels.include? k } @data["errors"] = errors unless errors.empty? @data.to_json end |
#to_param ⇒ Object Also known as: id
323 324 325 |
# File 'lib/relaxdb/document.rb', line 323 def to_param self._id end |
#update_conflict? ⇒ Boolean
233 234 235 |
# File 'lib/relaxdb/document.rb', line 233 def update_conflict? @update_conflict end |
#validate_att(att_name, att_val) ⇒ Object
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/relaxdb/document.rb', line 291 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 v_msg_meths = methods.select { |m | m =~ /_validation_msg/ } v_msg_meths.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9 if v_msg_meths.include? "#{att_name}_validation_msg".to_sym 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
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/relaxdb/document.rb', line 267 def validates? props = properties - validation_skip_list prop_vals = props.map { |prop| @data[prop.to_s] } rels = self.class.references_rels.keys - validation_skip_list rel_vals = rels.map { |rel| @data["#{rel}_id"] } att_names = props + rels att_vals = prop_vals + rel_vals total_success = true validate_methods = methods.select { |m| m =~ /validate_/ } validate_methods.map! { |m| m.to_sym } if RUBY_VERSION.to_f < 1.9 att_names.each_index do |i| att_name, att_val = att_names[i], att_vals[i] if validate_methods.include? "validate_#{att_name}".to_sym 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.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/relaxdb/document.rb', line 122 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 |