mongo_mapper 0.6.10 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/README.rdoc +5 -14
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongo_mapper.rb +48 -56
  5. data/lib/mongo_mapper/document.rb +136 -164
  6. data/lib/mongo_mapper/embedded_document.rb +29 -354
  7. data/lib/mongo_mapper/plugins.rb +31 -0
  8. data/lib/mongo_mapper/plugins/associations.rb +105 -0
  9. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  10. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  11. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  12. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  13. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  14. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  15. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  16. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  17. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  18. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  19. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  20. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  21. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  22. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  23. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  24. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  25. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  26. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  27. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  28. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  29. data/lib/mongo_mapper/plugins/keys.rb +324 -0
  30. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  31. data/lib/mongo_mapper/plugins/pagination.rb +24 -0
  32. data/lib/mongo_mapper/plugins/pagination/proxy.rb +68 -0
  33. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  34. data/lib/mongo_mapper/plugins/rails.rb +45 -0
  35. data/lib/mongo_mapper/plugins/serialization.rb +105 -0
  36. data/lib/mongo_mapper/plugins/validations.rb +46 -0
  37. data/lib/mongo_mapper/query.rb +130 -0
  38. data/lib/mongo_mapper/support.rb +40 -17
  39. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  40. data/lib/mongo_mapper/support/find.rb +77 -0
  41. data/mongo_mapper.gemspec +55 -38
  42. data/performance/read_write.rb +52 -0
  43. data/specs.watchr +23 -2
  44. data/test/functional/associations/test_belongs_to_proxy.rb +12 -10
  45. data/test/functional/associations/test_many_documents_as_proxy.rb +4 -21
  46. data/test/functional/associations/test_many_documents_proxy.rb +2 -8
  47. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +59 -39
  48. data/test/functional/associations/test_many_embedded_proxy.rb +145 -81
  49. data/test/functional/associations/test_many_polymorphic_proxy.rb +2 -40
  50. data/test/functional/associations/test_one_proxy.rb +25 -10
  51. data/test/functional/test_binary.rb +2 -8
  52. data/test/functional/test_callbacks.rb +1 -5
  53. data/test/functional/test_dirty.rb +27 -23
  54. data/test/functional/test_document.rb +224 -165
  55. data/test/functional/test_embedded_document.rb +72 -82
  56. data/test/functional/test_identity_map.rb +508 -0
  57. data/test/functional/test_modifiers.rb +15 -5
  58. data/test/functional/test_pagination.rb +1 -3
  59. data/test/functional/test_protected.rb +155 -0
  60. data/test/functional/test_string_id_compatibility.rb +7 -12
  61. data/test/functional/test_validations.rb +26 -58
  62. data/test/models.rb +0 -39
  63. data/test/test_helper.rb +37 -3
  64. data/test/unit/associations/test_base.rb +5 -5
  65. data/test/unit/associations/test_proxy.rb +8 -6
  66. data/test/unit/test_descendant_appends.rb +71 -0
  67. data/test/unit/test_document.rb +71 -76
  68. data/test/unit/test_dynamic_finder.rb +27 -29
  69. data/test/unit/test_embedded_document.rb +555 -601
  70. data/test/unit/{test_key.rb → test_keys.rb} +2 -5
  71. data/test/unit/test_mongo_mapper.rb +69 -9
  72. data/test/unit/test_pagination.rb +40 -32
  73. data/test/unit/test_plugins.rb +50 -0
  74. data/test/unit/{test_finder_options.rb → test_query.rb} +74 -65
  75. data/test/unit/test_rails.rb +123 -0
  76. data/test/unit/{test_serializations.rb → test_serialization.rb} +1 -2
  77. data/test/unit/test_support.rb +23 -7
  78. data/test/unit/test_time_zones.rb +3 -4
  79. data/test/unit/test_validations.rb +58 -17
  80. metadata +53 -36
  81. data/lib/mongo_mapper/associations.rb +0 -78
  82. data/lib/mongo_mapper/associations/base.rb +0 -119
  83. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +0 -26
  84. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +0 -21
  85. data/lib/mongo_mapper/associations/collection.rb +0 -19
  86. data/lib/mongo_mapper/associations/in_array_proxy.rb +0 -137
  87. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -26
  88. data/lib/mongo_mapper/associations/many_documents_proxy.rb +0 -115
  89. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +0 -31
  90. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +0 -54
  91. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +0 -11
  92. data/lib/mongo_mapper/associations/one_proxy.rb +0 -64
  93. data/lib/mongo_mapper/associations/proxy.rb +0 -116
  94. data/lib/mongo_mapper/callbacks.rb +0 -61
  95. data/lib/mongo_mapper/dirty.rb +0 -117
  96. data/lib/mongo_mapper/dynamic_finder.rb +0 -74
  97. data/lib/mongo_mapper/finder_options.rb +0 -145
  98. data/lib/mongo_mapper/key.rb +0 -36
  99. data/lib/mongo_mapper/mongo_mapper.rb +0 -125
  100. data/lib/mongo_mapper/pagination.rb +0 -66
  101. data/lib/mongo_mapper/rails_compatibility/document.rb +0 -15
  102. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +0 -28
  103. data/lib/mongo_mapper/serialization.rb +0 -54
  104. data/lib/mongo_mapper/serializers/json_serializer.rb +0 -48
  105. data/lib/mongo_mapper/validations.rb +0 -39
  106. data/test/functional/test_rails_compatibility.rb +0 -25
