mongomodel 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +47 -0
- data/bin/console +45 -0
- data/lib/mongomodel.rb +92 -0
- data/lib/mongomodel/attributes/mongo.rb +40 -0
- data/lib/mongomodel/attributes/store.rb +30 -0
- data/lib/mongomodel/attributes/typecasting.rb +51 -0
- data/lib/mongomodel/concerns/abstract_class.rb +17 -0
- data/lib/mongomodel/concerns/activemodel.rb +11 -0
- data/lib/mongomodel/concerns/associations.rb +103 -0
- data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
- data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
- data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
- data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
- data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
- data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
- data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
- data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
- data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
- data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
- data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
- data/lib/mongomodel/concerns/attributes.rb +85 -0
- data/lib/mongomodel/concerns/callbacks.rb +294 -0
- data/lib/mongomodel/concerns/logging.rb +15 -0
- data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
- data/lib/mongomodel/concerns/properties.rb +69 -0
- data/lib/mongomodel/concerns/record_status.rb +42 -0
- data/lib/mongomodel/concerns/timestamps.rb +32 -0
- data/lib/mongomodel/concerns/validations.rb +38 -0
- data/lib/mongomodel/concerns/validations/associated.rb +46 -0
- data/lib/mongomodel/document.rb +20 -0
- data/lib/mongomodel/document/callbacks.rb +46 -0
- data/lib/mongomodel/document/dynamic_finders.rb +88 -0
- data/lib/mongomodel/document/finders.rb +82 -0
- data/lib/mongomodel/document/indexes.rb +91 -0
- data/lib/mongomodel/document/optimistic_locking.rb +48 -0
- data/lib/mongomodel/document/persistence.rb +143 -0
- data/lib/mongomodel/document/scopes.rb +161 -0
- data/lib/mongomodel/document/validations.rb +68 -0
- data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
- data/lib/mongomodel/embedded_document.rb +42 -0
- data/lib/mongomodel/locale/en.yml +55 -0
- data/lib/mongomodel/support/collection.rb +109 -0
- data/lib/mongomodel/support/configuration.rb +35 -0
- data/lib/mongomodel/support/core_extensions.rb +10 -0
- data/lib/mongomodel/support/exceptions.rb +25 -0
- data/lib/mongomodel/support/mongo_options.rb +177 -0
- data/lib/mongomodel/support/types.rb +35 -0
- data/lib/mongomodel/support/types/array.rb +11 -0
- data/lib/mongomodel/support/types/boolean.rb +25 -0
- data/lib/mongomodel/support/types/custom.rb +38 -0
- data/lib/mongomodel/support/types/date.rb +20 -0
- data/lib/mongomodel/support/types/float.rb +13 -0
- data/lib/mongomodel/support/types/hash.rb +18 -0
- data/lib/mongomodel/support/types/integer.rb +13 -0
- data/lib/mongomodel/support/types/object.rb +21 -0
- data/lib/mongomodel/support/types/string.rb +9 -0
- data/lib/mongomodel/support/types/symbol.rb +9 -0
- data/lib/mongomodel/support/types/time.rb +12 -0
- data/lib/mongomodel/version.rb +3 -0
- data/spec/mongomodel/attributes/store_spec.rb +273 -0
- data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
- data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
- data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
- data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
- data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
- data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
- data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
- data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
- data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
- data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
- data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
- data/spec/mongomodel/concerns/logging_spec.rb +20 -0
- data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
- data/spec/mongomodel/concerns/properties_spec.rb +29 -0
- data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
- data/spec/mongomodel/concerns/validations_spec.rb +159 -0
- data/spec/mongomodel/document/callbacks_spec.rb +80 -0
- data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
- data/spec/mongomodel/document/finders_spec.rb +231 -0
- data/spec/mongomodel/document/indexes_spec.rb +121 -0
- data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
- data/spec/mongomodel/document/persistence_spec.rb +319 -0
- data/spec/mongomodel/document/scopes_spec.rb +204 -0
- data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
- data/spec/mongomodel/document/validations_spec.rb +132 -0
- data/spec/mongomodel/document_spec.rb +74 -0
- data/spec/mongomodel/embedded_document_spec.rb +66 -0
- data/spec/mongomodel/mongomodel_spec.rb +33 -0
- data/spec/mongomodel/support/collection_spec.rb +248 -0
- data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
- data/spec/mongomodel/support/property_spec.rb +83 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/specdoc.opts +6 -0
- data/spec/support/callbacks.rb +44 -0
- data/spec/support/helpers/define_class.rb +24 -0
- data/spec/support/helpers/specs_for.rb +11 -0
- data/spec/support/matchers/be_a_subclass_of.rb +5 -0
- data/spec/support/matchers/respond_to_boolean.rb +17 -0
- data/spec/support/matchers/run_callbacks.rb +20 -0
- data/spec/support/models.rb +23 -0
- data/spec/support/time.rb +6 -0
- metadata +232 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
|
2
|
+
|
|
3
|
+
module MongoModel
|
|
4
|
+
module DocumentExtensions
|
|
5
|
+
module Indexes
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
index :_type
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def property(name, *args, &block) #:nodoc:
|
|
14
|
+
property = super
|
|
15
|
+
index(name) if property.options[:index]
|
|
16
|
+
property
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def index(*args)
|
|
20
|
+
index = Index.new(*args)
|
|
21
|
+
indexes << index
|
|
22
|
+
@_indexes_initialized = false
|
|
23
|
+
index
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def indexes
|
|
27
|
+
read_inheritable_attribute(:indexes) || write_inheritable_attribute(:indexes, [])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def indexes_initialized?
|
|
31
|
+
@_indexes_initialized == true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def ensure_indexes!
|
|
35
|
+
indexes.each do |index|
|
|
36
|
+
collection.create_index(*index.to_args)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@_indexes_initialized = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def _find(*)
|
|
44
|
+
ensure_indexes! unless indexes_initialized?
|
|
45
|
+
super
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Index
|
|
52
|
+
def initialize(*keys)
|
|
53
|
+
options = keys.extract_options!
|
|
54
|
+
@unique = options.delete(:unique)
|
|
55
|
+
|
|
56
|
+
keys.each do |key|
|
|
57
|
+
self.keys[key.to_sym] = :ascending
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
options.each do |key, order|
|
|
61
|
+
self.keys[key.to_sym] = order
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def keys
|
|
66
|
+
@keys ||= OrderedHash.new
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def unique?
|
|
70
|
+
@unique
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_args
|
|
74
|
+
args = []
|
|
75
|
+
|
|
76
|
+
if keys.size == 1 && keys.all? { |k, o| o == :ascending }
|
|
77
|
+
args << keys.keys.first
|
|
78
|
+
else
|
|
79
|
+
args << keys.map { |k, o| [k, o == :ascending ? 1 : -1] }.sort_by { |k| k.first.to_s }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
args << true if unique?
|
|
83
|
+
|
|
84
|
+
args
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ==(other)
|
|
88
|
+
other.is_a?(Index) && to_args == other.to_args
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module DocumentExtensions
|
|
3
|
+
module OptimisticLocking
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def locking_enabled?
|
|
8
|
+
properties.include?(:_lock_version)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def lock_optimistically=(value)
|
|
12
|
+
if value == true
|
|
13
|
+
property :_lock_version, Integer, :default => 0, :internal => true, :protected => true
|
|
14
|
+
before_save :increment_lock_version, :if => :locking_enabled?
|
|
15
|
+
else
|
|
16
|
+
properties.delete(:_lock_version)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def locking_enabled?
|
|
22
|
+
self.class.locking_enabled?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
def increment_lock_version
|
|
27
|
+
self._lock_version += 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def save_to_collection
|
|
31
|
+
if locking_enabled? && _lock_version > 1
|
|
32
|
+
begin
|
|
33
|
+
collection.update({ '_id' => id, '_lock_version' => _lock_version-1 }, to_mongo)
|
|
34
|
+
success = database.last_status['updatedExisting']
|
|
35
|
+
|
|
36
|
+
self._lock_version -= 1 unless success
|
|
37
|
+
|
|
38
|
+
success
|
|
39
|
+
rescue Mongo::OperationFailure => e
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module DocumentExtensions
|
|
3
|
+
module Persistence
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
undef_method :id if method_defined?(:id)
|
|
8
|
+
property :id, String, :as => '_id', :default => lambda { ::Mongo::ObjectID.new.to_s }
|
|
9
|
+
|
|
10
|
+
class_inheritable_writer :collection_name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def save
|
|
14
|
+
create_or_update
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def save!
|
|
18
|
+
create_or_update || raise(DocumentNotSaved)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete
|
|
22
|
+
self.class.delete(id)
|
|
23
|
+
set_destroyed(true)
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def destroy
|
|
28
|
+
delete
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Updates all the attributes from the passed-in Hash and saves the document.
|
|
32
|
+
# If the object is invalid, the saving will fail and false will be returned.
|
|
33
|
+
def update_attributes(attributes)
|
|
34
|
+
self.attributes = attributes
|
|
35
|
+
save
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Updates a single attribute and saves the document without going through the normal validation procedure.
|
|
39
|
+
# This is especially useful for boolean flags on existing documents.
|
|
40
|
+
def update_attribute(name, value)
|
|
41
|
+
send("#{name}=", value)
|
|
42
|
+
save(false)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def collection
|
|
46
|
+
self.class.collection
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def database
|
|
50
|
+
self.class.database
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module ClassMethods
|
|
54
|
+
def create(attributes={}, &block)
|
|
55
|
+
if attributes.is_a?(Array)
|
|
56
|
+
attributes.map { |attrs| create(attrs, &block) }
|
|
57
|
+
else
|
|
58
|
+
instance = new(attributes, &block)
|
|
59
|
+
instance.save
|
|
60
|
+
instance
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def delete(id_or_conditions)
|
|
65
|
+
collection.remove(MongoOptions.new(self, :conditions => id_to_conditions(id_or_conditions)).selector)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def destroy(id_or_conditions)
|
|
69
|
+
find(:all, :conditions => id_to_conditions(id_or_conditions)).each { |instance| instance.destroy }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def from_mongo(document)
|
|
73
|
+
instance = super
|
|
74
|
+
instance.send(:instantiate, document)
|
|
75
|
+
instance
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def collection_name
|
|
79
|
+
if superclass.abstract_class?
|
|
80
|
+
read_inheritable_attribute(:collection_name) || name.tableize.gsub(/\//, '.')
|
|
81
|
+
else
|
|
82
|
+
superclass.collection_name
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def collection
|
|
87
|
+
@_collection ||= database.collection(collection_name)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def database
|
|
91
|
+
MongoModel.database
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def save_safely?
|
|
95
|
+
@save_safely
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def save_safely=(val)
|
|
99
|
+
@save_safely = val
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
def id_to_conditions(id_or_conditions)
|
|
104
|
+
case id_or_conditions
|
|
105
|
+
when String
|
|
106
|
+
{ :id => id_or_conditions }
|
|
107
|
+
when Array
|
|
108
|
+
{ :id.in => id_or_conditions }
|
|
109
|
+
else
|
|
110
|
+
id_or_conditions
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
def create_or_update
|
|
117
|
+
result = new_record? ? create : update
|
|
118
|
+
result != false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def create
|
|
122
|
+
save_to_collection
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def update
|
|
126
|
+
save_to_collection
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def save_to_collection
|
|
130
|
+
collection.save(to_mongo, :safe => self.class.save_safely?)
|
|
131
|
+
set_new_record(false)
|
|
132
|
+
true
|
|
133
|
+
rescue Mongo::OperationFailure => e
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def instantiate(document)
|
|
138
|
+
attributes.from_mongo!(document)
|
|
139
|
+
set_new_record(false)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
require 'active_support/core_ext/module/aliasing'
|
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
|
3
|
+
require 'active_support/core_ext/object/metaclass'
|
|
4
|
+
|
|
5
|
+
module MongoModel
|
|
6
|
+
module DocumentExtensions
|
|
7
|
+
module Scopes
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
class << self
|
|
12
|
+
[:find, :count].each do |method|
|
|
13
|
+
alias_method_chain method, :scope
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
named_scope :scoped, lambda { |scope| scope }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
protected
|
|
22
|
+
#
|
|
23
|
+
def named_scope(name, options={})
|
|
24
|
+
named_scopes[name] = options
|
|
25
|
+
|
|
26
|
+
metaclass.instance_eval do
|
|
27
|
+
define_method(name) do |*args|
|
|
28
|
+
scope(name).apply(*args)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
def default_scope(options={})
|
|
35
|
+
if options.empty?
|
|
36
|
+
read_inheritable_attribute(:default_scope) || write_inheritable_attribute(:default_scope, Scope.new(self))
|
|
37
|
+
else
|
|
38
|
+
write_inheritable_attribute(:default_scope, default_scope.merge(:find => options))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
def with_scope(options={}, &block)
|
|
44
|
+
push_scope(current_scope.merge(options), &block)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
def with_exclusive_scope(options={}, &block)
|
|
49
|
+
push_scope(Scope.new(self, options), &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
def push_scope(scope, &block)
|
|
54
|
+
scopes << scope
|
|
55
|
+
yield
|
|
56
|
+
ensure
|
|
57
|
+
scopes.pop
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def find_with_scope(*args)
|
|
61
|
+
options = args.extract_options!
|
|
62
|
+
options = current_scope.options_for(:find).deep_merge(options)
|
|
63
|
+
|
|
64
|
+
find_without_scope(*(args << options))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def count_with_scope(conditions={})
|
|
68
|
+
scope_conditions = current_scope.options_for(:find)[:conditions] || {}
|
|
69
|
+
|
|
70
|
+
count_without_scope(scope_conditions.deep_merge(conditions))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def named_scopes
|
|
74
|
+
read_inheritable_attribute(:named_scopes) || write_inheritable_attribute(:named_scopes, {})
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def scope(name)
|
|
78
|
+
Scope.new(self, named_scopes[name]) if named_scopes[name]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def scopes
|
|
82
|
+
Thread.current[:"#{self}_scopes"] ||= [ default_scope.dup ]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def current_scope
|
|
86
|
+
scopes.last
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class Scope
|
|
92
|
+
attr_reader :model, :options
|
|
93
|
+
|
|
94
|
+
delegate :with_scope, :with_exclusive_scope, :scope, :to => :model
|
|
95
|
+
delegate :inspect, :to => :proxy_found
|
|
96
|
+
|
|
97
|
+
def initialize(model, options={})
|
|
98
|
+
if options.is_a?(Proc) || options.has_key?(:find) || options.has_key?(:create)
|
|
99
|
+
@options = options
|
|
100
|
+
else
|
|
101
|
+
@exclusive = options.delete(:exclusive)
|
|
102
|
+
@options = { :find => options }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
@model = model
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def merge(scope)
|
|
109
|
+
raise ArgumentError, "Scope must be applied before it can be merged" unless options.is_a?(Hash)
|
|
110
|
+
options_to_merge = scope.is_a?(self.class) ? scope.options : scope
|
|
111
|
+
self.class.new(model, options.deep_merge(options_to_merge))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def merge!(scope)
|
|
115
|
+
raise ArgumentError, "Scope must be applied before it can be merged" unless options.is_a?(Hash)
|
|
116
|
+
options_to_merge = scope.is_a?(self.class) ? scope.options : scope
|
|
117
|
+
options.deep_merge!(options_to_merge)
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def reload
|
|
122
|
+
@found = nil
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def apply(*args)
|
|
127
|
+
if options.is_a?(Hash)
|
|
128
|
+
reload
|
|
129
|
+
else
|
|
130
|
+
self.class.new(model, options.call(*args))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def options_for(action=:find)
|
|
135
|
+
options[action] || {}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def exclusive?
|
|
139
|
+
@exclusive == true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
protected
|
|
143
|
+
def proxy_found
|
|
144
|
+
@found ||= find(:all)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
def method_missing(method, *args, &block)
|
|
149
|
+
if scope = scope(method)
|
|
150
|
+
scope.exclusive? ? scope : merge(scope.apply(*args))
|
|
151
|
+
else
|
|
152
|
+
send(scope_type, options) { model.send(method, *args, &block) }
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def scope_type
|
|
157
|
+
exclusive? ? :with_exclusive_scope : :with_scope
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module DocumentExtensions
|
|
3
|
+
module Validations
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
alias_method_chain :save, :validation
|
|
8
|
+
alias_method_chain :save!, :validation
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def property(name, *args, &block) #:nodoc:
|
|
13
|
+
property = super
|
|
14
|
+
|
|
15
|
+
validates_uniqueness_of(name) if property.options[:unique]
|
|
16
|
+
|
|
17
|
+
property
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates an object just like Document.create but calls save! instead of save
|
|
21
|
+
# so an exception is raised if the document is invalid.
|
|
22
|
+
def create!(attributes={}, &block)
|
|
23
|
+
if attributes.is_a?(Array)
|
|
24
|
+
attributes.map { |attrs| create!(attrs, &block) }
|
|
25
|
+
else
|
|
26
|
+
object = new(attributes, &block)
|
|
27
|
+
object.save!
|
|
28
|
+
object
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The validation process on save can be skipped by passing false. The regular Document#save method is
|
|
34
|
+
# replaced with this when the validations module is mixed in, which it is by default.
|
|
35
|
+
def save_with_validation(perform_validation = true)
|
|
36
|
+
if perform_validation && valid? || !perform_validation
|
|
37
|
+
begin
|
|
38
|
+
save_without_validation
|
|
39
|
+
rescue DocumentNotSaved
|
|
40
|
+
valid?
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Attempts to save the document just like Document#save but will raise a DocumentInvalid exception
|
|
49
|
+
# instead of returning false if the document is not valid.
|
|
50
|
+
def save_with_validation!
|
|
51
|
+
if valid?
|
|
52
|
+
begin
|
|
53
|
+
save_without_validation!
|
|
54
|
+
rescue DocumentNotSaved => e
|
|
55
|
+
raise valid? ? e : DocumentInvalid.new(self)
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
raise DocumentInvalid.new(self)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
|
|
66
|
+
filename = File.basename(path)
|
|
67
|
+
require "mongomodel/document/validations/#{filename}"
|
|
68
|
+
end
|