novelys_mongo_mapper 0.6.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +54 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper.rb +124 -0
  8. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  9. data/lib/mongo_mapper/document.rb +423 -0
  10. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  11. data/lib/mongo_mapper/embedded_document.rb +67 -0
  12. data/lib/mongo_mapper/finder_options.rb +127 -0
  13. data/lib/mongo_mapper/plugins.rb +14 -0
  14. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  15. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  16. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  18. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  19. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  20. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  21. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  23. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  25. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  26. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  27. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  28. data/lib/mongo_mapper/plugins/callbacks.rb +76 -0
  29. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  30. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  31. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  32. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  33. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  34. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  35. data/lib/mongo_mapper/plugins/keys.rb +300 -0
  36. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  37. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  38. data/lib/mongo_mapper/plugins/protected.rb +41 -0
  39. data/lib/mongo_mapper/plugins/rails.rb +45 -0
  40. data/lib/mongo_mapper/plugins/serialization.rb +105 -0
  41. data/lib/mongo_mapper/plugins/validations.rb +62 -0
  42. data/lib/mongo_mapper/support.rb +214 -0
  43. data/mongo_mapper.gemspec +179 -0
  44. data/performance/read_write.rb +52 -0
  45. data/specs.watchr +51 -0
  46. data/test/NOTE_ON_TESTING +1 -0
  47. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  48. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  49. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  50. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  51. data/test/functional/associations/test_many_documents_proxy.rb +431 -0
  52. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  53. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  54. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  55. data/test/functional/associations/test_one_proxy.rb +161 -0
  56. data/test/functional/test_associations.rb +44 -0
  57. data/test/functional/test_binary.rb +27 -0
  58. data/test/functional/test_callbacks.rb +81 -0
  59. data/test/functional/test_dirty.rb +163 -0
  60. data/test/functional/test_document.rb +1245 -0
  61. data/test/functional/test_embedded_document.rb +125 -0
  62. data/test/functional/test_identity_map.rb +508 -0
  63. data/test/functional/test_logger.rb +20 -0
  64. data/test/functional/test_modifiers.rb +252 -0
  65. data/test/functional/test_pagination.rb +93 -0
  66. data/test/functional/test_protected.rb +139 -0
  67. data/test/functional/test_string_id_compatibility.rb +67 -0
  68. data/test/functional/test_validations.rb +329 -0
  69. data/test/models.rb +232 -0
  70. data/test/support/custom_matchers.rb +55 -0
  71. data/test/support/timing.rb +16 -0
  72. data/test/test_helper.rb +55 -0
  73. data/test/unit/associations/test_base.rb +207 -0
  74. data/test/unit/associations/test_proxy.rb +105 -0
  75. data/test/unit/serializers/test_json_serializer.rb +189 -0
  76. data/test/unit/test_descendant_appends.rb +71 -0
  77. data/test/unit/test_document.rb +231 -0
  78. data/test/unit/test_dynamic_finder.rb +125 -0
  79. data/test/unit/test_embedded_document.rb +663 -0
  80. data/test/unit/test_finder_options.rb +329 -0
  81. data/test/unit/test_keys.rb +169 -0
  82. data/test/unit/test_mongo_mapper.rb +65 -0
  83. data/test/unit/test_pagination.rb +127 -0
  84. data/test/unit/test_plugins.rb +50 -0
  85. data/test/unit/test_rails.rb +123 -0
  86. data/test/unit/test_rails_compatibility.rb +52 -0
  87. data/test/unit/test_serialization.rb +51 -0
  88. data/test/unit/test_support.rb +354 -0
  89. data/test/unit/test_time_zones.rb +39 -0
  90. data/test/unit/test_validations.rb +544 -0
  91. metadata +248 -0
