mongo_mapper 0.8.6 → 0.9.0

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 (107) hide show
  1. data/UPGRADES +10 -0
  2. data/bin/mmconsole +0 -1
  3. data/examples/identity_map/automatic.rb +1 -7
  4. data/examples/plugins.rb +9 -9
  5. data/examples/safe.rb +43 -0
  6. data/lib/mongo_mapper.rb +46 -33
  7. data/lib/mongo_mapper/document.rb +33 -32
  8. data/lib/mongo_mapper/embedded_document.rb +22 -22
  9. data/lib/mongo_mapper/locale/en.yml +5 -0
  10. data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
  11. data/lib/mongo_mapper/plugins.rb +16 -3
  12. data/lib/mongo_mapper/plugins/accessible.rb +2 -0
  13. data/lib/mongo_mapper/plugins/active_model.rb +18 -0
  14. data/lib/mongo_mapper/plugins/associations.rb +37 -42
  15. data/lib/mongo_mapper/plugins/associations/base.rb +14 -50
  16. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +58 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +6 -1
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +30 -2
  19. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -0
  20. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +12 -6
  21. data/lib/mongo_mapper/plugins/associations/many_association.rb +67 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +5 -5
  23. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +1 -1
  24. data/lib/mongo_mapper/plugins/associations/one_association.rb +20 -0
  25. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +5 -0
  26. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +7 -7
  27. data/lib/mongo_mapper/plugins/associations/proxy.rb +2 -2
  28. data/lib/mongo_mapper/plugins/caching.rb +3 -1
  29. data/lib/mongo_mapper/plugins/callbacks.rb +12 -221
  30. data/lib/mongo_mapper/plugins/clone.rb +3 -1
  31. data/lib/mongo_mapper/plugins/dirty.rb +38 -91
  32. data/lib/mongo_mapper/plugins/document.rb +4 -2
  33. data/lib/mongo_mapper/plugins/dynamic_querying.rb +2 -0
  34. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +43 -0
  35. data/lib/mongo_mapper/plugins/embedded_document.rb +16 -9
  36. data/lib/mongo_mapper/plugins/equality.rb +2 -0
  37. data/lib/mongo_mapper/plugins/identity_map.rb +4 -2
  38. data/lib/mongo_mapper/plugins/indexes.rb +2 -0
  39. data/lib/mongo_mapper/plugins/inspect.rb +3 -1
  40. data/lib/mongo_mapper/plugins/keys.rb +28 -22
  41. data/lib/mongo_mapper/plugins/keys/key.rb +12 -6
  42. data/lib/mongo_mapper/plugins/logger.rb +2 -0
  43. data/lib/mongo_mapper/plugins/modifiers.rb +3 -1
  44. data/lib/mongo_mapper/plugins/pagination.rb +2 -0
  45. data/lib/mongo_mapper/plugins/persistence.rb +2 -0
  46. data/lib/mongo_mapper/plugins/protected.rb +2 -0
  47. data/lib/mongo_mapper/plugins/querying.rb +5 -4
  48. data/lib/mongo_mapper/plugins/rails.rb +3 -5
  49. data/lib/mongo_mapper/plugins/safe.rb +2 -0
  50. data/lib/mongo_mapper/plugins/sci.rb +2 -0
  51. data/lib/mongo_mapper/plugins/scopes.rb +2 -0
  52. data/lib/mongo_mapper/plugins/serialization.rb +67 -46
  53. data/lib/mongo_mapper/plugins/timestamps.rb +3 -1
  54. data/lib/mongo_mapper/plugins/userstamps.rb +2 -0
  55. data/lib/mongo_mapper/plugins/validations.rb +40 -24
  56. data/lib/mongo_mapper/railtie.rb +49 -0
  57. data/lib/mongo_mapper/railtie/database.rake +60 -0
  58. data/lib/mongo_mapper/support/descendant_appends.rb +11 -11
  59. data/lib/mongo_mapper/translation.rb +10 -0
  60. data/lib/mongo_mapper/version.rb +1 -1
  61. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  62. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  63. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  64. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +11 -0
  65. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +1 -0
  66. data/test/functional/associations/test_belongs_to_proxy.rb +131 -1
  67. data/test/functional/associations/test_in_array_proxy.rb +30 -0
  68. data/test/functional/associations/test_many_documents_proxy.rb +30 -2
  69. data/test/functional/associations/test_many_embedded_proxy.rb +33 -0
  70. data/test/functional/associations/test_many_polymorphic_proxy.rb +1 -0
  71. data/test/functional/associations/test_one_embedded_proxy.rb +21 -2
  72. data/test/functional/associations/test_one_proxy.rb +49 -9
  73. data/test/functional/test_associations.rb +2 -0
  74. data/test/functional/test_caching.rb +3 -2
  75. data/test/functional/test_callbacks.rb +25 -18
  76. data/test/functional/test_dirty.rb +123 -1
  77. data/test/functional/test_document.rb +26 -2
  78. data/test/functional/test_embedded_document.rb +68 -2
  79. data/test/functional/test_identity_map.rb +3 -4
  80. data/test/functional/test_querying.rb +11 -0
  81. data/test/functional/test_userstamps.rb +2 -2
  82. data/test/functional/test_validations.rb +31 -29
  83. data/test/models.rb +10 -0
  84. data/test/test_active_model_lint.rb +1 -1
  85. data/test/test_helper.rb +9 -10
  86. data/test/unit/associations/test_base.rb +24 -100
  87. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  88. data/test/unit/associations/test_many_association.rb +63 -0
  89. data/test/unit/associations/test_one_association.rb +18 -0
  90. data/test/unit/serializers/test_json_serializer.rb +0 -1
  91. data/test/unit/test_descendant_appends.rb +8 -16
  92. data/test/unit/test_document.rb +4 -9
  93. data/test/unit/test_dynamic_finder.rb +1 -1
  94. data/test/unit/test_embedded_document.rb +51 -18
  95. data/test/unit/test_identity_map_middleware.rb +34 -0
  96. data/test/unit/test_inspect.rb +22 -0
  97. data/test/unit/test_key.rb +21 -1
  98. data/test/unit/test_keys.rb +0 -2
  99. data/test/unit/test_plugins.rb +106 -20
  100. data/test/unit/test_rails.rb +8 -8
  101. data/test/unit/test_serialization.rb +116 -1
  102. data/test/unit/test_translation.rb +27 -0
  103. data/test/unit/test_validations.rb +66 -81
  104. metadata +103 -43
  105. data/examples/identity_map/middleware.rb +0 -14
  106. data/lib/mongo_mapper/plugins/descendants.rb +0 -17
  107. data/rails/init.rb +0 -19
