lookout-mongo_mapper 0.11.3

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.
Files changed (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +33 -0
  3. data/UPGRADES +26 -0
  4. data/bin/mmconsole +59 -0
  5. data/examples/attr_accessible.rb +22 -0
  6. data/examples/attr_protected.rb +22 -0
  7. data/examples/cache_key.rb +24 -0
  8. data/examples/custom_types.rb +24 -0
  9. data/examples/identity_map.rb +33 -0
  10. data/examples/identity_map/automatic.rb +2 -0
  11. data/examples/keys.rb +40 -0
  12. data/examples/modifiers/set.rb +25 -0
  13. data/examples/plugins.rb +38 -0
  14. data/examples/querying.rb +35 -0
  15. data/examples/safe.rb +43 -0
  16. data/examples/scopes.rb +52 -0
  17. data/examples/validating/embedded_docs.rb +29 -0
  18. data/lib/mongo_mapper.rb +94 -0
  19. data/lib/mongo_mapper/connection.rb +96 -0
  20. data/lib/mongo_mapper/document.rb +42 -0
  21. data/lib/mongo_mapper/embedded_document.rb +32 -0
  22. data/lib/mongo_mapper/exceptions.rb +30 -0
  23. data/lib/mongo_mapper/extensions/array.rb +19 -0
  24. data/lib/mongo_mapper/extensions/binary.rb +22 -0
  25. data/lib/mongo_mapper/extensions/boolean.rb +44 -0
  26. data/lib/mongo_mapper/extensions/date.rb +25 -0
  27. data/lib/mongo_mapper/extensions/float.rb +14 -0
  28. data/lib/mongo_mapper/extensions/hash.rb +14 -0
  29. data/lib/mongo_mapper/extensions/integer.rb +19 -0
  30. data/lib/mongo_mapper/extensions/kernel.rb +9 -0
  31. data/lib/mongo_mapper/extensions/nil_class.rb +18 -0
  32. data/lib/mongo_mapper/extensions/object.rb +26 -0
  33. data/lib/mongo_mapper/extensions/object_id.rb +32 -0
  34. data/lib/mongo_mapper/extensions/set.rb +20 -0
  35. data/lib/mongo_mapper/extensions/string.rb +18 -0
  36. data/lib/mongo_mapper/extensions/time.rb +28 -0
  37. data/lib/mongo_mapper/locale/en.yml +5 -0
  38. data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
  39. data/lib/mongo_mapper/plugins.rb +22 -0
  40. data/lib/mongo_mapper/plugins/accessible.rb +52 -0
  41. data/lib/mongo_mapper/plugins/active_model.rb +18 -0
  42. data/lib/mongo_mapper/plugins/associations.rb +90 -0
  43. data/lib/mongo_mapper/plugins/associations/base.rb +92 -0
  44. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +54 -0
  45. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +34 -0
  46. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
  47. data/lib/mongo_mapper/plugins/associations/collection.rb +27 -0
  48. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +44 -0
  49. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +133 -0
  50. data/lib/mongo_mapper/plugins/associations/many_association.rb +63 -0
  51. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  52. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +118 -0
  53. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
  54. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
  55. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
  56. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +22 -0
  57. data/lib/mongo_mapper/plugins/associations/one_association.rb +48 -0
  58. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
  59. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
  60. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +95 -0
  61. data/lib/mongo_mapper/plugins/associations/proxy.rb +134 -0
  62. data/lib/mongo_mapper/plugins/associations/single_association.rb +46 -0
  63. data/lib/mongo_mapper/plugins/caching.rb +21 -0
  64. data/lib/mongo_mapper/plugins/callbacks.rb +29 -0
  65. data/lib/mongo_mapper/plugins/clone.rb +22 -0
  66. data/lib/mongo_mapper/plugins/dirty.rb +60 -0
  67. data/lib/mongo_mapper/plugins/document.rb +41 -0
  68. data/lib/mongo_mapper/plugins/dynamic_querying.rb +45 -0
  69. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
  70. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +56 -0
  71. data/lib/mongo_mapper/plugins/embedded_document.rb +53 -0
  72. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  73. data/lib/mongo_mapper/plugins/identity_map.rb +128 -0
  74. data/lib/mongo_mapper/plugins/indexes.rb +13 -0
  75. data/lib/mongo_mapper/plugins/inspect.rb +16 -0
  76. data/lib/mongo_mapper/plugins/keys.rb +313 -0
  77. data/lib/mongo_mapper/plugins/keys/key.rb +61 -0
  78. data/lib/mongo_mapper/plugins/logger.rb +18 -0
  79. data/lib/mongo_mapper/plugins/modifiers.rb +134 -0
  80. data/lib/mongo_mapper/plugins/pagination.rb +16 -0
  81. data/lib/mongo_mapper/plugins/persistence.rb +69 -0
  82. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  83. data/lib/mongo_mapper/plugins/querying.rb +165 -0
  84. data/lib/mongo_mapper/plugins/querying/decorator.rb +36 -0
  85. data/lib/mongo_mapper/plugins/rails.rb +58 -0
  86. data/lib/mongo_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  87. data/lib/mongo_mapper/plugins/safe.rb +28 -0
  88. data/lib/mongo_mapper/plugins/sci.rb +36 -0
  89. data/lib/mongo_mapper/plugins/scopes.rb +27 -0
  90. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  91. data/lib/mongo_mapper/plugins/timestamps.rb +22 -0
  92. data/lib/mongo_mapper/plugins/touch.rb +18 -0
  93. data/lib/mongo_mapper/plugins/userstamps.rb +18 -0
  94. data/lib/mongo_mapper/plugins/validations.rb +86 -0
  95. data/lib/mongo_mapper/railtie.rb +48 -0
  96. data/lib/mongo_mapper/railtie/database.rake +65 -0
  97. data/lib/mongo_mapper/translation.rb +10 -0
  98. data/lib/mongo_mapper/version.rb +4 -0
  99. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  100. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  101. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  102. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +13 -0
  103. data/test/_NOTE_ON_TESTING +1 -0
  104. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +64 -0
  105. data/test/functional/associations/test_belongs_to_proxy.rb +238 -0
  106. data/test/functional/associations/test_in_array_proxy.rb +349 -0
  107. data/test/functional/associations/test_many_documents_as_proxy.rb +231 -0
  108. data/test/functional/associations/test_many_documents_proxy.rb +866 -0
  109. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +239 -0
  110. data/test/functional/associations/test_many_embedded_proxy.rb +289 -0
  111. data/test/functional/associations/test_many_polymorphic_proxy.rb +303 -0
  112. data/test/functional/associations/test_one_as_proxy.rb +491 -0
  113. data/test/functional/associations/test_one_embedded_polymorphic_proxy.rb +208 -0
  114. data/test/functional/associations/test_one_embedded_proxy.rb +100 -0
  115. data/test/functional/associations/test_one_proxy.rb +383 -0
  116. data/test/functional/test_accessible.rb +198 -0
  117. data/test/functional/test_associations.rb +46 -0
  118. data/test/functional/test_binary.rb +27 -0
  119. data/test/functional/test_caching.rb +77 -0
  120. data/test/functional/test_callbacks.rb +232 -0
  121. data/test/functional/test_dirty.rb +301 -0
  122. data/test/functional/test_document.rb +282 -0
  123. data/test/functional/test_dynamic_querying.rb +75 -0
  124. data/test/functional/test_embedded_document.rb +288 -0
  125. data/test/functional/test_equality.rb +20 -0
  126. data/test/functional/test_identity_map.rb +513 -0
  127. data/test/functional/test_indexes.rb +50 -0
  128. data/test/functional/test_logger.rb +20 -0
  129. data/test/functional/test_modifiers.rb +537 -0
  130. data/test/functional/test_pagination.rb +91 -0
  131. data/test/functional/test_protected.rb +201 -0
  132. data/test/functional/test_querying.rb +935 -0
  133. data/test/functional/test_safe.rb +76 -0
  134. data/test/functional/test_sci.rb +240 -0
  135. data/test/functional/test_scopes.rb +171 -0
  136. data/test/functional/test_timestamps.rb +62 -0
  137. data/test/functional/test_touch.rb +125 -0
  138. data/test/functional/test_userstamps.rb +44 -0
  139. data/test/functional/test_validations.rb +414 -0
  140. data/test/models.rb +261 -0
  141. data/test/support/railtie.rb +4 -0
  142. data/test/support/railtie/autoloaded.rb +2 -0
  143. data/test/support/railtie/not_autoloaded.rb +3 -0
  144. data/test/support/railtie/parent.rb +3 -0
  145. data/test/test_active_model_lint.rb +18 -0
  146. data/test/test_helper.rb +93 -0
  147. data/test/unit/associations/test_base.rb +146 -0
  148. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  149. data/test/unit/associations/test_many_association.rb +63 -0
  150. data/test/unit/associations/test_one_association.rb +47 -0
  151. data/test/unit/associations/test_proxy.rb +100 -0
  152. data/test/unit/serializers/test_json_serializer.rb +216 -0
  153. data/test/unit/serializers/test_xml_serializer.rb +196 -0
  154. data/test/unit/test_clone.rb +69 -0
  155. data/test/unit/test_document.rb +249 -0
  156. data/test/unit/test_dynamic_finder.rb +125 -0
  157. data/test/unit/test_embedded_document.rb +682 -0
  158. data/test/unit/test_equality.rb +38 -0
  159. data/test/unit/test_exceptions.rb +12 -0
  160. data/test/unit/test_extensions.rb +380 -0
  161. data/test/unit/test_identity_map_middleware.rb +34 -0
  162. data/test/unit/test_inspect.rb +47 -0
  163. data/test/unit/test_key.rb +205 -0
  164. data/test/unit/test_keys.rb +65 -0
  165. data/test/unit/test_mongo_mapper.rb +143 -0
  166. data/test/unit/test_pagination.rb +11 -0
  167. data/test/unit/test_plugins.rb +89 -0
  168. data/test/unit/test_rails.rb +183 -0
  169. data/test/unit/test_rails_compatibility.rb +38 -0
  170. data/test/unit/test_rails_reflect_on_association.rb +118 -0
  171. data/test/unit/test_railtie.rb +66 -0
  172. data/test/unit/test_serialization.rb +166 -0
  173. data/test/unit/test_time_zones.rb +44 -0
  174. data/test/unit/test_translation.rb +27 -0
  175. data/test/unit/test_validations.rb +562 -0
  176. metadata +285 -0
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Querying
5
+ Methods = Plucky::Methods + [:find!]
6
+
7
+ module Decorator
8
+ include DynamicQuerying::ClassMethods
9
+
10
+ def model(model=nil)
11
+ return @model if model.nil?
12
+ @model = model
13
+ self
14
+ end
15
+
16
+ def find!(*ids)
17
+ raise DocumentNotFound, "Couldn't find without an ID" if ids.size == 0
18
+
19
+ find(*ids).tap do |result|
20
+ if result.nil? || ids.size != Array(result).size
21
+ raise DocumentNotFound, "Couldn't find all of the ids (#{ids.join(',')}). Found #{Array(result).size}, but was expecting #{ids.size}"
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+ def method_missing(method, *args, &block)
28
+ return super unless model.respond_to?(method)
29
+ result = model.send(method, *args, &block)
30
+ return super unless result.is_a?(Plucky::Query)
31
+ merge(result)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Rails
5
+ autoload :ActiveRecordAssociationAdapter, "mongo_mapper/plugins/rails/active_record_association_adapter"
6
+ extend ActiveSupport::Concern
7
+
8
+ def to_param
9
+ id.to_s if persisted?
10
+ end
11
+
12
+ def to_model
13
+ self
14
+ end
15
+
16
+ def to_key
17
+ [id] if persisted?
18
+ end
19
+
20
+ def new_record?
21
+ new?
22
+ end
23
+
24
+ def read_attribute(name)
25
+ self[name]
26
+ end
27
+
28
+ def read_attribute_before_type_cast(name)
29
+ read_key_before_type_cast(name)
30
+ end
31
+
32
+ def write_attribute(name, value)
33
+ self[name] = value
34
+ end
35
+
36
+ module ClassMethods
37
+ def has_one(*args)
38
+ one(*args)
39
+ end
40
+
41
+ def has_many(*args, &extension)
42
+ many(*args, &extension)
43
+ end
44
+
45
+ def column_names
46
+ keys.keys
47
+ end
48
+
49
+ # Returns returns an ActiveRecordAssociationAdapter for an association. This adapter has an API that is a
50
+ # subset of ActiveRecord::Reflection::AssociationReflection. This allows MongoMapper to be used with the
51
+ # association helpers in gems like simple_form and formtastic.
52
+ def reflect_on_association(name)
53
+ ActiveRecordAssociationAdapter.for_association(associations[name]) if associations[name]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Rails
5
+ class ActiveRecordAssociationAdapter
6
+ attr_reader :klass, :macro, :name, :options
7
+
8
+ def self.for_association(association)
9
+ macro = case association
10
+ when MongoMapper::Plugins::Associations::BelongsToAssociation
11
+ :belongs_to
12
+ when MongoMapper::Plugins::Associations::ManyAssociation
13
+ :has_many
14
+ when MongoMapper::Plugins::Associations::OneAssociation
15
+ :has_one
16
+ else
17
+ raise "no #{name} for association of type #{association.class}"
18
+ end
19
+
20
+ new(association, macro)
21
+ end
22
+
23
+ def initialize(association, macro)
24
+ @klass, @name = association.klass, association.name
25
+ # only include compatible options
26
+ @options = association.options.slice(:conditions, :order)
27
+
28
+ @macro = macro
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Safe
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def inherited(subclass)
9
+ super
10
+ subclass.safe if safe?
11
+ end
12
+
13
+ def safe
14
+ @safe = true
15
+ end
16
+
17
+ def safe?
18
+ @safe == true
19
+ end
20
+ end
21
+
22
+ def save_to_collection(options={})
23
+ options[:safe] = self.class.safe? unless options.key?(:safe)
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Sci
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ extend ActiveSupport::DescendantsTracker
9
+ end
10
+
11
+ module ClassMethods
12
+ def inherited(subclass)
13
+ key :_type, String unless key?(:_type)
14
+ subclass.instance_variable_set("@single_collection_inherited", true)
15
+ subclass.set_collection_name(collection_name) unless subclass.embeddable?
16
+ super
17
+ end
18
+
19
+ def single_collection_inherited?
20
+ @single_collection_inherited == true
21
+ end
22
+
23
+ def query(options={})
24
+ super.tap do |query|
25
+ query[:_type] = {'$in' => [name] + descendants.map(&:name)} if single_collection_inherited?
26
+ end
27
+ end
28
+ end
29
+
30
+ def initialize(*args)
31
+ super
32
+ write_key :_type, self.class.name if self.class.key?(:_type)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Scopes
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :_scopes
9
+ end
10
+
11
+ module ClassMethods
12
+ def scope(name, scope_options={})
13
+ scopes[name] = lambda do |*args|
14
+ result = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
15
+ result = self.query(result) if result.is_a?(Hash)
16
+ self.query.merge(result)
17
+ end
18
+ singleton_class.send :define_method, name, &scopes[name]
19
+ end
20
+
21
+ def scopes
22
+ self._scopes || self._scopes = {}
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: UTF-8
2
+ require 'active_model/serializers/json'
3
+ require 'active_model/serializers/xml'
4
+
5
+ module MongoMapper
6
+ module Plugins
7
+ module Serialization
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ include ::ActiveModel::Serializers::JSON
12
+ include ::ActiveModel::Serializers::Xml
13
+ self.include_root_in_json = false
14
+ end
15
+
16
+ def serializable_attributes
17
+ attributes.keys.map { |k| k.to_s } + ['id'] - ['_id']
18
+ end
19
+
20
+ def serializable_hash(options = nil)
21
+ options ||= {}
22
+
23
+ options[:only] = Array.wrap(options[:only]).map { |k| k.to_s }
24
+ options[:except] = Array.wrap(options[:except]).map { |k| k.to_s }
25
+
26
+ attribute_names = serializable_attributes
27
+
28
+ if options[:only].any?
29
+ attribute_names &= options[:only]
30
+ elsif options[:except].any?
31
+ attribute_names -= options[:except]
32
+ end
33
+
34
+ attribute_names += Array.wrap(options[:methods]).map { |m| m.to_s }.select do |method|
35
+ respond_to?(method)
36
+ end
37
+
38
+ hash = attribute_names.sort.inject({}) do |hash, name|
39
+ value = send(name)
40
+ hash[name] = if value.is_a?(Array)
41
+ value.map {|v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
42
+ elsif value.respond_to?(:serializable_hash)
43
+ value.serializable_hash
44
+ else
45
+ value
46
+ end
47
+ hash
48
+ end
49
+
50
+ serializable_add_includes(options) do |association, records, opts|
51
+ hash[association.to_s] = records.is_a?(Array) ?
52
+ records.map { |r| r.serializable_hash(opts) } :
53
+ records.serializable_hash(opts)
54
+ end
55
+
56
+ hash
57
+ end
58
+
59
+ def to_xml(options = {}, &block)
60
+ XmlSerializer.new(self, options).serialize(&block)
61
+ end
62
+
63
+ private
64
+
65
+ def serializable_add_includes(options = {})
66
+ return unless include_associations = options.delete(:include)
67
+
68
+ base_only_or_except = { :except => options[:except],
69
+ :only => options[:only] }
70
+
71
+ include_has_options = include_associations.is_a?(Hash)
72
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
73
+
74
+ associations.each do |association|
75
+ records = get_proxy(self.class.associations[association])
76
+ unless records.nil?
77
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
78
+ opts = options.merge(association_options)
79
+ yield(association, records, opts)
80
+ end
81
+ end
82
+
83
+ options[:include] = include_associations
84
+ end
85
+
86
+ module ClassMethods
87
+ def from_json(json)
88
+ self.new.from_json(json)
89
+ end
90
+
91
+ def from_xml(xml)
92
+ self.new.from_xml(xml)
93
+ end
94
+ end
95
+ end
96
+
97
+ # Override default Serializer to use #serializable_hash
98
+ class XmlSerializer < ::ActiveModel::Serializers::Xml::Serializer
99
+ def attributes_hash
100
+ @serializable.serializable_hash(options)
101
+ end
102
+
103
+ def serializable_methods
104
+ # Methods are already included in #serializable_hash
105
+ []
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Timestamps
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def timestamps!
9
+ key :created_at, Time
10
+ key :updated_at, Time
11
+ class_eval { before_save :update_timestamps }
12
+ end
13
+ end
14
+
15
+ def update_timestamps
16
+ now = Time.now.utc
17
+ self[:created_at] = now if !persisted? && !created_at?
18
+ self[:updated_at] = now
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Touch
4
+ extend ActiveSupport::Concern
5
+
6
+ def touch(key = :updated_at)
7
+ raise ArgumentError, "Invalid key named #{key}" unless self.key_names.include?(key.to_s)
8
+ if self.class.embeddable?
9
+ self.write_attribute(key, Time.now.utc)
10
+ self._parent_document.touch
11
+ else
12
+ self.set(key => Time.now.utc)
13
+ end
14
+ true
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Userstamps
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def userstamps!(options = {})
9
+ class_name = (options[:class_name] || options[:class] || 'User' ).to_s
10
+ key :creator_id, ObjectId
11
+ key :updater_id, ObjectId
12
+ belongs_to :creator, :class_name => class_name
13
+ belongs_to :updater, :class_name => class_name
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Validations
5
+ extend ActiveSupport::Concern
6
+
7
+ include ::ActiveModel::Validations
8
+ include ::ActiveModel::Validations::Callbacks
9
+
10
+ module ClassMethods
11
+ def validates_uniqueness_of(*attr_names)
12
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
13
+ end
14
+
15
+ def validates_associated(*attr_names)
16
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
17
+ end
18
+ end
19
+
20
+ def save(options = {})
21
+ options.reverse_merge!(:validate => true)
22
+ !options[:validate] || valid? ? super : false
23
+ end
24
+
25
+ def valid?(context = nil)
26
+ context ||= (new_record? ? :create : :update)
27
+ super(context)
28
+ end
29
+
30
+ class UniquenessValidator < ::ActiveModel::EachValidator
31
+ def initialize(options)
32
+ super(options.reverse_merge(:case_sensitive => true))
33
+ end
34
+
35
+ def setup(klass)
36
+ @klass = klass
37
+ end
38
+
39
+ def validate_each(record, attribute, value)
40
+ conditions = scope_conditions(record)
41
+
42
+ if options[:case_sensitive]
43
+ conditions[attribute] = value
44
+ else
45
+ conditions[attribute] = /^#{Regexp.escape(value.to_s)}$/i
46
+ end
47
+
48
+ # Make sure we're not including the current document in the query
49
+ conditions[:_id.ne] = record._id if record._id
50
+
51
+ if @klass.exists?(conditions)
52
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
53
+ end
54
+ end
55
+
56
+ def message(instance)
57
+ super || "has already been taken"
58
+ end
59
+
60
+ def scope_conditions(instance)
61
+ Array(options[:scope] || []).inject({}) do |conditions, key|
62
+ conditions.merge(key => instance[key])
63
+ end
64
+ end
65
+ end
66
+
67
+ class AssociatedValidator < ::ActiveModel::EachValidator
68
+ def validate_each(record, attribute, value)
69
+ if !Array.wrap(value).all? { |c| c.nil? || c.valid?(options[:context]) }
70
+ record.errors.add(attribute, :invalid, :message => options[:message], :value => value)
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+
79
+ # Need to monkey patch ActiveModel for now since it uses the internal
80
+ # _run_validation_callbacks, which is impossible to override due to the
81
+ # way ActiveSupport::Callbacks is implemented.
82
+ ActiveModel::Validations::Callbacks.class_eval do
83
+ def run_validations!
84
+ run_callbacks(:validation) { super }
85
+ end
86
+ end