openlogic-couchrest_model 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/.rspec +4 -0
- data/Gemfile +4 -0
- data/LICENSE +176 -0
- data/README.md +137 -0
- data/Rakefile +38 -0
- data/THANKS.md +21 -0
- data/VERSION +1 -0
- data/benchmarks/dirty.rb +118 -0
- data/couchrest_model.gemspec +36 -0
- data/history.md +309 -0
- data/init.rb +1 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +231 -0
- data/lib/couchrest/model/base.rb +129 -0
- data/lib/couchrest/model/callbacks.rb +28 -0
- data/lib/couchrest/model/casted_array.rb +83 -0
- data/lib/couchrest/model/casted_by.rb +33 -0
- data/lib/couchrest/model/casted_hash.rb +84 -0
- data/lib/couchrest/model/class_proxy.rb +135 -0
- data/lib/couchrest/model/collection.rb +273 -0
- data/lib/couchrest/model/configuration.rb +67 -0
- data/lib/couchrest/model/connection.rb +70 -0
- data/lib/couchrest/model/core_extensions/hash.rb +9 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
- data/lib/couchrest/model/design_doc.rb +128 -0
- data/lib/couchrest/model/designs.rb +91 -0
- data/lib/couchrest/model/designs/view.rb +513 -0
- data/lib/couchrest/model/dirty.rb +39 -0
- data/lib/couchrest/model/document_queries.rb +99 -0
- data/lib/couchrest/model/embeddable.rb +78 -0
- data/lib/couchrest/model/errors.rb +25 -0
- data/lib/couchrest/model/extended_attachments.rb +83 -0
- data/lib/couchrest/model/persistence.rb +178 -0
- data/lib/couchrest/model/properties.rb +228 -0
- data/lib/couchrest/model/property.rb +114 -0
- data/lib/couchrest/model/property_protection.rb +71 -0
- data/lib/couchrest/model/proxyable.rb +183 -0
- data/lib/couchrest/model/support/couchrest_database.rb +13 -0
- data/lib/couchrest/model/support/couchrest_design.rb +33 -0
- data/lib/couchrest/model/typecast.rb +154 -0
- data/lib/couchrest/model/validations.rb +80 -0
- data/lib/couchrest/model/validations/casted_model.rb +16 -0
- data/lib/couchrest/model/validations/locale/en.yml +5 -0
- data/lib/couchrest/model/validations/uniqueness.rb +69 -0
- data/lib/couchrest/model/views.rb +151 -0
- data/lib/couchrest/railtie.rb +24 -0
- data/lib/couchrest_model.rb +66 -0
- data/lib/rails/generators/couchrest_model.rb +16 -0
- data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
- data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
- data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
- data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
- data/spec/.gitignore +1 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/config/couchdb.yml +10 -0
- data/spec/fixtures/models/article.rb +36 -0
- data/spec/fixtures/models/base.rb +164 -0
- data/spec/fixtures/models/card.rb +19 -0
- data/spec/fixtures/models/cat.rb +23 -0
- data/spec/fixtures/models/client.rb +6 -0
- data/spec/fixtures/models/course.rb +27 -0
- data/spec/fixtures/models/event.rb +8 -0
- data/spec/fixtures/models/invoice.rb +14 -0
- data/spec/fixtures/models/key_chain.rb +5 -0
- data/spec/fixtures/models/membership.rb +4 -0
- data/spec/fixtures/models/person.rb +11 -0
- data/spec/fixtures/models/project.rb +6 -0
- data/spec/fixtures/models/question.rb +7 -0
- data/spec/fixtures/models/sale_entry.rb +9 -0
- data/spec/fixtures/models/sale_invoice.rb +14 -0
- data/spec/fixtures/models/service.rb +10 -0
- data/spec/fixtures/models/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/functional/validations_spec.rb +8 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/unit/active_model_lint_spec.rb +30 -0
- data/spec/unit/assocations_spec.rb +242 -0
- data/spec/unit/attachment_spec.rb +176 -0
- data/spec/unit/base_spec.rb +537 -0
- data/spec/unit/casted_spec.rb +72 -0
- data/spec/unit/class_proxy_spec.rb +167 -0
- data/spec/unit/collection_spec.rb +86 -0
- data/spec/unit/configuration_spec.rb +77 -0
- data/spec/unit/connection_spec.rb +148 -0
- data/spec/unit/core_extensions/time_parsing.rb +77 -0
- data/spec/unit/design_doc_spec.rb +241 -0
- data/spec/unit/designs/view_spec.rb +831 -0
- data/spec/unit/designs_spec.rb +134 -0
- data/spec/unit/dirty_spec.rb +436 -0
- data/spec/unit/embeddable_spec.rb +498 -0
- data/spec/unit/inherited_spec.rb +33 -0
- data/spec/unit/persistence_spec.rb +481 -0
- data/spec/unit/property_protection_spec.rb +192 -0
- data/spec/unit/property_spec.rb +481 -0
- data/spec/unit/proxyable_spec.rb +376 -0
- data/spec/unit/subclass_spec.rb +85 -0
- data/spec/unit/typecast_spec.rb +521 -0
- data/spec/unit/validations_spec.rb +140 -0
- data/spec/unit/view_spec.rb +367 -0
- metadata +301 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
I18n.load_path << File.join(
|
4
|
+
File.dirname(__FILE__), "validations", "locale", "en.yml"
|
5
|
+
)
|
6
|
+
|
7
|
+
module CouchRest
|
8
|
+
module Model
|
9
|
+
|
10
|
+
# This applies to both Model::Base and Model::CastedModel
|
11
|
+
module Dirty
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
include ActiveModel::Dirty
|
14
|
+
|
15
|
+
included do
|
16
|
+
# internal dirty setting - overrides global setting.
|
17
|
+
# this is used to temporarily disable dirty tracking when setting
|
18
|
+
# attributes directly, for performance reasons.
|
19
|
+
self.send(:attr_accessor, :disable_dirty)
|
20
|
+
end
|
21
|
+
|
22
|
+
def use_dirty?
|
23
|
+
doc = base_doc
|
24
|
+
doc && !doc.disable_dirty
|
25
|
+
end
|
26
|
+
|
27
|
+
def couchrest_attribute_will_change!(attr)
|
28
|
+
return if attr.nil? || !use_dirty?
|
29
|
+
attribute_will_change!(attr)
|
30
|
+
couchrest_parent_will_change!
|
31
|
+
end
|
32
|
+
|
33
|
+
def couchrest_parent_will_change!
|
34
|
+
casted_by.couchrest_attribute_will_change!(casted_by_property.name) if casted_by_property
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module DocumentQueries
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
# Load all documents that have the model_type_key's field equal to the
|
9
|
+
# name of the current class. Take the standard set of
|
10
|
+
# CouchRest::Database#view options.
|
11
|
+
def all(opts = {}, &block)
|
12
|
+
view(:all, opts, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the number of documents that have the model_type_key's field
|
16
|
+
# equal to the name of the current class. Takes the standard set of
|
17
|
+
# CouchRest::Database#view options
|
18
|
+
def count(opts = {}, &block)
|
19
|
+
all({:raw => true, :limit => 0}.merge(opts), &block)['total_rows']
|
20
|
+
end
|
21
|
+
|
22
|
+
# Load the first document that have the model_type_key's field equal to
|
23
|
+
# the name of the current class.
|
24
|
+
#
|
25
|
+
# ==== Returns
|
26
|
+
# Object:: The first object instance available
|
27
|
+
# or
|
28
|
+
# Nil:: if no instances available
|
29
|
+
#
|
30
|
+
# ==== Parameters
|
31
|
+
# opts<Hash>::
|
32
|
+
# View options, see <tt>CouchRest::Database#view</tt> options for more info.
|
33
|
+
def first(opts = {})
|
34
|
+
first_instance = self.all(opts.merge!(:limit => 1))
|
35
|
+
first_instance.empty? ? nil : first_instance.first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Load the last document that have the model_type_key's field equal to
|
39
|
+
# the name of the current class.
|
40
|
+
# It's similar to method first, just adds :descending => true
|
41
|
+
#
|
42
|
+
# ==== Returns
|
43
|
+
# Object:: The last object instance available
|
44
|
+
# or
|
45
|
+
# Nil:: if no instances available
|
46
|
+
#
|
47
|
+
# ==== Parameters
|
48
|
+
# opts<Hash>::
|
49
|
+
# View options, see <tt>CouchRest::Database#view</tt> options for more info.
|
50
|
+
def last(opts = {})
|
51
|
+
first(opts.merge!(:descending => true))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Load a document from the database by id
|
55
|
+
# No exceptions will be raised if the document isn't found
|
56
|
+
#
|
57
|
+
# ==== Returns
|
58
|
+
# Object:: if the document was found
|
59
|
+
# or
|
60
|
+
# Nil::
|
61
|
+
#
|
62
|
+
# === Parameters
|
63
|
+
# id<String, Integer>:: Document ID
|
64
|
+
# db<Database>:: optional option to pass a custom database to use
|
65
|
+
def get(id, db = database)
|
66
|
+
begin
|
67
|
+
get!(id, db)
|
68
|
+
rescue
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
alias :find :get
|
73
|
+
|
74
|
+
# Load a document from the database by id
|
75
|
+
# An exception will be raised if the document isn't found
|
76
|
+
#
|
77
|
+
# ==== Returns
|
78
|
+
# Object:: if the document was found
|
79
|
+
# or
|
80
|
+
# Exception
|
81
|
+
#
|
82
|
+
# === Parameters
|
83
|
+
# id<String, Integer>:: Document ID
|
84
|
+
# db<Database>:: optional option to pass a custom database to use
|
85
|
+
def get!(id, db = database)
|
86
|
+
raise CouchRest::Model::DocumentNotFound if id.blank?
|
87
|
+
|
88
|
+
doc = db.get id
|
89
|
+
build_from_database(doc)
|
90
|
+
rescue RestClient::ResourceNotFound
|
91
|
+
raise CouchRest::Model::DocumentNotFound
|
92
|
+
end
|
93
|
+
alias :find! :get!
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module CouchRest::Model
|
2
|
+
module Embeddable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# Include Attributes early to ensure super() will work
|
6
|
+
include CouchRest::Attributes
|
7
|
+
|
8
|
+
included do
|
9
|
+
include CouchRest::Model::Configuration
|
10
|
+
include CouchRest::Model::Properties
|
11
|
+
include CouchRest::Model::PropertyProtection
|
12
|
+
include CouchRest::Model::Associations
|
13
|
+
include CouchRest::Model::Validations
|
14
|
+
include CouchRest::Model::Callbacks
|
15
|
+
include CouchRest::Model::CastedBy
|
16
|
+
include CouchRest::Model::Dirty
|
17
|
+
include CouchRest::Model::Callbacks
|
18
|
+
|
19
|
+
class_eval do
|
20
|
+
# Override CastedBy's base_doc?
|
21
|
+
def base_doc?
|
22
|
+
false # Can never be base doc!
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Initialize a new Casted Model. Accepts the same
|
29
|
+
# options as CouchRest::Model::Base for preparing and initializing
|
30
|
+
# attributes.
|
31
|
+
def initialize(keys = {}, options = {})
|
32
|
+
super()
|
33
|
+
prepare_all_attributes(keys, options)
|
34
|
+
run_callbacks(:initialize) { self }
|
35
|
+
end
|
36
|
+
|
37
|
+
# False if the casted model has already
|
38
|
+
# been saved in the containing document
|
39
|
+
def new?
|
40
|
+
casted_by.nil? ? true : casted_by.new?
|
41
|
+
end
|
42
|
+
alias :new_record? :new?
|
43
|
+
|
44
|
+
def persisted?
|
45
|
+
!new?
|
46
|
+
end
|
47
|
+
|
48
|
+
# The to_param method is needed for rails to generate resourceful routes.
|
49
|
+
# In your controller, remember that it's actually the id of the document.
|
50
|
+
def id
|
51
|
+
return nil if base_doc.nil?
|
52
|
+
base_doc.id
|
53
|
+
end
|
54
|
+
alias :to_key :id
|
55
|
+
alias :to_param :id
|
56
|
+
|
57
|
+
# Sets the attributes from a hash
|
58
|
+
def update_attributes_without_saving(hash)
|
59
|
+
hash.each do |k, v|
|
60
|
+
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
61
|
+
end
|
62
|
+
hash.each do |k, v|
|
63
|
+
self.send("#{k}=",v)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias :attributes= :update_attributes_without_saving
|
67
|
+
|
68
|
+
end # End Embeddable
|
69
|
+
|
70
|
+
# Provide backwards compatability with previous versions (pre 1.1.0)
|
71
|
+
module CastedModel
|
72
|
+
extend ActiveSupport::Concern
|
73
|
+
included do
|
74
|
+
include CouchRest::Model::Embeddable
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module CouchRest
|
3
|
+
module Model
|
4
|
+
module Errors
|
5
|
+
|
6
|
+
class CouchRestModelError < StandardError; end
|
7
|
+
|
8
|
+
# Raised when a persisence method ending in ! fails validation. The message
|
9
|
+
# will contain the full error messages from the +Document+ in question.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# <tt>Validations.new(person.errors)</tt>
|
14
|
+
class Validations < CouchRestModelError
|
15
|
+
attr_reader :document
|
16
|
+
def initialize(document)
|
17
|
+
@document = document
|
18
|
+
super("Validation Failed: #{@document.errors.full_messages.join(", ")}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class DocumentNotFound < Errors::CouchRestModelError; end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module ExtendedAttachments
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Add a file attachment to the current document. Expects
|
7
|
+
# :file and :name to be included in the arguments.
|
8
|
+
def create_attachment(args={})
|
9
|
+
raise ArgumentError unless args[:file] && args[:name]
|
10
|
+
return if has_attachment?(args[:name])
|
11
|
+
set_attachment_attr(args)
|
12
|
+
rescue ArgumentError => e
|
13
|
+
raise ArgumentError, 'You must specify :file and :name'
|
14
|
+
end
|
15
|
+
|
16
|
+
# return all attachments
|
17
|
+
def attachments
|
18
|
+
self['_attachments'] ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# reads the data from an attachment
|
22
|
+
def read_attachment(attachment_name)
|
23
|
+
database.fetch_attachment(self, attachment_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# modifies a file attachment on the current doc
|
27
|
+
def update_attachment(args={})
|
28
|
+
raise ArgumentError unless args[:file] && args[:name]
|
29
|
+
return unless has_attachment?(args[:name])
|
30
|
+
delete_attachment(args[:name])
|
31
|
+
set_attachment_attr(args)
|
32
|
+
rescue ArgumentError => e
|
33
|
+
raise ArgumentError, 'You must specify :file and :name'
|
34
|
+
end
|
35
|
+
|
36
|
+
# deletes a file attachment from the current doc
|
37
|
+
def delete_attachment(attachment_name)
|
38
|
+
return unless attachments
|
39
|
+
if attachments.include?(attachment_name)
|
40
|
+
attribute_will_change!("_attachments")
|
41
|
+
attachments.delete attachment_name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# returns true if attachment_name exists
|
46
|
+
def has_attachment?(attachment_name)
|
47
|
+
!!(attachments && attachments[attachment_name] && !attachments[attachment_name].empty?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# returns URL to fetch the attachment from
|
51
|
+
def attachment_url(attachment_name)
|
52
|
+
return unless has_attachment?(attachment_name)
|
53
|
+
"#{database.root}/#{self.id}/#{attachment_name}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# returns URI to fetch the attachment from
|
57
|
+
def attachment_uri(attachment_name)
|
58
|
+
return unless has_attachment?(attachment_name)
|
59
|
+
"#{database.uri}/#{self.id}/#{attachment_name}"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def get_mime_type(path)
|
65
|
+
return nil if path.nil?
|
66
|
+
type = ::MIME::Types.type_for(path)
|
67
|
+
type.empty? ? nil : type.first.content_type
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_attachment_attr(args)
|
71
|
+
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
|
72
|
+
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
|
73
|
+
|
74
|
+
attribute_will_change!("_attachments")
|
75
|
+
attachments[args[:name]] = {
|
76
|
+
'content_type' => content_type,
|
77
|
+
'data' => args[:file].read
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
end # module ExtendedAttachments
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module Persistence
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Create the document. Validation is enabled by default and will return
|
7
|
+
# false if the document is not valid. If all goes well, the document will
|
8
|
+
# be returned.
|
9
|
+
def create(options = {})
|
10
|
+
return false unless perform_validations(options)
|
11
|
+
_run_create_callbacks do
|
12
|
+
_run_save_callbacks do
|
13
|
+
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
14
|
+
result = database.save_doc(self)
|
15
|
+
ret = (result["ok"] == true) ? self : false
|
16
|
+
@changed_attributes.clear if ret && @changed_attributes
|
17
|
+
ret
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates the document in the db. Raises an exception
|
23
|
+
# if the document is not created properly.
|
24
|
+
def create!(options = {})
|
25
|
+
self.class.fail_validate!(self) unless self.create(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Trigger the callbacks (before, after, around)
|
29
|
+
# only if the document isn't new
|
30
|
+
def update(options = {})
|
31
|
+
raise "Cannot save a destroyed document!" if destroyed?
|
32
|
+
raise "Calling #{self.class.name}#update on document that has not been created!" if new?
|
33
|
+
return false unless perform_validations(options)
|
34
|
+
return true if !self.disable_dirty && !self.changed?
|
35
|
+
_run_update_callbacks do
|
36
|
+
_run_save_callbacks do
|
37
|
+
result = database.save_doc(self)
|
38
|
+
ret = result["ok"] == true
|
39
|
+
@changed_attributes.clear if ret && @changed_attributes
|
40
|
+
ret
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Trigger the callbacks (before, after, around) and save the document
|
46
|
+
def save(options = {})
|
47
|
+
self.new? ? create(options) : update(options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Saves the document to the db using save. Raises an exception
|
51
|
+
# if the document is not saved properly.
|
52
|
+
def save!
|
53
|
+
self.class.fail_validate!(self) unless self.save
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Deletes the document from the database. Runs the :destroy callbacks.
|
58
|
+
def destroy
|
59
|
+
_run_destroy_callbacks do
|
60
|
+
result = database.delete_doc(self)
|
61
|
+
if result['ok']
|
62
|
+
@_destroyed = true
|
63
|
+
self.freeze
|
64
|
+
end
|
65
|
+
result['ok']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroyed?
|
70
|
+
!!@_destroyed
|
71
|
+
end
|
72
|
+
|
73
|
+
def persisted?
|
74
|
+
!new? && !destroyed?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Update the document's attributes and save. For example:
|
78
|
+
#
|
79
|
+
# doc.update_attributes :name => "Fred"
|
80
|
+
# Is the equivilent of doing the following:
|
81
|
+
#
|
82
|
+
# doc.attributes = { :name => "Fred" }
|
83
|
+
# doc.save
|
84
|
+
#
|
85
|
+
def update_attributes(hash)
|
86
|
+
update_attributes_without_saving hash
|
87
|
+
save
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reloads the attributes of this object from the database.
|
91
|
+
# It doesn't override custom instance variables.
|
92
|
+
#
|
93
|
+
# Returns self.
|
94
|
+
def reload
|
95
|
+
prepare_all_attributes(database.get(id), :directly_set_attributes => true)
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def perform_validations(options = {})
|
102
|
+
perform_validation = case options
|
103
|
+
when Hash
|
104
|
+
options[:validate] != false
|
105
|
+
else
|
106
|
+
options
|
107
|
+
end
|
108
|
+
perform_validation ? valid? : true
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
module ClassMethods
|
113
|
+
|
114
|
+
# Creates a new instance, bypassing attribute protection and
|
115
|
+
# uses the type field to determine which model to use to instanatiate
|
116
|
+
# the new object.
|
117
|
+
#
|
118
|
+
# ==== Returns
|
119
|
+
# a document instance
|
120
|
+
#
|
121
|
+
def build_from_database(doc = {}, options = {}, &block)
|
122
|
+
src = doc[model_type_key]
|
123
|
+
base = (src.blank? || src == self.to_s) ? self : src.constantize
|
124
|
+
base.new(doc, options.merge(:directly_set_attributes => true), &block)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Defines an instance and save it directly to the database
|
128
|
+
#
|
129
|
+
# ==== Returns
|
130
|
+
# returns the reloaded document
|
131
|
+
def create(attributes = {}, &block)
|
132
|
+
instance = new(attributes, &block)
|
133
|
+
instance.create
|
134
|
+
instance
|
135
|
+
end
|
136
|
+
|
137
|
+
# Defines an instance and save it directly to the database
|
138
|
+
#
|
139
|
+
# ==== Returns
|
140
|
+
# returns the reloaded document or raises an exception
|
141
|
+
def create!(attributes = {}, &block)
|
142
|
+
instance = new(attributes, &block)
|
143
|
+
instance.create!
|
144
|
+
instance
|
145
|
+
end
|
146
|
+
|
147
|
+
# Name a method that will be called before the document is first saved,
|
148
|
+
# which returns a string to be used for the document's <tt>_id</tt>.
|
149
|
+
#
|
150
|
+
# Because CouchDB enforces a constraint that each id must be unique,
|
151
|
+
# this can be used to enforce eg: uniq usernames. Note that this id
|
152
|
+
# must be globally unique across all document types which share a
|
153
|
+
# database, so if you'd like to scope uniqueness to this class, you
|
154
|
+
# should use the class name as part of the unique id.
|
155
|
+
def unique_id(method = nil, &block)
|
156
|
+
if method
|
157
|
+
define_method :set_unique_id do
|
158
|
+
self['_id'] ||= self.send(method)
|
159
|
+
end
|
160
|
+
elsif block
|
161
|
+
define_method :set_unique_id do
|
162
|
+
uniqid = block.call(self)
|
163
|
+
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
164
|
+
self['_id'] ||= uniqid
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Raise an error if validation failed.
|
170
|
+
def fail_validate!(document)
|
171
|
+
raise Errors::Validations.new(document)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|