@@ -2,9 +2,7 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Rails
5
- def self.configure(model)
6
- model.extend ActiveModel::Naming if defined?(ActiveModel)
7
- end
5
+ extend ActiveSupport::Concern
8
6
 
9
7
  module InstanceMethods
10
8
  def to_param
@@ -27,8 +25,8 @@ module MongoMapper
27
25
  self[name]
28
26
  end
29
27
 
30
- def read_attribute_before_typecast(name)
31
- read_key_before_typecast(name)
28
+ def read_attribute_before_type_cast(name)
29
+ read_key_before_type_cast(name)
32
30
  end
33
31
 
34
32
  def write_attribute(name, value)
@@ -2,6 +2,8 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Safe
5
+ extend ActiveSupport::Concern
6
+
5
7
  module ClassMethods
6
8
  def inherited(subclass)
7
9
  super
@@ -2,6 +2,8 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Sci
5
+ extend ActiveSupport::Concern
6
+
5
7
  module ClassMethods
6
8
  def inherited(subclass)
7
9
  key :_type, String unless key?(:_type)
@@ -2,6 +2,8 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Scopes
5
+ extend ActiveSupport::Concern
6
+
5
7
  module ClassMethods
6
8
  def scope(name, scope_options={})
7
9
  scopes[name] = lambda do |*args|
@@ -1,73 +1,94 @@
1
1
  # encoding: UTF-8
2
- require 'active_support/json'
2
+ require 'active_model/serializers/json'
3
+ require 'active_model/serializers/xml'
3
4
 
4
5
  module MongoMapper
5
6
  module Plugins
6
7
  module Serialization
7
- def self.configure(model)
8
- model.class_eval { cattr_accessor :include_root_in_json, :instance_writer => true }
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
9
14
  end
10
15
 
11
16
  module InstanceMethods
12
- def as_json options={}
17
+ def serializable_attributes
18
+ attributes.keys.map { |k| k.to_s } + ['id'] - ['_id']
19
+ end
20
+
21
+ def serializable_hash(options = nil)
13
22
  options ||= {}