@@ -0,0 +1,74 @@
1
+ module MongoMapper
2
+ # @api private
3
+ module Finders
4
+ def dynamic_find(finder, args)
5
+ attributes = {}
6
+ finder.attributes.each_with_index do |attr, index|
7
+ attributes[attr] = args[index]
8
+ end
9
+
10
+ options = args.extract_options!.merge(attributes)
11
+
12
+ if result = send(finder.finder, options)
13
+ result
14
+ else
15
+ if finder.raise?
16
+ raise DocumentNotFound, "Couldn't find Document with #{attributes.inspect} in collection named #{collection.name}"
17
+ end
18
+
19
+ if finder.instantiator
20
+ self.send(finder.instantiator, attributes)
21
+ end
22
+ end
23
+ end
24
+
25
+ protected
26
+ def method_missing(method, *args, &block)
27
+ finder = DynamicFinder.new(method)
28
+
29
+ if finder.found?
30
+ dynamic_find(finder, args)
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+
37
+ class DynamicFinder
38
+ attr_reader :method, :attributes, :finder, :bang, :instantiator
39
+
40
+ def initialize(method)
41
+ @method = method
42
+ @finder = :first
43
+ @bang = false
44
+ match
45
+ end
46
+
47
+ def found?
48
+ @finder.present?
49
+ end
50
+
51
+ def raise?
52
+ bang == true
53
+ end
54
+
55
+ protected
56
+ def match
57
+ case method.to_s
58
+ when /^find_(all_by|by)_([_a-zA-Z]\w*)$/
59
+ @finder = :all if $1 == 'all_by'
60
+ names = $2
61
+ when /^find_by_([_a-zA-Z]\w*)\!$/
62
+ @bang = true
63
+ names = $1
64
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
65
+ @instantiator = $1 == 'initialize' ? :new : :create
66
+ names = $2
67
+ else
68
+ @finder = nil
69
+ end
70
+
71
+ @attributes = names && names.split('_and_')
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,67 @@
1
+ module MongoMapper
2
+ module EmbeddedDocument
3
+ extend DescendantAppends
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ extend Plugins
10
+
11
+ plugin Plugins::Associations
12
+ plugin Plugins::Clone
13
+ plugin Plugins::Descendants
14
+ plugin Plugins::Equality
15
+ plugin Plugins::Inspect
16
+ plugin Plugins::Keys
17
+ plugin Plugins::Logger
18
+ plugin Plugins::Protected
19
+ plugin Plugins::Rails
20
+ plugin Plugins::Serialization
21
+ plugin Plugins::Validations
22
+
23
+ attr_accessor :_root_document, :_parent_document
24
+ end
25
+
26
+ super
27
+ end
28
+
29
+ module ClassMethods
30
+ def embeddable?
31
+ true
32
+ end
33
+
34
+ def embedded_in(owner_name)
35
+ define_method(owner_name) do
36
+ self._parent_document
37
+ end
38
+ end
39
+ end
40
+
41
+ module InstanceMethods
42
+ def save(options={})
43
+ if result = _root_document.try(:save, options)
44
+ @new = false
45
+ end
46
+ result
47
+ end
48
+
49
+ def save!(options={})
50
+ if result = _root_document.try(:save!, options)
51
+ @new = false
52
+ end
53
+ result
54
+ end
55
+
56
+ def update_attributes(attrs={})
57
+ self.attributes = attrs
58
+ self.save
59
+ end
60
+
61
+ def update_attributes!(attrs={})
62
+ self.attributes = attrs
63
+ self.save!
64
+ end
65
+ end # InstanceMethods
66
+ end # EmbeddedDocument
67
+ end # MongoMapper
@@ -0,0 +1,127 @@
1
+ module MongoMapper
2
+ # = Important Note
3
+ # This class is private to MongoMapper and should not be considered part of
4
+ # MongoMapper's public API.
5
+ #
6
+ class FinderOptions
7
+ OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
8
+
9
+ def self.normalized_field(field)
10
+ field.to_s == 'id' ? :_id : field
11
+ end
12
+
13
+ def self.normalized_order_direction(direction)
14
+ direction ||= 'ASC'
15
+ direction.upcase == 'ASC' ? 1 : -1
16
+ end
17
+
18
+ def initialize(model, options)
19
+ raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
20
+
21
+ @model = model
22
+ @options = {}
23
+ @conditions = {}
24
+
25
+ options.each_pair do |key, value|
26
+ key = key.respond_to?(:to_sym) ? key.to_sym : key
27
+ if OptionKeys.include?(key)
28
+ @options[key] = value
29
+ elsif key == :conditions
30
+ @conditions.merge!(value)
31
+ else
32
+ @conditions[key] = value
33
+ end
34
+ end
35
+
36
+ add_sci_scope
37
+ end
38
+
39
+ def criteria
40
+ to_mongo_criteria(@conditions)
41
+ end
42
+
43
+ def options
44
+ fields = @options[:fields] || @options[:select]
45
+ skip = @options[:skip] || @options[:offset] || 0
46
+ limit = @options[:limit] || 0
47
+ sort = @options[:sort] || convert_order_to_sort(@options[:order])
48
+
49
+ {:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
50
+ end
51
+
52
+ def to_a
53
+ [criteria, options]
54
+ end
55
+
56
+ private
57
+ def to_mongo_criteria(conditions, parent_key=nil)
58
+ criteria = {}
59
+
60
+ conditions.each_pair do |field, value|
61
+ field = self.class.normalized_field(field)
62
+
63
+ if @model.object_id_key?(field) && value.is_a?(String)
64
+ value = Mongo::ObjectID.from_string(value)
65
+ end
66
+
67
+ if field.is_a?(SymbolOperator)
68
+ criteria.update(field.to_mm_criteria(value))
69
+ next
70
+ end
71
+
72
+ case value
73
+ when Array
74
+ criteria[field] = operator?(field) ? value : {'$in' => value}
75
+ when Hash
76
+ criteria[field] = to_mongo_criteria(value, field)
77
+ when Time
78
+ criteria[field] = value.utc
79
+ else
80
+ criteria[field] = value
81
+ end
82
+ end
83
+
84
+ criteria
85
+ end
86
+
87
+ def operator?(field)
88
+ field.to_s =~ /^\$/
89
+ end
90
+
91
+ # adds _type single collection inheritance scope for models that need it
92
+ def add_sci_scope
93
+ if @model.single_collection_inherited?
94
+ @conditions[:_type] = @model.to_s
95
+ end
96
+ end
97
+
98
+ def to_mongo_fields(fields)
99
+ return if fields.blank?
100
+
101
+ if fields.respond_to?(:flatten, :compact)
102
+ fields.flatten.compact
103
+ else
104
+ fields.split(',').map { |field| field.strip }
105
+ end
106
+ end
107
+
108
+ def convert_order_to_sort(sort)
109
+ return if sort.blank?
110
+
111
+ if sort.respond_to?(:all?) && sort.all? { |s| s.respond_to?(:to_mm_order) }
112
+ sort.map { |s| s.to_mm_order }
113
+ elsif sort.respond_to?(:to_mm_order)
114
+ [sort.to_mm_order]
115
+ else
116
+ pieces = sort.split(',')
117
+ pieces.map { |s| to_mongo_sort_piece(s) }
118
+ end
119
+ end
120
+
121
+ def to_mongo_sort_piece(str)
122
+ field, direction = str.strip.split(' ')
123
+ direction = FinderOptions.normalized_order_direction(direction)
124
+ [field, direction]
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,14 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ def plugins
4
+ @plugins ||= []
5
+ end
6
+
7
+ def plugin(mod)
8
+ extend mod::ClassMethods if mod.const_defined?(:ClassMethods)
9
+ include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
10
+ mod.configure(self) if mod.respond_to?(:configure)
11
+ plugins << mod
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,104 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Associations
4
+ module ClassMethods
5
+ def belongs_to(association_id, options={}, &extension)
6
+ create_association(:belongs_to, association_id, options, &extension)
7
+ end
8
+
9
+ def many(association_id, options={}, &extension)
10
+ create_association(:many, association_id, options, &extension)
11
+ end
12
+
13
+ def one(association_id, options={}, &extension)
14
+ create_association(:one, association_id, options, &extension)
15
+ end
16
+
17
+ def associations
18
+ @associations ||= HashWithIndifferentAccess.new
19
+ end
20
+
21
+ def associations=(hash)
22
+ @associations = hash
23
+ end
24
+
25
+ def inherited(subclass)
26
+ subclass.associations = associations.dup
27
+ super
28
+ end
29
+
30
+ private
31
+ def create_association(type, name, options, &extension)
32
+ association = Associations::Base.new(type, name, options, &extension)
33
+ associations[association.name] = association
34
+
35
+ define_method(association.name) do
36
+ get_proxy(association)
37
+ end
38
+
39
+ define_method("#{association.name}=") do |value|
40
+ get_proxy(association).replace(value)
41
+ value
42
+ end
43
+
44
+ if association.one? || association.belongs_to?
45
+ define_method("#{association.name}?") do
46
+ get_proxy(association).present?
47
+ end
48
+ end
49
+
50
+ if association.options[:dependent] && association.many? && !association.embeddable?
51
+ after_destroy do |doc|
52
+ case association.options[:dependent]
53
+ when :destroy
54
+ doc.get_proxy(association).destroy_all
55
+ when :delete_all
56
+ doc.get_proxy(association).delete_all
57
+ when :nullify
58
+ doc.get_proxy(association).nullify
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ module InstanceMethods
66
+ def associations
67
+ self.class.associations
68
+ end
69
+
70
+ # @api private?
71
+ def embedded_associations
72
+ associations.select do |name, association|
73
+ association.embeddable?
74
+ end.map do |name, association|
75
+ association
76
+ end
77
+ end
78
+
79
+ def get_proxy(association)
80
+ unless proxy = self.instance_variable_get(association.ivar)
81
+ proxy = association.proxy_class.new(self, association)
82
+ self.instance_variable_set(association.ivar, proxy)
83
+ end
84
+
85
+ proxy
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ require 'mongo_mapper/plugins/associations/base'
93
+ require 'mongo_mapper/plugins/associations/proxy'
94
+ require 'mongo_mapper/plugins/associations/collection'
95
+ require 'mongo_mapper/plugins/associations/embedded_collection'
96
+ require 'mongo_mapper/plugins/associations/many_documents_proxy'
97
+ require 'mongo_mapper/plugins/associations/belongs_to_proxy'
98
+ require 'mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy'
99
+ require 'mongo_mapper/plugins/associations/many_polymorphic_proxy'
100
+ require 'mongo_mapper/plugins/associations/many_embedded_proxy'
101
+ require 'mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy'
102
+ require 'mongo_mapper/plugins/associations/many_documents_as_proxy'
103
+ require 'mongo_mapper/plugins/associations/one_proxy'
104
+ require 'mongo_mapper/plugins/associations/in_array_proxy'
@@ -0,0 +1,121 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Associations
4
+ class Base
5
+ attr_reader :type, :name, :options, :finder_options
6
+
7
+ # Options that should not be considered MongoDB query options/criteria
8
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :polymorphic]
9
+
10
+ def initialize(type, name, options={}, &extension)
11
+ @type, @name, @options, @finder_options = type, name, {}, {}
12
+ options.symbolize_keys!
13
+
14
+ options[:extend] = modulized_extensions(extension, options[:extend])
15
+
16
+ options.each_pair do |key, value|
17
+ if AssociationOptions.include?(key)
18
+ @options[key] = value
19
+ else
20
+ @finder_options[key] = value
21
+ end
22
+ end
23
+ end
24
+
25
+ def class_name
26
+ @class_name ||= begin
27
+ if cn = options[:class_name]
28
+ cn
29
+ elsif many?
30
+ name.to_s.singularize.camelize
31
+ else
32
+ name.to_s.camelize
33
+ end
34
+ end
35
+ end
36
+
37
+ def klass
38
+ @klass ||= options[:class] || class_name.constantize
39
+ end
40
+
41
+ def many?
42
+ @many_type ||= @type == :many
43
+ end
44
+
45
+ def belongs_to?
46
+ @belongs_to_type ||= @type == :belongs_to
47
+ end
48
+
49
+ def one?
50
+ @one_type ||= @type == :one
51
+ end
52
+
53
+ def polymorphic?
54
+ !!@options[:polymorphic]
55
+ end
56
+
57
+ def as?
58
+ !!@options[:as]
59
+ end
60
+
61
+ def in_array?
62
+ !!@options[:in]
63
+ end
64
+
65
+ def embeddable?
66
+ many? && klass.embeddable?
67
+ end
68
+
69
+ def type_key_name
70
+ @type_key_name ||= many? ? '_type' : "#{as}_type"
71
+ end
72
+
73
+ def as
74
+ @options[:as] || self.name
75
+ end
76
+
77
+ def foreign_key
78
+ @options[:foreign_key] || "#{name}_id"
79
+ end
80
+
81
+ def ivar
82
+ @ivar ||= "@_#{name}"
83
+ end
84
+
85
+ def proxy_class
86
+ @proxy_class ||= begin
87
+ if many?
88
+ if self.klass.embeddable?
89
+ polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
90
+ else
91
+ if polymorphic?
92
+ ManyPolymorphicProxy
93
+ elsif as?
94
+ ManyDocumentsAsProxy
95
+ elsif in_array?
96
+ InArrayProxy
97
+ else
98
+ ManyDocumentsProxy
99
+ end
100
+ end
101
+ elsif one?
102
+ OneProxy
103
+ else
104
+ polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
105
+ end
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # @param [Array<Module, Proc>] extensions a collection of Modules or
112
+ # Procs that extend the behaviour of this association.
113
+ def modulized_extensions(*extensions)
114
+ extensions.flatten.compact.map do |extension|
115
+ Proc === extension ? Module.new(&extension) : extension
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end