Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into rspec

* master:
  Document change to update_attribute
  Fix accessible/protected tests after change to update_attribute
  ActiveSupport::Concern's automatic inclusion of the InstanceMethods module is deprecated
  Fix add_to_set/push_uniq test typos
  Fuzzy match development dependency requirements on the minor version
  rename `connection_options` to `options` and create a test
  use mongodb connection options from yaml config file
  rename test
  move reflect_on_association to Rails module
  Make update_attribute behave like in ActiveRecord
  ActiveRecord compatible association reflection
  added :context option to validates_associated
  Added namespacing to model generator
  Add OneEmbeddedPolymorphicProxy (the last missing polymorphic proxy)
  type_key_name should be "_type" for ManyAssociation and OneAssociation, only "#{as}_type" for BelongsToAssociation
  More tests for many embedded polymorphic proxy (wasn't clear that it worked with non-SCI models)

Conflicts:
	Gemfile
	spec/mongo_mapper_spec.rb
	test/unit/associations/test_one_association.rb
  • Loading branch information...
commit 22040344b237e947de5471c91d0cc0b0b3355fd0 2 parents 0909a3c + a07f76d
Brandon Keepers bkeepers authored
Showing with 1,067 additions and 543 deletions.
  1. +8 −8 Gemfile
  2. +3 −0  UPGRADES
  3. +2 −5 examples/plugins.rb
  4. +1 −0  lib/mongo_mapper.rb
  5. +4 −0 lib/mongo_mapper/connection.rb
  6. +5 −6 lib/mongo_mapper/extensions/object.rb
  7. +14 −16 lib/mongo_mapper/plugins/accessible.rb
  8. +21 −23 lib/mongo_mapper/plugins/associations.rb
  9. +1 −1  lib/mongo_mapper/plugins/associations/base.rb
  10. +4 −0 lib/mongo_mapper/plugins/associations/belongs_to_association.rb
  11. +0 −4 lib/mongo_mapper/plugins/associations/many_association.rb
  12. +1 −1  lib/mongo_mapper/plugins/associations/one_association.rb
  13. +30 −0 lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb
  14. +11 −13 lib/mongo_mapper/plugins/caching.rb
  15. +12 −14 lib/mongo_mapper/plugins/callbacks.rb
  16. +11 −13 lib/mongo_mapper/plugins/clone.rb
  17. +36 −38 lib/mongo_mapper/plugins/dirty.rb
  18. +20 −22 lib/mongo_mapper/plugins/document.rb
  19. +11 −13 lib/mongo_mapper/plugins/embedded_callbacks.rb
  20. +22 −24 lib/mongo_mapper/plugins/embedded_document.rb
  21. +6 −8 lib/mongo_mapper/plugins/equality.rb
  22. +11 −13 lib/mongo_mapper/plugins/identity_map.rb
  23. +6 −8 lib/mongo_mapper/plugins/inspect.rb
  24. +109 −110 lib/mongo_mapper/plugins/keys.rb
  25. +2 −4 lib/mongo_mapper/plugins/logger.rb
  26. +32 −34 lib/mongo_mapper/plugins/modifiers.rb
  27. +5 −7 lib/mongo_mapper/plugins/persistence.rb
  28. +14 −16 lib/mongo_mapper/plugins/protected.rb
  29. +29 −31 lib/mongo_mapper/plugins/querying.rb
  30. +28 −22 lib/mongo_mapper/plugins/rails.rb
  31. +33 −0 lib/mongo_mapper/plugins/rails/active_record_association_adapter.rb
  32. +3 −5 lib/mongo_mapper/plugins/safe.rb
  33. +3 −5 lib/mongo_mapper/plugins/sci.rb
  34. +49 −52 lib/mongo_mapper/plugins/serialization.rb
  35. +4 −6 lib/mongo_mapper/plugins/timestamps.rb
  36. +8 −10 lib/mongo_mapper/plugins/validations.rb
  37. +1 −1  lib/rails/generators/mongo_mapper/model/model_generator.rb
  38. +2 −0  lib/rails/generators/mongo_mapper/model/templates/model.rb
  39. +11 −0 spec/mongo_mapper/plugins/associations/one_association_spec.rb
  40. +2 −4 spec/mongo_mapper/plugins_spec.rb
  41. +9 −0 spec/mongo_mapper_spec.rb
  42. +63 −0 test/functional/associations/test_many_embedded_polymorphic_proxy.rb
  43. +208 −0 test/functional/associations/test_one_embedded_polymorphic_proxy.rb
  44. +5 −0 test/functional/test_accessible.rb
  45. +12 −0 test/functional/test_embedded_document.rb
  46. +5 −5 test/functional/test_modifiers.rb
  47. +10 −0 test/functional/test_protected.rb
  48. +15 −1 test/functional/test_querying.rb
  49. +33 −0 test/functional/test_validations.rb
  50. +14 −0 test/models.rb
  51. +118 −0 test/unit/test_rails_reflect_on_association.rb
16 Gemfile
View
@@ -2,7 +2,7 @@ source :rubygems
gemspec
group :development, :test, :rspec do
- gem 'bson_ext', '~> 1.3.0'
+ gem 'bson_ext', '~> 1.5'
gem 'SystemTimer', :platform => :mri_18
gem 'ruby-debug', :platform => :mri_18
@@ -10,12 +10,12 @@ group :development, :test, :rspec do
gem 'perftools.rb', :platform => :mri, :require => 'perftools'
gem 'rake'
- gem 'tzinfo'
- gem 'json'
- gem 'log_buddy'
- gem 'timecop', '~> 0.3.1'
- gem 'rack-test'
- gem 'rails'
+ gem 'tzinfo', '~> 0.3'
+ gem 'json', '~> 1.6'
+ gem 'log_buddy', '~> 0.6'
+ gem 'timecop', '~> 0.3'
+ gem 'rack-test', '~> 0.6'
+ gem 'rails', '~> 3.0'
end
# FIXME: remove after porting all the specs to rpsec
@@ -27,4 +27,4 @@ end
group :rspec do
gem 'rspec', '~> 2.0'
-end
+end
3  UPGRADES
View
@@ -1,3 +1,6 @@
+0.10 => 0.11
+ * #update_attribute now ignores attr_accessible and attr_protected
+
0.9 => 0.10
* Using String IDs are no longer supported. If you are declaring your own ID, ensure it is an ObjectId, and set the default
key :_id, ObjectId, :default => lambda { BSON::ObjectId.new }
7 examples/plugins.rb
View
@@ -16,11 +16,8 @@ def foo
end
end
- # InstanceMethods module will automatically get included
- module InstanceMethods
- def foo
- 'Foo instance method!'
- end
+ def foo
+ 'Foo instance method!'
end
# Any configuration can be done in the #included block, which gets
1  lib/mongo_mapper.rb
View
@@ -75,6 +75,7 @@ module Associations
autoload :OneProxy, 'mongo_mapper/plugins/associations/one_proxy'
autoload :OneAsProxy, 'mongo_mapper/plugins/associations/one_as_proxy'
autoload :OneEmbeddedProxy, 'mongo_mapper/plugins/associations/one_embedded_proxy'
+ autoload :OneEmbeddedPolymorphicProxy, 'mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy'
autoload :InArrayProxy, 'mongo_mapper/plugins/associations/in_array_proxy'
end
end
4 lib/mongo_mapper/connection.rb
View
@@ -62,6 +62,10 @@ def connect(environment, options={})
raise 'Set config before connecting. MongoMapper.config = {...}' if config.blank?
env = config_for_environment(environment)
+ if env['options'].is_a? Hash
+ options = env['options'].symbolize_keys.merge(options)
+ end
+
MongoMapper.connection = if env['hosts']
Mongo::ReplSetConnection.new( *env['hosts'].push(options) )
else
11 lib/mongo_mapper/extensions/object.rb
View
@@ -2,6 +2,8 @@
module MongoMapper
module Extensions
module Object
+ extend ActiveSupport::Concern
+
module ClassMethods
def to_mongo(value)
value
@@ -12,16 +14,13 @@ def from_mongo(value)
end
end
- module InstanceMethods
- def to_mongo
- self.class.to_mongo(self)
- end
+ def to_mongo
+ self.class.to_mongo(self)
end
end
end
end
class Object
- extend MongoMapper::Extensions::Object::ClassMethods
- include MongoMapper::Extensions::Object::InstanceMethods
+ include MongoMapper::Extensions::Object
end
30 lib/mongo_mapper/plugins/accessible.rb
View
@@ -14,25 +14,23 @@ def attr_accessible(*attrs)
end
end
- module InstanceMethods
- def attributes=(attrs={})
- super(filter_inaccessible_attrs(attrs))
- end
-
- def update_attributes(attrs={})
- super(filter_inaccessible_attrs(attrs))
- end
+ def attributes=(attrs={})
+ super(filter_inaccessible_attrs(attrs))
+ end
- def update_attributes!(attrs={})
- super(filter_inaccessible_attrs(attrs))
- end
+ def update_attributes(attrs={})
+ super(filter_inaccessible_attrs(attrs))
+ end
- protected
- def filter_inaccessible_attrs(attrs)
- return attrs if !accessible_attributes? || attrs.blank?
- attrs.dup.delete_if { |key, val| !accessible_attributes.include?(key.to_sym) }
- end
+ def update_attributes!(attrs={})
+ super(filter_inaccessible_attrs(attrs))
end
+
+ protected
+ def filter_inaccessible_attrs(attrs)
+ return attrs if !accessible_attributes? || attrs.blank?
+ attrs.dup.delete_if { |key, val| !accessible_attributes.include?(key.to_sym) }
+ end
end
end
end
44 lib/mongo_mapper/plugins/associations.rb
View
@@ -56,35 +56,33 @@ def create_association(association)
end
end
- module InstanceMethods
- def associations
- self.class.associations
- end
+ def associations
+ self.class.associations
+ end
- def embedded_associations
- associations.values.select { |assoc| assoc.embeddable? }
- end
+ def embedded_associations
+ associations.values.select { |assoc| assoc.embeddable? }
+ end
- def build_proxy(association)
- proxy = association.proxy_class.new(self, association)
- self.instance_variable_set(association.ivar, proxy)
+ def build_proxy(association)
+ proxy = association.proxy_class.new(self, association)
+ self.instance_variable_set(association.ivar, proxy)
- proxy
- end
+ proxy
+ end
- def get_proxy(association)
- unless proxy = self.instance_variable_get(association.ivar)
- proxy = build_proxy(association)
- end
- proxy
+ def get_proxy(association)
+ unless proxy = self.instance_variable_get(association.ivar)
+ proxy = build_proxy(association)
end
+ proxy
+ end
- def save_to_collection(options={})
- super if defined?(super)
- associations.each do |association_name, association|
- proxy = get_proxy(association)
- proxy.save_to_collection(options) if proxy.proxy_respond_to?(:save_to_collection) && association.autosave?
- end
+ def save_to_collection(options={})
+ super if defined?(super)
+ associations.each do |association_name, association|
+ proxy = get_proxy(association)
+ proxy.save_to_collection(options) if proxy.proxy_respond_to?(:save_to_collection) && association.autosave?
end
end
end
2  lib/mongo_mapper/plugins/associations/base.rb
View
@@ -40,7 +40,7 @@ def embeddable?
end
def type_key_name
- "#{as}_type"
+ "_type"
end
def as
4 lib/mongo_mapper/plugins/associations/belongs_to_association.rb
View
@@ -3,6 +3,10 @@ module MongoMapper
module Plugins
module Associations
class BelongsToAssociation < SingleAssociation
+ def type_key_name
+ "#{as}_type"
+ end
+
def embeddable?
false
end
4 lib/mongo_mapper/plugins/associations/many_association.rb
View
@@ -8,10 +8,6 @@ def class_name
@class_name ||= options[:class_name] || name.to_s.singularize.camelize
end
- def type_key_name
- "_type"
- end
-
# hate this, need to revisit
def proxy_class
@proxy_class ||= if klass.embeddable?
2  lib/mongo_mapper/plugins/associations/one_association.rb
View
@@ -10,7 +10,7 @@ def embeddable?
def proxy_class
@proxy_class ||=
if klass.embeddable?
- OneEmbeddedProxy
+ polymorphic? ? OneEmbeddedPolymorphicProxy : OneEmbeddedProxy
elsif as?
OneAsProxy
else
30 lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb
View
@@ -0,0 +1,30 @@
+# encoding: UTF-8
+module MongoMapper
+ module Plugins
+ module Associations
+ class OneEmbeddedPolymorphicProxy < OneEmbeddedProxy
+ def replace(value)
+ @value = value.respond_to?(:attributes) ? value.attributes.merge(association.type_key_name => value.class.name) : value
+ reset
+ end
+
+ protected
+ def find_target
+ if @value
+ child = polymorphic_class(@value).load(@value)
+ assign_references(child)
+ child
+ end
+ end
+
+ def polymorphic_class(doc)
+ if class_name = doc[association.type_key_name]
+ class_name.constantize
+ else
+ klass
+ end
+ end
+ end
+ end
+ end
+end
24 lib/mongo_mapper/plugins/caching.rb
View
@@ -4,19 +4,17 @@ module Plugins
module Caching
extend ActiveSupport::Concern
- module InstanceMethods
- def cache_key(*suffixes)
- cache_key = case
- when !persisted?
- "#{self.class.name}/new"
- when timestamp = self[:updated_at]
- "#{self.class.name}/#{id}-#{timestamp.to_s(:number)}"
- else
- "#{self.class.name}/#{id}"
- end
- cache_key += "/#{suffixes.join('/')}" unless suffixes.empty?
- cache_key
- end
+ def cache_key(*suffixes)
+ cache_key = case
+ when !persisted?
+ "#{self.class.name}/new"
+ when timestamp = self[:updated_at]
+ "#{self.class.name}/#{id}-#{timestamp.to_s(:number)}"
+ else
+ "#{self.class.name}/#{id}"
+ end
+ cache_key += "/#{suffixes.join('/')}" unless suffixes.empty?
+ cache_key
end
end
end
26 lib/mongo_mapper/plugins/callbacks.rb
View
@@ -4,23 +4,21 @@ module Plugins
module Callbacks
extend ActiveSupport::Concern
- module InstanceMethods
- def destroy
- run_callbacks(:destroy) { super }
- end
+ def destroy
+ run_callbacks(:destroy) { super }
+ end
- private
- def create_or_update(*)
- run_callbacks(:save) { super }
- end
+ private
+ def create_or_update(*)
+ run_callbacks(:save) { super }
+ end
- def create(*)
- run_callbacks(:create) { super }
- end
+ def create(*)
+ run_callbacks(:create) { super }
+ end
- def update(*)
- run_callbacks(:update) { super }
- end
+ def update(*)
+ run_callbacks(:update) { super }
end
end
end
24 lib/mongo_mapper/plugins/clone.rb
View
@@ -4,19 +4,17 @@ module Plugins
module Clone
extend ActiveSupport::Concern
- module InstanceMethods
- def initialize_copy(other)
- @_new = true
- @_destroyed = false
- @_id = nil
- associations.each do |name, association|
- instance_variable_set(association.ivar, nil)
- end
- self.attributes = other.attributes.clone.except(:_id).inject({}) do |hash, entry|
- key, value = entry
- hash[key] = value.duplicable? ? value.clone : value
- hash
- end
+ def initialize_copy(other)
+ @_new = true
+ @_destroyed = false
+ @_id = nil
+ associations.each do |name, association|
+ instance_variable_set(association.ivar, nil)
+ end
+ self.attributes = other.attributes.clone.except(:_id).inject({}) do |hash, entry|
+ key, value = entry
+ hash[key] = value.duplicable? ? value.clone : value
+ hash
end
end
end
74 lib/mongo_mapper/plugins/dirty.rb
View
@@ -6,56 +6,54 @@ module Dirty
include ::ActiveModel::Dirty
- module InstanceMethods
- def initialize(*)
- # never register initial id assignment as a change
- super.tap { changed_attributes.delete('_id') }
- end
+ def initialize(*)
+ # never register initial id assignment as a change
+ super.tap { changed_attributes.delete('_id') }
+ end
- def initialize_from_database(*)
- super.tap { changed_attributes.clear }
- end
+ def initialize_from_database(*)
+ super.tap { changed_attributes.clear }
+ end
- def save(*)
- clear_changes { super }
- end
+ def save(*)
+ clear_changes { super }
+ end
- def reload(*)
- super.tap { clear_changes }
- end
+ def reload(*)
+ super.tap { clear_changes }
+ end
- protected
+ protected
- def attribute_method?(attr)
- # This overrides ::ActiveSupport::Dirty#attribute_method? to allow attributes to be any key
- # in the attributes hash ( default ) or any key defined on the model that may not yet have
- # had a value stored in the attributes collection.
- super || key_names.include?(attr)
- end
+ def attribute_method?(attr)
+ # This overrides ::ActiveSupport::Dirty#attribute_method? to allow attributes to be any key
+ # in the attributes hash ( default ) or any key defined on the model that may not yet have
+ # had a value stored in the attributes collection.
+ super || key_names.include?(attr)
+ end
- def clear_changes
- previous = changes
- (block_given? ? yield : true).tap do |result|
- unless result == false #failed validation; nil is OK.
- @previously_changed = previous
- changed_attributes.clear
- end
+ def clear_changes
+ previous = changes
+ (block_given? ? yield : true).tap do |result|
+ unless result == false #failed validation; nil is OK.
+ @previously_changed = previous
+ changed_attributes.clear
end
end
+ end
- private
+ private
- def write_key(key, value)
- key = key.to_s
- attribute_will_change!(key) unless attribute_changed?(key)
- super(key, value).tap do
- changed_attributes.delete(key) unless attribute_value_changed?(key)
- end
+ def write_key(key, value)
+ key = key.to_s
+ attribute_will_change!(key) unless attribute_changed?(key)
+ super(key, value).tap do
+ changed_attributes.delete(key) unless attribute_value_changed?(key)
end
+ end
- def attribute_value_changed?(key_name)
- attribute_was(key_name) != read_key(key_name)
- end
+ def attribute_value_changed?(key_name)
+ attribute_was(key_name) != read_key(key_name)
end
end
end
42 lib/mongo_mapper/plugins/document.rb
View
@@ -10,34 +10,32 @@ def embeddable?
end
end
- module InstanceMethods
- def new?
- @_new
- end
+ def new?
+ @_new
+ end
- def destroyed?
- @_destroyed == true
- end
+ def destroyed?
+ @_destroyed == true
+ end
- def reload
- if doc = collection.find_one(:_id => id)
- self.class.associations.each_value do |association|
- get_proxy(association).reset
- end
- instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
- load_from_database(doc)
- self
- else
- raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
+ def reload
+ if doc = collection.find_one(:_id => id)
+ self.class.associations.each_value do |association|
+ get_proxy(association).reset
end
- end
-
- # Used by embedded docs to find root easily without if/respond_to? stuff.
- # Documents are always root documents.
- def _root_document
+ instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
+ load_from_database(doc)
self
+ else
+ raise DocumentNotFound, "Document match #{_id.inspect} does not exist in #{collection.name} collection"
end
end
+
+ # Used by embedded docs to find root easily without if/respond_to? stuff.
+ # Documents are always root documents.
+ def _root_document
+ self
+ end
end
end
end
24 lib/mongo_mapper/plugins/embedded_callbacks.rb
View
@@ -10,23 +10,21 @@ module EmbeddedCallbacks
define_model_callbacks :save, :create, :update, :destroy, :only => [:before, :after]
end
- module InstanceMethods
- def run_callbacks(callback, *args, &block)
- embedded_docs = []
+ def run_callbacks(callback, *args, &block)
+ embedded_docs = []
- embedded_associations.each do |association|
- embedded_docs += Array(get_proxy(association).send(:load_target))
- end
+ embedded_associations.each do |association|
+ embedded_docs += Array(get_proxy(association).send(:load_target))
+ end
- block = embedded_docs.inject(block) do |chain, doc|
- if doc.class.respond_to?("_#{callback}_callbacks")
- lambda { doc.run_callbacks(callback, *args, &chain) }
- else
- chain
- end
+ block = embedded_docs.inject(block) do |chain, doc|
+ if doc.class.respond_to?("_#{callback}_callbacks")
+ lambda { doc.run_callbacks(callback, *args, &chain) }
+ else
+ chain
end
- super callback, *args, &block
end
+ super callback, *args, &block
end
end
end
46 lib/mongo_mapper/plugins/embedded_document.rb
View
@@ -18,37 +18,35 @@ def embedded_in(owner_name)
end
end
- module InstanceMethods
- def new?
- _root_document.try(:new?) || @_new
- end
+ def new?
+ _root_document.try(:new?) || @_new
+ end
- def destroyed?
- !!_root_document.try(:destroyed?)
- end
+ def destroyed?
+ !!_root_document.try(:destroyed?)
+ end
- def save(options={})
- _root_document.try(:save, options).tap do |result|
- persist(options) if result
- end
+ def save(options={})
+ _root_document.try(:save, options).tap do |result|
+ persist(options) if result
end
+ end
- def save!(options={})
- valid? || raise(DocumentNotValid.new(self))
- _root_document.try(:save!, options).tap do |result|
- persist(options) if result
- end
+ def save!(options={})
+ valid? || raise(DocumentNotValid.new(self))
+ _root_document.try(:save!, options).tap do |result|
+ persist(options) if result
end
+ end
- def persist(options={})
- @_new = false
- clear_changes if respond_to?(:clear_changes)
- save_to_collection(options)
- end
+ def persist(options={})
+ @_new = false
+ clear_changes if respond_to?(:clear_changes)
+ save_to_collection(options)
+ end
- def _root_document
- @_root_document ||= _parent_document.try(:_root_document)
- end
+ def _root_document
+ @_root_document ||= _parent_document.try(:_root_document)
end
end
end
14 lib/mongo_mapper/plugins/equality.rb
View
@@ -10,15 +10,13 @@ def ===(other)
end
end
- module InstanceMethods
- def eql?(other)
- other.is_a?(self.class) && _id == other._id
- end
- alias :== :eql?
+ def eql?(other)
+ other.is_a?(self.class) && _id == other._id
+ end
+ alias :== :eql?
- def hash
- _id.hash
- end
+ def hash
+ _id.hash
end
end
end
24 lib/mongo_mapper/plugins/identity_map.rb
View
@@ -108,22 +108,20 @@ def selecting_fields?(options)
end
end
- module InstanceMethods
- def identity_map
- self.class.identity_map
- end
+ def identity_map
+ self.class.identity_map
+ end
- def save(*args)
- if result = super
- identity_map[_id] = self if self.class.identity_map_on?
- end
- result
+ def save(*args)
+ if result = super
+ identity_map[_id] = self if self.class.identity_map_on?
end
+ result
+ end
- def delete
- identity_map.delete(_id) if self.class.identity_map_on?
- super
- end
+ def delete
+ identity_map.delete(_id) if self.class.identity_map_on?
+ super
end
end
end
14 lib/mongo_mapper/plugins/inspect.rb
View
@@ -4,14 +4,12 @@ module Plugins
module Inspect
extend ActiveSupport::Concern
- module InstanceMethods
- def inspect(include_nil = false)
- keys = include_nil ? key_names : attributes.keys
- attributes_as_nice_string = keys.sort.collect do |name|
- "#{name}: #{self[name].inspect}"
- end.join(", ")
- "#<#{self.class} #{attributes_as_nice_string}>"
- end
+ def inspect(include_nil = false)
+ keys = include_nil ? key_names : attributes.keys
+ attributes_as_nice_string = keys.sort.collect do |name|
+ "#{name}: #{self[name].inspect}"
+ end.join(", ")
+ "#<#{self.class} #{attributes_as_nice_string}>"
end
end
end
219 lib/mongo_mapper/plugins/keys.rb
View
@@ -159,151 +159,150 @@ def create_validations_for(key)
end
end
- module InstanceMethods
- def initialize(attrs={})
- @_new = true
- self.attributes = attrs
- end
+ def initialize(attrs={})
+ @_new = true
+ self.attributes = attrs
+ end
- def initialize_from_database(attrs={})
- @_new = false
- load_from_database(attrs)
- self
- end
+ def initialize_from_database(attrs={})
+ @_new = false
+ load_from_database(attrs)
+ self
+ end
- def persisted?
- !new? && !destroyed?
- end
+ def persisted?
+ !new? && !destroyed?
+ end
- def attributes=(attrs)
- return if attrs.blank?
+ def attributes=(attrs)
+ return if attrs.blank?
- attrs.each_pair do |key, value|
- if respond_to?(:"#{key}=")
- self.send(:"#{key}=", value)
- else
- self[key] = value
- end
+ attrs.each_pair do |key, value|
+ if respond_to?(:"#{key}=")
+ self.send(:"#{key}=", value)
+ else
+ self[key] = value
end
end
+ end
- def attributes
- HashWithIndifferentAccess.new.tap do |attrs|
- keys.select { |name,key| !self[key.name].nil? || key.type == ObjectId }.each do |name, key|
- value = key.set(self[key.name])
- attrs[name] = value
- end
+ def attributes
+ HashWithIndifferentAccess.new.tap do |attrs|
+ keys.select { |name,key| !self[key.name].nil? || key.type == ObjectId }.each do |name, key|
+ value = key.set(self[key.name])
+ attrs[name] = value
+ end
- embedded_associations.each do |association|
- if documents = instance_variable_get(association.ivar)
- if association.is_a?(Associations::OneAssociation)
- attrs[association.name] = documents.to_mongo
- else
- attrs[association.name] = documents.map { |document| document.to_mongo }
- end
+ embedded_associations.each do |association|
+ if documents = instance_variable_get(association.ivar)
+ if association.is_a?(Associations::OneAssociation)
+ attrs[association.name] = documents.to_mongo
+ else
+ attrs[association.name] = documents.map { |document| document.to_mongo }
end
end
end
end
- alias :to_mongo :attributes
-
- def assign(attrs={})
- warn "[DEPRECATION] #assign is deprecated, use #attributes="
- self.attributes = attrs
- end
-
- def update_attributes(attrs={})
- self.attributes = attrs
- save
- end
+ end
+ alias :to_mongo :attributes
- def update_attributes!(attrs={})
- self.attributes = attrs
- save!
- end
+ def assign(attrs={})
+ warn "[DEPRECATION] #assign is deprecated, use #attributes="
+ self.attributes = attrs
+ end
- def update_attribute(name, value)
- update_attributes(name.to_sym => value)
- end
+ def update_attributes(attrs={})
+ self.attributes = attrs
+ save
+ end
- def id
- _id
- end
+ def update_attributes!(attrs={})
+ self.attributes = attrs
+ save!
+ end
- def id=(value)
- if self.class.using_object_id?
- value = ObjectId.to_mongo(value)
- end
+ def update_attribute(name, value)
+ self.send(:"#{name}=", value)
+ save(:validate => false)
+ end
- self[:_id] = value
- end
+ def id
+ _id
+ end
- def [](name)
- read_key(name)
+ def id=(value)
+ if self.class.using_object_id?
+ value = ObjectId.to_mongo(value)
end
- def []=(name, value)
- ensure_key_exists(name)
- write_key(name, value)
- end
+ self[:_id] = value
+ end
- def keys
- self.class.keys
- end
+ def [](name)
+ read_key(name)
+ end
- def key_names
- keys.keys
- end
+ def []=(name, value)
+ ensure_key_exists(name)
+ write_key(name, value)
+ end
- def non_embedded_keys
- keys.values.select { |key| !key.embeddable? }
- end
+ def keys
+ self.class.keys
+ end
- def embedded_keys
- keys.values.select { |key| key.embeddable? }
- end
+ def key_names
+ keys.keys
+ end
- private
- def load_from_database(attrs)
- return if attrs.blank?
- attrs.each do |key, value|
- if respond_to?(:"#{key}=") && !self.class.key?(key)
- self.send(:"#{key}=", value)
- else
- self[key] = value
- end
- end
- end
+ def non_embedded_keys
+ keys.values.select { |key| !key.embeddable? }
+ end
- def ensure_key_exists(name)
- self.class.key(name) unless respond_to?("#{name}=")
- end
+ def embedded_keys
+ keys.values.select { |key| key.embeddable? }
+ end
- def set_parent_document(key, value)
- if key.embeddable? && value.is_a?(key.type)
- value._parent_document = self
+ private
+ def load_from_database(attrs)
+ return if attrs.blank?
+ attrs.each do |key, value|
+ if respond_to?(:"#{key}=") && !self.class.key?(key)
+ self.send(:"#{key}=", value)
+ else
+ self[key] = value
end
end
+ end
- def read_key(key_name)
- if key = keys[key_name.to_s]
- value = key.get(instance_variable_get(:"@#{key_name}"))
- set_parent_document(key, value)
- instance_variable_set(:"@#{key_name}", value)
- end
- end
+ def ensure_key_exists(name)
+ self.class.key(name) unless respond_to?("#{name}=")
+ end
- def read_key_before_type_cast(name)
- instance_variable_get(:"@#{name}_before_type_cast")
+ def set_parent_document(key, value)
+ if key.embeddable? && value.is_a?(key.type)
+ value._parent_document = self
end
+ end
- def write_key(name, value)
- key = keys[name.to_s]
+ def read_key(key_name)
+ if key = keys[key_name.to_s]
+ value = key.get(instance_variable_get(:"@#{key_name}"))
set_parent_document(key, value)
- instance_variable_set :"@#{name}_before_type_cast", value
- instance_variable_set :"@#{name}", key.set(value)
+ instance_variable_set(:"@#{key_name}", value)
end
- end
+ end
+
+ def read_key_before_type_cast(name)
+ instance_variable_get(:"@#{name}_before_type_cast")
+ end
+
+ def write_key(name, value)
+ key = keys[name.to_s]
+ set_parent_document(key, value)
+ instance_variable_set :"@#{name}_before_type_cast", value
+ instance_variable_set :"@#{name}", key.set(value)
+ end
end
end
end
6 lib/mongo_mapper/plugins/logger.rb
View
@@ -10,10 +10,8 @@ def logger
end
end
- module InstanceMethods
- def logger
- self.class.logger
- end
+ def logger
+ self.class.logger
end
end
end
66 lib/mongo_mapper/plugins/modifiers.rb
View
@@ -75,47 +75,45 @@ def criteria_and_keys_from_args(args)
end
end
- module InstanceMethods
- def unset(*keys)
- self.class.unset(id, *keys)
- end
+ def unset(*keys)
+ self.class.unset(id, *keys)
+ end
- def increment(hash)
- self.class.increment(id, hash)
- end
+ def increment(hash)
+ self.class.increment(id, hash)
+ end
- def decrement(hash)
- self.class.decrement(id, hash)
- end
+ def decrement(hash)
+ self.class.decrement(id, hash)
+ end
- def set(hash)
- self.class.set(id, hash)
- end
+ def set(hash)
+ self.class.set(id, hash)
+ end
- def push(hash)
- self.class.push(id, hash)
- end
-
- def push_all(hash)
- self.class.push_all(id, hash)
- end
+ def push(hash)
+ self.class.push(id, hash)
+ end
- def pull(hash)
- self.class.pull(id, hash)
- end
-
- def pull_all(hash)
- self.class.pull_all(id, hash)
- end
+ def push_all(hash)
+ self.class.push_all(id, hash)
+ end
- def add_to_set(hash)
- self.class.push_uniq(id, hash)
- end
- alias push_uniq add_to_set
+ def pull(hash)
+ self.class.pull(id, hash)
+ end
- def pop(hash)
- self.class.pop(id, hash)
- end
+ def pull_all(hash)
+ self.class.pull_all(id, hash)
+ end
+
+ def add_to_set(hash)
+ self.class.push_uniq(id, hash)
+ end
+ alias push_uniq add_to_set
+
+ def pop(hash)
+ self.class.pop(id, hash)
end
end
end
12 lib/mongo_mapper/plugins/persistence.rb
View
@@ -57,14 +57,12 @@ def assert_supported
end
end
- module InstanceMethods
- def collection
- _root_document.class.collection
- end
+ def collection
+ _root_document.class.collection
+ end
- def database
- _root_document.class.database
- end
+ def database
+ _root_document.class.database
end
end
end
30 lib/mongo_mapper/plugins/protected.rb
View
@@ -23,25 +23,23 @@ def key(*args)
end
end
- module InstanceMethods
- def attributes=(attrs={})
- super(filter_protected_attrs(attrs))
- end
-
- def update_attributes(attrs={})
- super(filter_protected_attrs(attrs))
- end
+ def attributes=(attrs={})
+ super(filter_protected_attrs(attrs))
+ end
- def update_attributes!(attrs={})
- super(filter_protected_attrs(attrs))
- end
+ def update_attributes(attrs={})
+ super(filter_protected_attrs(attrs))
+ end
- protected
- def filter_protected_attrs(attrs)
- return attrs if protected_attributes.blank? || attrs.blank?
- attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
- end
+ def update_attributes!(attrs={})
+ super(filter_protected_attrs(attrs))
end
+
+ protected
+ def filter_protected_attrs(attrs)
+ return attrs if protected_attributes.blank? || attrs.blank?
+ attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
+ end
end
end
end
60 lib/mongo_mapper/plugins/querying.rb
View
@@ -137,44 +137,42 @@ def update_multiple(docs)
end
end
- module InstanceMethods
- def save(options={})
- options.assert_valid_keys(:validate, :safe)
- create_or_update(options)
- end
+ def save(options={})
+ options.assert_valid_keys(:validate, :safe)
+ create_or_update(options)
+ end
- def save!(options={})
- options.assert_valid_keys(:safe)
- save(options) || raise(DocumentNotValid.new(self))
- end
+ def save!(options={})
+ options.assert_valid_keys(:safe)
+ save(options) || raise(DocumentNotValid.new(self))
+ end
- def destroy
- delete
- end
+ def destroy
+ delete
+ end
- def delete
- self.class.delete(id).tap { @_destroyed = true } if persisted?
- end
+ def delete
+ self.class.delete(id).tap { @_destroyed = true } if persisted?
+ end
- private
- def create_or_update(options={})
- result = persisted? ? update(options) : create(options)
- result != false
- end
+ private
+ def create_or_update(options={})
+ result = persisted? ? update(options) : create(options)
+ result != false
+ end
- def create(options={})
- save_to_collection(options)
- end
+ def create(options={})
+ save_to_collection(options)
+ end
- def update(options={})
- save_to_collection(options)
- end
+ def update(options={})
+ save_to_collection(options)
+ end
- def save_to_collection(options={})
- @_new = false
- collection.save(to_mongo, :safe => options[:safe])
- end
- end
+ def save_to_collection(options={})
+ @_new = false
+ collection.save(to_mongo, :safe => options[:safe])
+ end
end
end
end
50 lib/mongo_mapper/plugins/rails.rb
View
@@ -2,36 +2,35 @@
module MongoMapper
module Plugins
module Rails
+ autoload :ActiveRecordAssociationAdapter, "mongo_mapper/plugins/rails/active_record_association_adapter"
extend ActiveSupport::Concern
- module InstanceMethods
- def to_param
- id.to_s if persisted?
- end
+ def to_param
+ id.to_s if persisted?
+ end
- def to_model
- self
- end
+ def to_model
+ self
+ end
- def to_key
- [id] if persisted?
- end
+ def to_key
+ [id] if persisted?
+ end
- def new_record?
- new?
- end
+ def new_record?
+ new?
+ end
- def read_attribute(name)
- self[name]
- end
+ def read_attribute(name)
+ self[name]
+ end
- def read_attribute_before_type_cast(name)
- read_key_before_type_cast(name)
- end
+ def read_attribute_before_type_cast(name)
+ read_key_before_type_cast(name)
+ end
- def write_attribute(name, value)
- self[name] = value
- end
+ def write_attribute(name, value)
+ self[name] = value
end
module ClassMethods
@@ -46,6 +45,13 @@ def has_many(*args, &extension)
def column_names
keys.keys
end
+
+ # Returns returns an ActiveRecordAssociationAdapter for an association. This adapter has an API that is a
+ # subset of ActiveRecord::Reflection::AssociationReflection. This allows MongoMapper to be used with the
+ # association helpers in gems like simple_form and formtastic.
+ def reflect_on_association(name)
+ ActiveRecordAssociationAdapter.for_association(associations[name]) if associations[name]
+ end
end
end
end
33 lib/mongo_mapper/plugins/rails/active_record_association_adapter.rb
View
@@ -0,0 +1,33 @@
+# encoding: UTF-8
+module MongoMapper
+ module Plugins
+ module Rails
+ class ActiveRecordAssociationAdapter
+ attr_reader :klass, :macro, :name, :options
+
+ def self.for_association(association)
+ macro = case association
+ when MongoMapper::Plugins::Associations::BelongsToAssociation
+ :belongs_to
+ when MongoMapper::Plugins::Associations::ManyAssociation
+ :has_many
+ when MongoMapper::Plugins::Associations::OneAssociation
+ :has_one
+ else
+ raise "no #{name} for association of type #{association.class}"
+ end
+
+ new(association, macro)
+ end
+
+ def initialize(association, macro)
+ @klass, @name = association.klass, association.name
+ # only include compatible options
+ @options = association.options.slice(:conditions, :order)
+
+ @macro = macro
+ end
+ end
+ end
+ end
+end
8 lib/mongo_mapper/plugins/safe.rb
View
@@ -19,11 +19,9 @@ def safe?
end
end
- module InstanceMethods
- def save_to_collection(options={})
- options[:safe] = self.class.safe? unless options.key?(:safe)
- super
- end
+ def save_to_collection(options={})
+ options[:safe] = self.class.safe? unless options.key?(:safe)
+ super
end
end
end
8 lib/mongo_mapper/plugins/sci.rb
View
@@ -27,11 +27,9 @@ def query(options={})
end
end
- module InstanceMethods
- def initialize(*args)
- super
- write_key :_type, self.class.name if self.class.key?(:_type)
- end
+ def initialize(*args)
+ super
+ write_key :_type, self.class.name if self.class.key?(:_type)
end
end
end
101 lib/mongo_mapper/plugins/serialization.rb
View
@@ -13,77 +13,74 @@ module Serialization
self.include_root_in_json = false
end
- module InstanceMethods
- def serializable_attributes
- attributes.keys.map { |k| k.to_s } + ['id'] - ['_id']
- end
-
- def serializable_hash(options = nil)
- options ||= {}
+ def serializable_attributes
+ attributes.keys.map { |k| k.to_s } + ['id'] - ['_id']
+ end
- options[:only] = Array.wrap(options[:only]).map { |k| k.to_s }
- options[:except] = Array.wrap(options[:except]).map { |k| k.to_s }
+ def serializable_hash(options = nil)
+ options ||= {}
- attribute_names = serializable_attributes
+ options[:only] = Array.wrap(options[:only]).map { |k| k.to_s }
+ options[:except] = Array.wrap(options[:except]).map { |k| k.to_s }
- if options[:only].any?
- attribute_names &= options[:only]
- elsif options[:except].any?
- attribute_names -= options[:except]
- end
+ attribute_names = serializable_attributes
- attribute_names += Array.wrap(options[:methods]).map { |m| m.to_s }.select do |method|
- respond_to?(method)
- end
+ if options[:only].any?
+ attribute_names &= options[:only]
+ elsif options[:except].any?
+ attribute_names -= options[:except]
+ end
- hash = attribute_names.sort.inject({}) do |hash, name|
- value = send(name)
- hash[name] = if value.is_a?(Array)
- value.map {|v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
- elsif value.respond_to?(:serializable_hash)
- value.serializable_hash
- else
- value
- end
- hash
- end
+ attribute_names += Array.wrap(options[:methods]).map { |m| m.to_s }.select do |method|
+ respond_to?(method)
+ end
- serializable_add_includes(options) do |association, records, opts|
- hash[association.to_s] = records.is_a?(Array) ?
- records.map { |r| r.serializable_hash(opts) } :
- records.serializable_hash(opts)
+ hash = attribute_names.sort.inject({}) do |hash, name|
+ value = send(name)
+ hash[name] = if value.is_a?(Array)
+ value.map {|v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
+ elsif value.respond_to?(:serializable_hash)
+ value.serializable_hash
+ else
+ value
end
-
hash
end
- def to_xml(options = {}, &block)
- XmlSerializer.new(self, options).serialize(&block)
+ serializable_add_includes(options) do |association, records, opts|
+ hash[association.to_s] = records.is_a?(Array) ?
+ records.map { |r| r.serializable_hash(opts) } :
+ records.serializable_hash(opts)
end
- private
+ hash
+ end
- def serializable_add_includes(options = {})
- return unless include_associations = options.delete(:include)
+ def to_xml(options = {}, &block)
+ XmlSerializer.new(self, options).serialize(&block)
+ end
- base_only_or_except = { :except => options[:except],
- :only => options[:only] }
+ private
- include_has_options = include_associations.is_a?(Hash)
- associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
+ def serializable_add_includes(options = {})
+ return unless include_associations = options.delete(:include)
- associations.each do |association|
- records = get_proxy(self.class.associations[association])
- unless records.nil?
- association_options = include_has_options ? include_associations[association] : base_only_or_except
- opts = options.merge(association_options)
- yield(association, records, opts)
- end
- end
+ base_only_or_except = { :except => options[:except],
+ :only => options[:only] }
+
+ include_has_options = include_associations.is_a?(Hash)
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
- options[:include] = include_associations
+ associations.each do |association|
+ records = get_proxy(self.class.associations[association])
+ unless records.nil?
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
+ opts = options.merge(association_options)
+ yield(association, records, opts)
+ end
end
+ options[:include] = include_associations
end
module ClassMethods
10 lib/mongo_mapper/plugins/timestamps.rb
View
@@ -12,12 +12,10 @@ def timestamps!
end
end
- module InstanceMethods
- def update_timestamps
- now = Time.now.utc
- self[:created_at] = now if !persisted? && !created_at?
- self[:updated_at] = now
- end
+ def update_timestamps
+ now = Time.now.utc
+ self[:created_at] = now if !persisted? && !created_at?
+ self[:updated_at] = now
end
end
end
18 lib/mongo_mapper/plugins/validations.rb
View
@@ -17,16 +17,14 @@ def validates_associated(*attr_names)
end
end
- module InstanceMethods
- def save(options = {})
- options.reverse_merge!(:validate => true)
- !options[:validate] || valid? ? super : false
- end
+ def save(options = {})
+ options.reverse_merge!(:validate => true)
+ !options[:validate] || valid? ? super : false
+ end
- def valid?(context = nil)
- context ||= (new_record? ? :create : :update)
- super(context)
- end
+ def valid?(context = nil)
+ context ||= (new_record? ? :create : :update)
+ super(context)
end
class UniquenessValidator < ::ActiveModel::EachValidator
@@ -68,7 +66,7 @@ def scope_conditions(instance)
class AssociatedValidator < ::ActiveModel::EachValidator
def validate_each(record, attribute, value)
- if !Array.wrap(value).all? { |c| c.nil? || c.valid? }
+ if !Array.wrap(value).all? { |c| c.nil? || c.valid?(options[:context]) }
record.errors.add(attribute, :invalid, :message => options[:message], :value => value)
end
end
2  lib/rails/generators/mongo_mapper/model/model_generator.rb
View
@@ -14,7 +14,7 @@ def self.source_root
end
def create_model_file
- template 'model.rb', File.join('app/models', "#{file_name}.rb")
+ template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
end
hook_for :test_framework
2  lib/rails/generators/mongo_mapper/model/templates/model.rb
View
@@ -1,3 +1,4 @@
+<% module_namespacing do -%>
class <%= class_name %>
include MongoMapper::Document
@@ -9,3 +10,4 @@ class <%= class_name %>
<% end -%>
end
+<% end -%>
11 spec/mongo_mapper/plugins/associations/one_association_spec.rb
View
@@ -3,6 +3,12 @@
describe MongoMapper::Plugins::Associations::OneAssociation do
Associations = MongoMapper::Plugins::Associations
+ describe "type_key_name" do
+ it "should be _type" do
+ Associations::OneAssociation.new(:foo).type_key_name.should == '_type'
+ end
+ end
+
context "embeddable?" do
it "should be true if class is embeddable" do
base = Associations::OneAssociation.new(:media)
@@ -30,6 +36,11 @@
base = Associations::OneAssociation.new(:media)
base.proxy_class.should == Associations::OneEmbeddedProxy
end
+
+ it "should be OneEmbeddedPolymorphicProxy for polymorphic one embedded" do
+ base = Associations::OneAssociation.new(:media, :polymorphic => true)
+ base.proxy_class.should == Associations::OneEmbeddedPolymorphicProxy
+ end
end
end
6 spec/mongo_mapper/plugins_spec.rb
View
@@ -19,10 +19,8 @@ def class_foo
end
end
- module InstanceMethods
- def instance_foo
- 'instance_foo'
- end
+ def instance_foo
+ 'instance_foo'
end
end
9 spec/mongo_mapper_spec.rb
View
@@ -65,6 +65,15 @@ class Address; end
MongoMapper.connect('development', :logger => logger)
end
+ it "should work with options from config" do
+ MongoMapper.config = {
+ 'development' => {'host' => '192.168.1.1', 'port' => 2222, 'database' => 'test', 'options' => {'safe' => true}}
+ }
+ connection, logger = mock('connection'), mock('logger')
+ Mongo::Connection.should_receive(:new).with('192.168.1.1', 2222, :logger => logger, :safe => true)
+ MongoMapper.connect('development', :logger => logger)
+ end
+
it "should work with options using uri" do
MongoMapper.config = {
'development' => {'uri' => 'mongodb://127.0.0.1:27017/test'}
63 test/functional/associations/test_many_embedded_polymorphic_proxy.rb
View
@@ -30,6 +30,69 @@ def setup
catalog.medias[0].new?.should == false
end
+ context "associating objects of non-SCI class" do
+ should "work on replacement" do
+ catalog = Catalog.new
+ catalog.medias = [Human.new(:name => 'Frank'), Robot.new(:serial_number => '1B')]
+
+ catalog.medias.size.should == 2
+ catalog.medias[0].name.should == 'Frank'
+ catalog.medias[0].class.should == Human
+ catalog.medias[1].serial_number.should == '1B'
+ catalog.medias[1].class.should == Robot
+
+ catalog.save.should be_true
+ catalog.reload
+
+ catalog.medias.size.should == 2
+ catalog.medias[0].name.should == 'Frank'
+ catalog.medias[0].class.should == Human
+ catalog.medias[1].serial_number.should == '1B'
+ catalog.medias[1].class.should == Robot
+ end
+
+ should "work on replacement with hashes" do
+ catalog = Catalog.new
+ catalog.medias = [{:name => 'Frank', '_type' => 'Human'}, {:serial_number => '1B', '_type' => 'Robot'}]
+
+ catalog.medias.size.should == 2
+ catalog.medias[0].name.should == 'Frank'
+ catalog.medias[0].class.should == Human
+ catalog.medias[1].serial_number.should == '1B'
+ catalog.medias[1].class.should == Robot
+
+ catalog.save.should be_true
+ catalog.reload
+
+ catalog.medias.size.should == 2
+ catalog.medias[0].name.should == 'Frank'
+ catalog.medias[0].class.should == Human
+ catalog.medias[1].serial_number.should == '1B'
+ catalog.medias[1].class.should == Robot
+ end
+
+ should "work with concatination" do
+ catalog = Catalog.new
+ catalog.medias << Human.new(:name => 'Frank')
+ catalog.medias << Robot.new(:serial_number => '1B')
+
+ catalog.medias.size.should == 2
+ catalog.medias[0].name.should == 'Frank'
+ catalog.medias[0].class.should == Human
+ catalog.medias[1].serial_number.should == '1B'
+ catalog.medias[1].class.should == Robot
+
+ catalog.save.should be_true
+ catalog.reload
+
+ catalog.medias.size.should == 2
+ catalog.medias[0].name.should == 'Frank'
+ catalog.medias[0].class.should == Human
+ catalog.medias[1].serial_number.should == '1B'
+ catalog.medias[1].class.should == Robot
+ end
+ end
+
context "count" do
should "default to 0" do
Catalog.new.medias.count.should == 0
208 test/functional/associations/test_one_embedded_polymorphic_proxy.rb
View
@@ -0,0 +1,208 @@
+require 'test_helper'
+require 'models'
+
+class OneEmbeddedPolymorhpicProxyTest < Test::Unit::TestCase
+ def setup
+ @post_class = Doc('Post') do
+ key :title, String
+ end
+ end
+
+ should "default to nil" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ @post_class.new.author.should be_nil
+ end
+
+ should "return nil instead of a proxy" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ nil.should === @post_class.new.author
+ end
+
+ should "be able to build" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ post = @post_class.create
+ author = post.build_author(:serial_number => "1B")
+ post.author.should be_instance_of(Robot)
+ post.author.should be_new
+ post.author.serial_number.should == '1B'
+ post.author.should == author
+ post.author.post.should == post
+ end
+
+ should "allow assignment of associated document using a hash" do
+ @post_class.one :author, :polymorphic => :true, :class => Robot
+
+ post = @post_class.new('author' => { 'name' => 'Frank', '_type' => 'Human' })
+ post.author.name.should == 'Frank'
+ post.author.class.should == Human
+
+ post.save.should be_true
+ post.reload
+
+ post.author.name.should == 'Frank'
+ post.author.class.should == Human
+ end
+
+ context "replacing the association" do
+ context "with an object" do
+ setup do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ @post = @post_class.create
+ @human = Human.new(:name => 'Frank')
+ end
+
+ should "work" do
+ @post.author = @human
+ @post.save
+ @post.reload
+
+ @post.author.should == @human
+ @post.author.nil?.should be_false
+ @post.author.class.should == Human
+
+ new_human = Human.new(:name => 'Emily')
+ @post.author = new_human
+ @post.author.should == new_human
+ end
+
+ should "generate a new proxy instead of modifying the existing one" do
+ @post.author = @human
+ @post.save
+ @post.reload
+
+ @post.author.should == @human
+ @post.author.nil?.should be_false
+
+ original_author = @post.author
+ original_author.name.should == 'Frank'
+ new_human = Human.new(:name => 'Emily')
+ @post.author = new_human
+ @post.author.should == new_human
+
+ original_author.name.should == 'Frank'
+ end
+
+ should "assign _type" do
+ @post.author = @human
+ @post.author._type.should == "Human"
+ end
+ end
+
+ context "with a Hash" do
+ setup do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ @post = @post_class.create
+ end
+
+ should "convert to an object of the class and work" do
+ @post.author = {'serial_number' => '1B'}
+ @post.save
+ @post.reload
+
+ @post.author.serial_number.should == '1B'
+ @post.author.nil?.should be_false
+
+ @post.author = {'serial_number' => '2C'}
+ @post.author.serial_number.should == '2C'
+ end
+
+ should "convert to an object of _type if given" do
+ @post.author = {'name' => 'Frank', '_type' => 'Human'}
+ @post.author.name.should == 'Frank'
+ @post.author.class.should == Human
+ @post.save
+ @post.reload
+
+ @post.author.name.should == 'Frank'
+ @post.author.class.should == Human
+ end
+
+ should "assign _type" do
+ @post.author = {'name' => 'Frank', '_type' => 'Human'}
+ @post.save
+ @post.reload
+ @post.author._type.should == "Human"
+ end
+ end
+ end
+
+ should "unset the association" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ post = @post_class.create
+ human = Human.new
+ post.update_attributes!(:author => human)
+ post.reload
+ post.author = nil
+ post.author.should == nil
+ end
+
+ should "set modularized associated models correctly" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+
+ post = @post_class.new('author' => {'_type' => 'TrModels::Ambulance', 'license_plate' => 'GGG123', 'icu' => true})
+
+ post.author.class.should == TrModels::Ambulance
+ post.author.license_plate.should == 'GGG123'
+ post.author.icu.should be_true
+ post.save.should be_true
+
+ post = post.reload
+ post.author.class.should == TrModels::Ambulance
+ post.author.license_plate.should == 'GGG123'
+ post.author.icu.should be_true
+ end
+
+ should "not have problem loading root document if embedded one is nil" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+ post = @post_class.create
+
+ lambda {
+ @post_class.find(post.id)
+ }.should_not raise_error
+ end
+
+ should "load the parent and root documents for nested embedded documents" do
+ @address_class = EDoc('Address') do
+ key :city, String
+ key :state, String
+ end
+ @author_class = EDoc('EmbeddedAuthor')
+ @author_class.one :address, :polymorphic => true, :class => @address_class
+ @post_class.one :author, :polymorphic => true, :class => @author_class
+
+ post = @post_class.create(:title => 'Post Title', :author => { :name => 'Frank', :address => { :city => 'Boston', :state => 'MA' } })
+
+ post.author.address._parent_document.should == post.author
+ post.author.address._root_document.should == post
+ end
+
+ should "have boolean method for testing presence" do
+ @post_class.one :author, :polymorphic => true, :class => Robot
+
+ post = @post_class.new
+ post.author?.should be_false
+
+ post.author = Human.new(:name => 'Frank')
+ post.author?.should be_true
+ end
+
+ should "initialize id for nested embedded document created from hash" do
+ @address_class = EDoc('Address') do
+ key :city, String
+ key :state, String
+ end
+ @author_class = EDoc('EmbeddedAuthor')
+ @author_class.one :address, :polymorphic => true, :class => @address_class
+ @post_class.one :author, :polymorphic => true, :class => @author_class
+
+ post = @post_class.create(:title => 'Post Title', :author => {
+ :name => 'Frank',
+ :address => {
+ :city => 'Boston',
+ :state => 'MA'
+ }
+ })
+
+ post.author.address.id.should_not be_nil
+ end
+end
5 test/functional/test_accessible.rb
View
@@ -68,6 +68,11 @@ class AccessibleTest < Test::Unit::TestCase
doc.name.should == 'John'
end
+ should "not ignore inaccessible attribute on #update_attribute" do
+ @doc.update_attribute('admin', true)
+ @doc.admin.should be_true
+ end
+
should "ignore inaccessible attribute on #update_attributes" do
@doc.update_attributes(:name => 'Ren Hoek', :admin => true)
@doc.name.should == 'Ren Hoek'
12 test/functional/test_embedded_document.rb
View
@@ -241,6 +241,18 @@ def setup
person.pets.first.crazy_key.should == 'crazy'
end
+ should "be able to update_attribute" do
+ pet = @pet_klass.new(:name => 'sparky')
+ person = @klass.create(:pets => [pet])
+ person.reload
+ pet = person.pets.first
+
+ pet.update_attribute('name', 'koda').should be_true
+ person.reload
+ person.pets.first._id.should == pet._id
+ person.pets.first.name.should == 'koda'
+ end
+
should "be able to update_attributes" do
pet = @pet_klass.new(:name => 'sparky')
person = @klass.create(:pets => [pet])
10 test/functional/test_modifiers.rb
View
@@ -325,7 +325,7 @@ def assert_keys_removed(page, *keys)
end
end
- context "InstanceMethods" do
+ context "instance methods" do
should "be able to unset with keys" do
page = @page_class.create(:title => 'Foo', :tags => %w(foo))
page.unset(:title, :tags)
@@ -368,7 +368,7 @@ def assert_keys_removed(page, *keys)
page.reload
page.tags.should == %w(foo)
end
-
+
should "be able to push_all with modifier hashes" do
page = @page_class.create
page.push_all(:tags => %w(foo bar))
@@ -384,7 +384,7 @@ def assert_keys_removed(page, *keys)
page.reload
page.tags.should == %w(bar)
end
-
+
should "be able to pull_all with criteria and modifier hashes" do
page = @page_class.create(:tags => %w(foo bar baz))
page.pull_all(:tags => %w(foo bar))
@@ -398,7 +398,7 @@ def assert_keys_removed(page, *keys)
page2 = @page_class.create
page.add_to_set(:tags => 'foo')
- page.add_to_set(:tags => 'foo')
+ page2.add_to_set(:tags => 'foo')
page.reload
page.tags.should == %w(foo)
@@ -412,7 +412,7 @@ def assert_keys_removed(page, *keys)
page2 = @page_class.create
page.push_uniq(:tags => 'foo')
- page.push_uniq(:tags => 'foo')
+ page2.push_uniq(:tags => 'foo')
page.reload
page.tags.should == %w(foo)
10 test/functional/test_protected.rb
View
@@ -74,6 +74,11 @@ class ProtectedTest < Test::Unit::TestCase
doc.name.should == 'John'
end
+ should "ignore protected attribute on #update_attribute" do
+ @doc.update_attribute('admin', true)
+ @doc.admin.should be_true
+ end
+
should "ignore protected attribute on #update_attributes" do
@doc.update_attributes(:name => 'Ren Hoek', :admin => true)
@doc.name.should == 'Ren Hoek'
@@ -176,6 +181,11 @@ class ::OtherChild < ::GrandParent
@edoc.admin.should be_true
end
+ should "not ignore protected attribute on #update_attribute" do
+ @edoc.update_attribute('admin', true)
+ @edoc.admin.should be_true
+ end
+
should "ignore protected attribute on #update_attributes" do
@edoc.update_attributes(:name => 'Ren Hoek', :admin => true)
@edoc.name.should == 'Ren Hoek'
16 test/functional/test_querying.rb
View
@@ -682,10 +682,24 @@ def setup
@doc = @document.create(:first_name => 'John', :age => '27')
end
- should "update the attribute" do
+ should "accept symbols as keys" do
@doc.update_attribute(:first_name, 'Chris').should be_true
@doc.reload.first_name.should == 'Chris'
end
+
+ should "update the attribute" do
+ @doc.update_attribute('first_name', 'Chris').should be_true
+ @doc.reload.first_name.should == 'Chris'
+ end
+
+ should "update the attribute without invoking validations" do
+ @document.key :name, String, :required => true
+
+ @doc.expects(:valid?).never
+ @doc.update_attribute('name', '').should be_true
+ @doc.reload.name.should == ''
+ @document.count.should == 1
+ end
end
context "#save (new document)" do
33 test/functional/test_validations.rb
View
@@ -350,6 +350,39 @@ def action_present
doc.children.build
doc.should have_error_on(:children, 'are invalid')
end
+
+ end
+
+ context "validating associated docs with custom context" do
+ setup do
+ @child_class = EDoc do
+ key :name
+
+ validates_length_of :name, :minimum => 5, :on => :custom_context
+ end
+
+ @root_class = Doc { }
+ @root_class.many :children, :class => @child_class
+ @root_class.validates_associated :children, :context => :custom_context
+ end
+
+ should "pass if there are no associated docs" do
+ doc = @root_class.new
+ doc.valid?(:custom_context).should be_true
+ end
+
+ should "pass if the associated doc is valid" do
+ doc = @root_class.new
+ doc.children.build(:name => 'George')
+ doc.valid?(:custom_context).should be_true
+ end
+
+ should "fail if the associated doc is invalid" do
+ doc = @root_class.new
+ doc.children.build(:name => 'Bob')
+ doc.valid?(:custom_context).should_not be_true
+ end
+
end
# context "validates uniqueness of with :unique shortcut" do
# should "work" do
14 test/models.rb
View
@@ -245,3 +245,17 @@ class Article
class AltUser
include MongoMapper::Document
end
+
+class Human
+ include MongoMapper::EmbeddedDocument
+
+ key :name, String
+ embedded_in :post
+end
+
+class Robot
+ include MongoMapper::EmbeddedDocument
+
+ key :serial_number, String
+ embedded_in :post
+end
118 test/unit/test_rails_reflect_on_association.rb
View
@@ -0,0 +1,118 @@
+require 'test_helper'
+
+module ReflectOnAssociationTestModels
+ class Tree
+ include MongoMapper::Document
+ many :birds, :class_name => "ReflectOnAssociationTestModels::Bird"
+ end
+
+ class Bird
+ include MongoMapper::Document
+ belongs_to :tree, :class_name => "ReflectOnAssociationTestModels::Tree"
+ end
+
+ class Book
+ include MongoMapper::Document
+ many :authors, :class_name => "ReflectOnAssociationTestModels::Author", :in => :author_ids
+ end
+
+ class Author
+ include MongoMapper::Document
+ end
+
+ class Employee
+ include MongoMapper::Document
+ one :desk, :class_name => "ReflectOnAssociationTestModels::Desk"
+ end
+
+ class Desk
+ include MongoMapper::Document
+ belongs_to :employee, :class_name => "ReflectOnAssociationTestModels::Employee"
+ end
+
+ class Order
+ include MongoMapper::Document
+ many :line_items, :class_name => "ReflectOnAssociationTestModels::LineItem"
+ end
+
+ class LineItem
+ include MongoMapper::EmbeddedDocument
+ end
+
+ class Body
+ include MongoMapper::Document
+ one :heart, :class_name => "ReflectOnAssociationTestModels::Heart"
+ end
+
+ class Heart
+ include MongoMapper::EmbeddedDocument
+ end
+end
+
+class ReflectOnAssociationTest < Test::Unit::TestCase
+ context "one-to-many association" do
+ should "return :has_many association for Tree#birds" do
+ association = ReflectOnAssociationTestModels::Tree.reflect_on_association(:birds)
+ association.klass.should == ReflectOnAssociationTestModels::Bird
+ association.macro.should == :has_many
+ association.name.should == :birds
+ association.options.should == {}
+ end
+
+ should "return :belongs_to association for Bird#tree" do
+ association = ReflectOnAssociationTestModels::Bird.reflect_on_association(:tree)
+ association.klass.should == ReflectOnAssociationTestModels::Tree