14
- unless options[:only]
15
- methods = [options.delete(:methods)].flatten.compact
16
- methods << :id
17
- options[:methods] = methods.uniq
18
- end
19
23
 
20
- except = [options.delete(:except)].flatten.compact
21
- except << :_id
22
- options[:except] = except
24
+ options[:only] = Array.wrap(options[:only]).map { |k| k.to_s }
25
+ options[:except] = Array.wrap(options[:except]).map { |k| k.to_s }
23
26
 
24
- # Direct rip from Rails 3 ActiveModel Serialization (#serializable_hash)
25
- hash = begin
26
- options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
27
- options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
27
+ attribute_names = serializable_attributes
28
28
 
29
- attribute_names = attributes.keys.sort
30
- if options[:only].any?
31
- attribute_names &= options[:only]
32
- elsif options[:except].any?
33
- attribute_names -= options[:except]
34
- end
29
+ if options[:only].any?
30
+ attribute_names &= options[:only]
31
+ elsif options[:except].any?
32
+ attribute_names -= options[:except]
33
+ end
35
34
 
36
- method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
37
- methods << name if respond_to?(name.to_s)
38
- methods
35
+ attribute_names += Array.wrap(options[:methods]).map { |m| m.to_s }.select do |method|
36
+ respond_to?(method)
37
+ end
38
+
39
+ hash = attribute_names.sort.inject({}) do |hash, name|
40
+ value = send(name)
41
+ hash[name] = if value.is_a?(Array)
42
+ value.map {|v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
43
+ elsif value.respond_to?(:serializable_hash)
44
+ value.serializable_hash
45
+ else
46
+ value
39
47
  end
48
+ hash
49
+ end
40
50
 
41
- (attribute_names + method_names).inject({}) { |hash, name|
42
- hash[name] = send(name)
43
- hash
44
- }
51
+ serializable_add_includes(options) do |association, records, opts|
52
+ hash[association.to_s] = records.is_a?(Array) ?
53
+ records.map { |r| r.serializable_hash(opts) } :
54
+ records.serializable_hash(opts)
45
55
  end
46
- # End rip
47
-
48
- options.delete(:only) if options[:only].nil? or options[:only].empty?
49
-
50
- hash.each do |key, value|
51
- if value.is_a?(Array)
52
- hash[key] = value.map do |item|
53
- item.respond_to?(:as_json) ? item.as_json(options) : item
54
- end
55
- elsif value.is_a? BSON::ObjectId
56
- hash[key] = value.to_s
57
- elsif value.respond_to?(:as_json)
58
- hash[key] = value.as_json(options)
56
+
57
+ hash
58
+ end
59
+
60
+ private
61
+
62
+ def serializable_add_includes(options = {})
63
+ return unless include_associations = options.delete(:include)
64
+
65
+ base_only_or_except = { :except => options[:except],
66
+ :only => options[:only] }
67
+
68
+ include_has_options = include_associations.is_a?(Hash)
69
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
70
+
71
+ associations.each do |association|
72
+ records = get_proxy(self.class.associations[association])
73
+ unless records.nil?
74
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
75
+ opts = options.merge(association_options)
76
+ yield(association, records, opts)
59
77
  end
60
78
  end
61
79
 
62
- # Replicate Rails 3 naming - and also bin anytihng after : for use in our dynamic classes from unit tests
63
- hash = { ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self.class.name)).gsub(/:.*/,'') => hash } if include_root_in_json
64
- hash
80
+ options[:include] = include_associations
65
81
  end
82
+
66
83
  end
67
84
 
68
85
  module ClassMethods
69
86
  def from_json(json)
70
- self.new(ActiveSupport::JSON.decode(json))
87
+ self.new.from_json(json)
88
+ end
89
+
90
+ def from_xml(xml)
91
+ self.new.from_xml(xml)
71
92
  end
72
93
  end
73
94
 
@@ -2,6 +2,8 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Timestamps
5
+ extend ActiveSupport::Concern
6
+
5
7
  module ClassMethods
6
8
  def timestamps!
7
9
  key :created_at, Time
@@ -13,7 +15,7 @@ module MongoMapper
13
15
  module InstanceMethods
14
16
  def update_timestamps
15
17
  now = Time.now.utc
16
- self[:created_at] = now if new? && !created_at?
18
+ self[:created_at] = now if !persisted? && !created_at?
17
19
  self[:updated_at] = now
18
20
  end
19
21
  end
@@ -2,6 +2,8 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Userstamps
5
+ extend ActiveSupport::Concern
6
+
5
7
  module ClassMethods