@@ -0,0 +1,17 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Logger
4
+ module ClassMethods
5
+ def logger
6
+ MongoMapper.logger
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def logger
12
+ self.class.logger
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Pagination
4
+ module ClassMethods
5
+ def per_page
6
+ 25
7
+ end
8
+
9
+ def paginate(options)
10
+ per_page = options.delete(:per_page) || self.per_page
11
+ page = options.delete(:page)
12
+ total_entries = count(options)
13
+ pagination = Pagination::Proxy.new(total_entries, page, per_page)
14
+
15
+ options.update(:limit => pagination.limit, :skip => pagination.skip)
16
+ pagination.subject = find_many(options)
17
+ pagination
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ require 'mongo_mapper/plugins/pagination/proxy'
@@ -0,0 +1,68 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Pagination
4
+ class Proxy
5
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
6
+
7
+ attr_accessor :subject
8
+ attr_reader :total_entries, :per_page, :current_page
9
+ alias limit per_page
10
+
11
+ def initialize(total_entries, current_page, per_page=nil)
12
+ @total_entries = total_entries.to_i
13
+ self.per_page = per_page
14
+ self.current_page = current_page
15
+ end
16
+
17
+ def total_pages
18
+ (total_entries / per_page.to_f).ceil
19
+ end
20
+
21
+ def out_of_bounds?
22
+ current_page > total_pages
23
+ end
24
+
25
+ def previous_page
26
+ current_page > 1 ? (current_page - 1) : nil
27
+ end
28
+
29
+ def next_page
30
+ current_page < total_pages ? (current_page + 1) : nil
31
+ end
32
+
33
+ def skip
34
+ (current_page - 1) * per_page
35
+ end
36
+ alias offset skip # for will paginate support
37
+
38
+ def send(method, *args, &block)
39
+ if respond_to?(method)
40
+ super
41
+ else
42
+ subject.send(method, *args, &block)
43
+ end
44
+ end
45
+
46
+ def ===(other)
47
+ other === subject
48
+ end
49
+
50
+ def method_missing(name, *args, &block)
51
+ @subject.send(name, *args, &block)
52
+ end
53
+
54
+ private
55
+ def per_page=(value)
56
+ value = 25 if value.blank?
57
+ @per_page = value.to_i
58
+ end
59
+
60
+ def current_page=(value)
61
+ value = value.to_i
62
+ value = 1 if value < 1
63
+ @current_page = value
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,45 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Protected
4
+ module ClassMethods
5
+ def attr_protected(*attrs)
6
+ self.write_inheritable_attribute(:attr_protected, Set.new(attrs) + (protected_attributes || []))
7
+ end
8
+
9
+ def protected_attributes
10
+ self.read_inheritable_attribute(:attr_protected)
11
+ end
12
+
13
+ def key(*args)
14
+ key = super
15
+ attr_protected key.name.to_sym if key.options[:protected]
16
+ key
17
+ end
18
+ end
19
+
20
+ module InstanceMethods
21
+ def assign(attrs={})
22
+ super(filter_protected_attrs(attrs))
23
+ end
24
+
25
+ def update_attributes(attrs={})
26
+ super(filter_protected_attrs(attrs))
27
+ end
28
+
29
+ def update_attributes!(attrs={})
30
+ super(filter_protected_attrs(attrs))
31
+ end
32
+
33
+ def protected_attributes
34
+ self.class.protected_attributes
35
+ end
36
+
37
+ protected
38
+ def filter_protected_attrs(attrs)
39
+ return attrs if protected_attributes.blank?
40
+ attrs.dup.delete_if { |key, val| protected_attributes.include?(key) }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Rails
4
+ module InstanceMethods
5
+ def to_param
6
+ id.to_s
7
+ end
8
+
9
+ def new_record?
10
+ new?
11
+ end
12
+
13
+ def read_attribute(name)
14
+ self[name]
15
+ end
16
+
17
+ def read_attribute_before_typecast(name)
18
+ read_key_before_typecast(name)
19
+ end
20
+
21
+ def write_attribute(name, value)
22
+ self[name] = value
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def has_one(*args)
28
+ one(*args)
29
+ end
30
+
31
+ def has_many(*args)
32
+ many(*args)
33
+ end
34
+
35
+ def column_names
36
+ keys.keys
37
+ end
38
+
39
+ def human_name
40
+ self.name.demodulize.titleize
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,105 @@
1
+ require 'active_support/json'
2
+
3
+ module MongoMapper
4
+ module Plugins
5
+ module Serialization
6
+ def self.configure(model)
7
+ model.class_eval { include Json }
8
+ end
9
+
10
+ class Serializer
11
+ attr_reader :options
12
+
13
+ def initialize(record, options={})
14
+ @record, @options = record, options.dup
15
+ end
16
+
17
+ def serializable_key_names
18
+ key_names = @record.attributes.keys
19
+
20
+ if options[:only]
21
+ options.delete(:except)
22
+ key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
23
+ else
24
+ options[:except] = Array(options[:except])
25
+ key_names = key_names - options[:except].collect { |n| n.to_s }
26
+ end
27
+
28
+ key_names
29
+ end
30
+
31
+ def serializable_method_names
32
+ Array(options[:methods]).inject([]) do |method_attributes, name|
33
+ method_attributes << name if @record.respond_to?(name.to_s)
34
+ method_attributes
35
+ end
36
+ end
37
+
38
+ def serializable_names
39
+ serializable_key_names + serializable_method_names
40
+ end
41
+
42
+ def serializable_record
43
+ returning(serializable_record = {}) do
44
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
45
+ end
46
+ end
47
+
48
+ def serialize
49
+ # overwrite to implement
50
+ end
51
+
52
+ def to_s(&block)
53
+ serialize(&block)
54
+ end
55
+ end
56
+
57
+ module Json
58
+ def self.included(base)
59
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
60
+ base.extend ClassMethods
61
+ end
62
+
63
+ module ClassMethods
64
+ def json_class_name
65
+ @json_class_name ||= name.demodulize.underscore.inspect
66
+ end
67
+ end
68
+
69
+ def to_json(options={})
70
+ apply_to_json_defaults(options)
71
+
72
+ if include_root_in_json
73
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
74
+ else
75
+ JsonSerializer.new(self, options).to_s
76
+ end
77
+ end
78
+
79
+ def from_json(json)
80
+ self.attributes = ActiveSupport::JSON.decode(json)
81
+ self
82
+ end
83
+
84
+ class JsonSerializer < Serializer
85
+ def serialize
86
+ serializable_record.to_json
87
+ end
88
+ end
89
+
90
+ private
91
+ def apply_to_json_defaults(options)
92
+ unless options[:only]
93
+ methods = [options.delete(:methods)].flatten.compact
94
+ methods << :id
95
+ options[:methods] = methods.uniq
96
+ end
97
+
98
+ except = [options.delete(:except)].flatten.compact
99
+ except << :_id
100
+ options[:except] = except
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,46 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Validations
4
+ def self.configure(model)
5
+ model.class_eval { include Validatable }
6
+ end
7
+
8
+ module DocumentMacros
9
+ def validates_uniqueness_of(*args)
10
+ add_validations(args, MongoMapper::Plugins::Validations::ValidatesUniquenessOf)
11
+ end
12
+ end
13
+
14
+ class ValidatesUniquenessOf < Validatable::ValidationBase
15
+ option :scope, :case_sensitive
16
+ default :case_sensitive => true
17
+
18
+ def valid?(instance)
19
+ value = instance[attribute]
20
+ return true if allow_blank && value.blank?
21
+ return true if allow_nil && value.nil?
22
+ base_conditions = case_sensitive ? {self.attribute => value} : {}
23
+ doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
24
+ doc.nil? || instance._id == doc._id
25
+ end
26
+
27
+ def message(instance)
28
+ super || "has already been taken"
29
+ end
30
+
31
+ def scope_conditions(instance)
32
+ return {} unless scope
33
+ Array(scope).inject({}) do |conditions, key|
34
+ conditions.merge(key => instance[key])
35
+ end
36
+ end
37
+
38
+ def where_conditions(instance)
39
+ conditions = {}
40
+ conditions[attribute] = /#{instance[attribute].to_s}/i unless case_sensitive
41
+ conditions
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,130 @@
1
+ module MongoMapper
2
+ # IMPORTANT
3
+ # This class is private to MongoMapper and should not be considered part of MongoMapper's public API.
4
+ #
5
+ class Query
6
+ OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
7
+
8
+ attr_reader :model
9
+
10
+ def initialize(model, options)
11
+ raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
12
+ @model, @options, @conditions, @original_options = model, {}, {}, options
13
+ separate_options_and_conditions
14
+ add_sci_condition
15
+ end
16
+
17
+ def criteria
18
+ to_criteria(@conditions)
19
+ end
20
+
21
+ def options
22
+ fields = @options[:fields] || @options[:select]
23
+ skip = @options[:skip] || @options[:offset] || 0
24
+ limit = @options[:limit] || 0
25
+ sort = @options[:sort] || normalized_sort(@options[:order])
26
+
27
+ {:fields => to_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
28
+ end
29
+
30
+ def to_a
31
+ [criteria, options]
32
+ end
33
+
34
+ private
35
+ def separate_options_and_conditions
36
+ @original_options.each_pair do |key, value|
37
+ key = key.respond_to?(:to_sym) ? key.to_sym : key
38
+
39
+ if OptionKeys.include?(key)
40
+ @options[key] = value
41
+ elsif key == :conditions
42
+ @conditions.update(value)
43
+ else
44
+ @conditions[key] = value
45
+ end
46
+ end
47
+ end
48
+
49
+ # adds _type single collection inheritance scope for models that need it
50
+ def add_sci_condition
51
+ @conditions[:_type] = model.to_s if model.single_collection_inherited?
52
+ end
53
+
54
+ def modifier?(field)
55
+ field.to_s =~ /^\$/
56
+ end
57
+
58
+ def symbol_operator?(object)
59
+ object.respond_to?(:field, :operator)
60
+ end
61
+
62
+ def to_criteria(conditions, parent_key=nil)
63
+ criteria = {}
64
+
65
+ conditions.each_pair do |key, value|
66
+ key = normalized_key(key)
67
+
68
+ if model.object_id_key?(key) && value.is_a?(String)
69
+ value = Mongo::ObjectID.from_string(value)
70
+ end
71
+
72
+ if symbol_operator?(key)
73
+ value = {"$#{key.operator}" => value}
74
+ key = normalized_key(key.field)
75
+ end
76
+
77
+ criteria[key] = normalized_value(key, value)
78
+ end
79
+
80
+ criteria
81
+ end
82
+
83
+ def to_fields(fields)
84
+ return if fields.blank?
85
+
86
+ if fields.respond_to?(:flatten, :compact)
87
+ fields.flatten.compact
88
+ else
89
+ fields.split(',').map { |field| field.strip }
90
+ end
91
+ end
92
+
93
+ def to_order(field, direction=nil)
94
+ direction ||= 'ASC'
95
+ direction = direction.upcase == 'ASC' ? 1 : -1
96
+ [field.to_s, direction]
97
+ end
98
+
99
+ def normalized_key(field)
100
+ field.to_s == 'id' ? :_id : field
101
+ end
102
+
103
+ def normalized_value(field, value)
104
+ case value
105
+ when Array
106
+ modifier?(field) ? value : {'$in' => value}
107
+ when Hash
108
+ to_criteria(value, field)
109
+ when Time
110
+ value.utc
111
+ else
112
+ value
113
+ end
114
+ end
115
+
116
+ def normalized_sort(sort)
117
+ return if sort.blank?
118
+
119
+ if sort.respond_to?(:all?) && sort.all? { |s| symbol_operator?(s) }
120
+ sort.map { |s| to_order(s.field, s.operator) }
121
+ elsif symbol_operator?(sort)
122
+ [to_order(sort.field, sort.operator)]
123
+ else
124
+ sort.split(',').map do |str|
125
+ to_order(*str.strip.split(' '))
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end