mongo_mapper 0.8.6 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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