6
8
  def userstamps!
7
9
  key :creator_id, ObjectId
@@ -2,30 +2,44 @@
2
2
  module MongoMapper
3
3
  module Plugins
4
4
  module Validations
5
- def self.configure(model)
6
- model.class_eval do
7
- include Validatable
8
- extend Validations::DocumentMacros
5
+ extend ActiveSupport::Concern
6
+
7
+ include ::ActiveModel::Validations
8
+
9
+ module ClassMethods
10
+ def validates_uniqueness_of(*attr_names)
11
+ validates_with UniquenessValidator, _merge_attributes(attr_names)
9
12
  end
10
- end
11
13
 
12
- module DocumentMacros
13
- def validates_uniqueness_of(*args)
14
- add_validations(args, Validations::ValidatesUniquenessOf)
14
+ def validates_associated(*attr_names)
15
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
15
16
  end
16
17
  end
17
18
 
18
- class ValidatesUniquenessOf < Validatable::ValidationBase
19
- option :scope, :case_sensitive
20
- default :case_sensitive => true
19
+ class UniquenessValidator < ::ActiveModel::EachValidator
20
+ def initialize(options)
21
+ super(options.reverse_merge(:case_sensitive => true))
22
+ end
23
+
24
+ def setup(klass)
25
+ @klass = klass
26
+ end
27
+
28
+ def validate_each(record, attribute, value)
29
+ conditions = scope_conditions(record)
21
30
 
22
- def valid?(instance)
23
- value = instance[attribute]
24
- return allow_nil if value.nil? and not allow_nil.nil?
25
- return allow_blank if value.blank? and not allow_blank.nil?
26
- base_conditions = case_sensitive ? {self.attribute => value} : {}
27
- doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
28
- doc.nil? || instance._id == doc._id
31
+ if options[:case_sensitive]
32
+ conditions[attribute] = value
33
+ else
34
+ conditions[attribute] = /^#{Regexp.escape(value.to_s)}$/i
35
+ end
36
+
37
+ # Make sure we're not including the current document in the query
38
+ conditions[:_id.ne] = record._id if record._id
39
+
40
+ if @klass.exists?(conditions)
41
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
42
+ end
29
43
  end
30
44
 
31
45
  def message(instance)
@@ -33,18 +47,20 @@ module MongoMapper
33
47
  end
34
48
 
35
49
  def scope_conditions(instance)
36
- return {} unless scope
37
- Array(scope).inject({}) do |conditions, key|
50
+ Array(options[:scope] || []).inject({}) do |conditions, key|
38
51
  conditions.merge(key => instance[key])
39
52
  end
40
53
  end
54
+ end
41
55
 
42
- def where_conditions(instance)
43
- conditions = {}
44
- conditions[attribute] = /^#{Regexp.escape(instance[attribute].to_s)}$/i unless case_sensitive
45
- conditions
56
+ class AssociatedValidator < ::ActiveModel::EachValidator
57
+ def validate_each(record, attribute, value)
58
+ if !Array.wrap(value).all? { |c| c.nil? || c.valid? }
59
+ record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
60
+ end
46
61
  end
47
62
  end
63
+
48
64
  end
49
65
  end
50
66
  end
@@ -0,0 +1,49 @@
1
+ require "mongo_mapper"
2
+ require "rails"
3
+ require "active_model/railtie"
4
+
5
+ module MongoMapper
6
+ # = MongoMapper Railtie
7
+ class Railtie < Rails::Railtie
8
+
9
+ config.mongo_mapper = ActiveSupport::OrderedOptions.new
10
+
11
+ rake_tasks do
12
+ load "mongo_mapper/railtie/database.rake"
13
+ end
14
+
15
+ initializer "mongo_mapper.set_configs" do |app|
16
+ ActiveSupport.on_load(:mongo_mapper) do
17
+ app.config.mongo_mapper.each do |k,v|
18
+ send "#{k}=", v
19
+ end
20
+ end
21
+ end
22
+
23
+ # This sets the database configuration and establishes the connection.
24
+ initializer "mongo_mapper.initialize_database" do |app|
25
+ config_file = Rails.root.join('config/mongo.yml')
26
+ if config_file.file?
27
+ config = YAML.load(ERB.new(config_file.read).result)
28
+ MongoMapper.setup(config, Rails.env, :logger => Rails.logger)
29
+ end
30
+ end
31
+
32
+ # Clear the identity map after each request
33
+ initializer "mongo_mapper.clear_identity_map" do |app|
34
+ app.config.middleware.use 'MongoMapper::Middleware::IdentityMap'
35
+ end
36
+
37
+ initializer "mongo_mapper.prepare_dispatcher" do |app|
38
+ # See http://groups.google.com/group/mongomapper/browse_thread/thread/68f62e8eda43b43a/4841dba76938290c
39
+ # to_prepare is called before each request in development mode and the first request in production.
40
+ ActionDispatch::Callbacks.to_prepare do
41
+ unless app.config.cache_classes
42
+ # Rails reloading was making descendants fill up and leak memory, these make sure they get cleared
43
+ ActiveSupport::DescendantsTracker.clear
44
+ MongoMapper::Plugins::IdentityMap.models.clear
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,60 @@
1
+ namespace :db do
2
+
3
+ if not Rake::Task.task_defined?("db:drop")
4
+ desc 'Drops all the collections for the database for the current Rails.env'
5
+ task :drop => :environment do
6
+ MongoMapper.database.collections.select {|c| c.name !~ /system/ }.each(&:drop)
7
+ end
8
+ end
9
+
10
+ if not Rake::Task.task_defined?("db:seed")
11
+ # if another ORM has defined db:seed, don't run it twice.
12
+ desc 'Load the seed data from db/seeds.rb'
13
+ task :seed => :environment do
14
+ seed_file = File.join(Rails.root, 'db', 'seeds.rb')
15
+ load(seed_file) if File.exist?(seed_file)
16
+ end
17
+ end
18
+
19
+ if not Rake::Task.task_defined?("db:setup")
20
+ desc 'Create the database, and initialize with the seed data'
21
+ task :setup => [ 'db:create', 'db:seed' ]
22
+ end
23
+
24
+ if not Rake::Task.task_defined?("db:reseed")
25
+ desc 'Delete data and seed'
26
+ task :reseed => [ 'db:drop', 'db:seed' ]
27
+ end
28
+
29
+ if not Rake::Task.task_defined?("db:create")
30
+ task :create => :environment do
31
+ # noop
32
+ end
33
+ end
34
+
35
+ if not Rake::Task.task_defined?("db:migrate")
36
+ task :migrate => :environment do
37
+ # noop
38
+ end
39
+ end
40
+
41
+ if not Rake::Task.task_defined?("db:schema:load")
42
+ namespace :schema do
43
+ task :load do
44
+ # noop
45
+ end
46
+ end
47
+ end
48
+
49
+ if not Rake::Task.task_defined?("db:test:prepare")
50
+ namespace :test do
51
+ task :prepare => :environment do
52
+ MongoMapper.connect('test')
53
+ MongoMapper.database.collections.select {|c| c.name !~ /system/ }.each(&:drop)
54
+ MongoMapper.connect(Rails.env)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ task 'test:prepare' => 'db:test:prepare'
@@ -4,29 +4,29 @@ require 'set'
4
4
  module MongoMapper
5
5
  module Support
6
6
  module DescendantAppends
7
- def included(model)
8
- extra_extensions.each { |extension| model.extend(extension) }
9
- extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
10
- descendants << model
11
- end
12
-
13
- # @api public
14
- def descendants
15
- @descendants ||= Set.new
7
+ def included(model = nil, &block)
8
+ if model
9
+ extra_extensions.each { |extension| model.extend(extension) }
10
+ extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
11
+ direct_descendants << model
12
+ end
13
+ super
16
14
  end
17
15
 
18
16
  # @api public
19
17
  def append_extensions(*extensions)
18
+ warn "[DEPRECATED] append_extensions is deprecated. Use #plugin with a module that extends ActiveSupport::Concern."
20
19
  extra_extensions.concat(extensions)
21
- descendants.each do |model|
20
+ direct_descendants.each do |model|
22
21
  extensions.each { |extension| model.extend(extension) }
23
22
  end
24
23
  end
25
24
 
26
25
  # @api public
27
26
  def append_inclusions(*inclusions)
27
+ warn "[DEPRECATED] append_inclusions is deprecated. Use #plugin with a module that extends ActiveSupport::Concern."
28
28
  extra_inclusions.concat(inclusions)
29
- descendants.each do |model|
29
+ direct_descendants.each do |model|
30
30
  inclusions.each { |inclusion| model.send(:include, inclusion) }
31
31
  end
32
32
  end