mongo_mapper-rails3 0.7.0.1

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 (94) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +60 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +60 -0
  8. data/lib/mongo_mapper.rb +131 -0
  9. data/lib/mongo_mapper/document.rb +439 -0
  10. data/lib/mongo_mapper/embedded_document.rb +68 -0
  11. data/lib/mongo_mapper/plugins.rb +30 -0
  12. data/lib/mongo_mapper/plugins/associations.rb +106 -0
  13. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  14. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  15. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  16. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  17. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  18. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +141 -0
  19. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  20. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +120 -0
  21. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  22. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  23. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  24. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  25. data/lib/mongo_mapper/plugins/associations/proxy.rb +119 -0
  26. data/lib/mongo_mapper/plugins/callbacks.rb +87 -0
  27. data/lib/mongo_mapper/plugins/clone.rb +14 -0
  28. data/lib/mongo_mapper/plugins/descendants.rb +17 -0
  29. data/lib/mongo_mapper/plugins/dirty.rb +120 -0
  30. data/lib/mongo_mapper/plugins/equality.rb +24 -0
  31. data/lib/mongo_mapper/plugins/identity_map.rb +124 -0
  32. data/lib/mongo_mapper/plugins/inspect.rb +15 -0
  33. data/lib/mongo_mapper/plugins/keys.rb +310 -0
  34. data/lib/mongo_mapper/plugins/logger.rb +19 -0
  35. data/lib/mongo_mapper/plugins/pagination.rb +26 -0
  36. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  37. data/lib/mongo_mapper/plugins/protected.rb +46 -0
  38. data/lib/mongo_mapper/plugins/rails.rb +46 -0
  39. data/lib/mongo_mapper/plugins/serialization.rb +50 -0
  40. data/lib/mongo_mapper/plugins/validations.rb +88 -0
  41. data/lib/mongo_mapper/query.rb +130 -0
  42. data/lib/mongo_mapper/support.rb +217 -0
  43. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  44. data/lib/mongo_mapper/support/find.rb +77 -0
  45. data/mongo_mapper-rails3.gemspec +208 -0
  46. data/performance/read_write.rb +52 -0
  47. data/specs.watchr +51 -0
  48. data/test/NOTE_ON_TESTING +1 -0
  49. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  50. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  51. data/test/functional/associations/test_in_array_proxy.rb +321 -0
  52. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  53. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  54. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  55. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  56. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  57. data/test/functional/associations/test_one_proxy.rb +161 -0
  58. data/test/functional/test_associations.rb +44 -0
  59. data/test/functional/test_binary.rb +27 -0
  60. data/test/functional/test_callbacks.rb +81 -0
  61. data/test/functional/test_dirty.rb +163 -0
  62. data/test/functional/test_document.rb +1244 -0
  63. data/test/functional/test_embedded_document.rb +125 -0
  64. data/test/functional/test_identity_map.rb +508 -0
  65. data/test/functional/test_logger.rb +20 -0
  66. data/test/functional/test_modifiers.rb +252 -0
  67. data/test/functional/test_pagination.rb +93 -0
  68. data/test/functional/test_protected.rb +161 -0
  69. data/test/functional/test_string_id_compatibility.rb +67 -0
  70. data/test/functional/test_validations.rb +329 -0
  71. data/test/models.rb +232 -0
  72. data/test/support/custom_matchers.rb +55 -0
  73. data/test/support/timing.rb +16 -0
  74. data/test/test_helper.rb +59 -0
  75. data/test/unit/associations/test_base.rb +207 -0
  76. data/test/unit/associations/test_proxy.rb +105 -0
  77. data/test/unit/serializers/test_json_serializer.rb +189 -0
  78. data/test/unit/test_descendant_appends.rb +71 -0
  79. data/test/unit/test_document.rb +231 -0
  80. data/test/unit/test_dynamic_finder.rb +123 -0
  81. data/test/unit/test_embedded_document.rb +663 -0
  82. data/test/unit/test_keys.rb +169 -0
  83. data/test/unit/test_lint.rb +8 -0
  84. data/test/unit/test_mongo_mapper.rb +125 -0
  85. data/test/unit/test_pagination.rb +160 -0
  86. data/test/unit/test_plugins.rb +51 -0
  87. data/test/unit/test_query.rb +334 -0
  88. data/test/unit/test_rails.rb +123 -0
  89. data/test/unit/test_rails_compatibility.rb +57 -0
  90. data/test/unit/test_serialization.rb +51 -0
  91. data/test/unit/test_support.rb +362 -0
  92. data/test/unit/test_time_zones.rb +39 -0
  93. data/test/unit/test_validations.rb +557 -0
  94. metadata +344 -0
@@ -0,0 +1,19 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Logger
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def logger
8
+ MongoMapper.logger
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def logger
14
+ self.class.logger
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Pagination
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def per_page
8
+ 25
9
+ end
10
+
11
+ def paginate(options)
12
+ per_page = options.delete(:per_page) || self.per_page
13
+ page = options.delete(:page)
14
+ total_entries = count(options)
15
+ pagination = Pagination::Proxy.new(total_entries, page, per_page)
16
+
17
+ options.update(:limit => pagination.limit, :skip => pagination.skip)
18
+ pagination.subject = find_many(options)
19
+ pagination
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'mongo_mapper/plugins/pagination/proxy'
@@ -0,0 +1,72 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Pagination
4
+ class Proxy
5
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|respond_to\?|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
+ def respond_to?(name, *args, &block)
55
+ super || @subject.respond_to?(name, *args, &block)
56
+ end
57
+
58
+ private
59
+ def per_page=(value)
60
+ value = 25 if value.blank?
61
+ @per_page = value.to_i
62
+ end
63
+
64
+ def current_page=(value)
65
+ value = value.to_i
66
+ value = 1 if value < 1
67
+ @current_page = value
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,46 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Protected
4
+ extend ActiveSupport::Concern
5
+ module ClassMethods
6
+ def attr_protected(*attrs)
7
+ self.write_inheritable_attribute(:attr_protected, Set.new(attrs) + (protected_attributes || []))
8
+ end
9
+
10
+ def protected_attributes
11
+ self.read_inheritable_attribute(:attr_protected)
12
+ end
13
+
14
+ def key(*args)
15
+ key = super
16
+ attr_protected key.name.to_sym if key.options[:protected]
17
+ key
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ def assign(attrs={})
23
+ super(filter_protected_attrs(attrs))
24
+ end
25
+
26
+ def update_attributes(attrs={})
27
+ super(filter_protected_attrs(attrs))
28
+ end
29
+
30
+ def update_attributes!(attrs={})
31
+ super(filter_protected_attrs(attrs))
32
+ end
33
+
34
+ def protected_attributes
35
+ self.class.protected_attributes
36
+ end
37
+
38
+ protected
39
+ def filter_protected_attrs(attrs)
40
+ return attrs if protected_attributes.blank?
41
+ attrs.dup.delete_if { |key, val| protected_attributes.include?(key.to_sym) }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Rails
4
+ extend ActiveSupport::Concern
5
+ module InstanceMethods
6
+ def to_param
7
+ id.to_s
8
+ end
9
+
10
+ def new_record?
11
+ new?
12
+ end
13
+
14
+ def read_attribute(name)
15
+ self[name]
16
+ end
17
+
18
+ def read_attribute_before_typecast(name)
19
+ read_key_before_typecast(name)
20
+ end
21
+
22
+ def write_attribute(name, value)
23
+ self[name] = value
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def has_one(*args)
29
+ one(*args)
30
+ end
31
+
32
+ def has_many(*args)
33
+ many(*args)
34
+ end
35
+
36
+ def column_names
37
+ keys.keys
38
+ end
39
+
40
+ def human
41
+ self.name.demodulize.titleize
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,50 @@
1
+ require 'active_support/json'
2
+
3
+ module MongoMapper
4
+ module Plugins
5
+ module Serialization
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include ActiveModel::Serializers::JSON
10
+ # Re-include this here otherwise we get run over by ActiveModel serialization
11
+ include SerializableHash
12
+ extend FromJson
13
+ end
14
+
15
+ module SerializableHash
16
+ def serializable_hash options={}
17
+ options ||= {}
18
+ unless options[:only]
19
+ methods = [options.delete(:methods)].flatten.compact
20
+ methods << :id
21
+ options[:methods] = methods.uniq
22
+ end
23
+
24
+ except = [options.delete(:except)].flatten.compact
25
+ except << :_id
26
+ options[:except] = except
27
+
28
+ hash = super(options)
29
+ hash.each do |key, value|
30
+ if value.is_a?(Array)
31
+ hash[key] = value.map do |item|
32
+ item.respond_to?(:serializable_hash) ? item.serializable_hash(options) : item
33
+ end
34
+ elsif value.respond_to?(:serializable_hash)
35
+ hash[key] = value.serializable_hash(options)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ module FromJson
42
+ def from_json(json)
43
+ self.attributes = ActiveSupport::JSON.decode(json)
44
+ self
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,88 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Validations
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ include ::ActiveModel::Validations
7
+ extend FixValidationKeyNames
8
+ extend LifecycleValidationMethods
9
+ end
10
+
11
+ module DocumentMacros
12
+ def validates_uniqueness_of(attrs, ops={})
13
+ # add_validations(args, MongoMapper::Plugins::Validations::ValidatesUniquenessOf)
14
+ validates_with ValidatesUniquenessOf, {:attributes => attrs}.merge(ops)
15
+ end
16
+
17
+
18
+ end
19
+
20
+ module FixValidationKeyNames
21
+ def validates_inclusion_of(*args,&b)
22
+ if args.last.kind_of?(Hash)
23
+ args.last[:in] ||= args.last[:within]
24
+ end
25
+ super(*args,&b)
26
+ end
27
+ def validates_exclusion_of(*args,&b)
28
+ if args.last.kind_of?(Hash)
29
+ args.last[:in] ||= args.last[:within]
30
+ end
31
+ super(*args,&b)
32
+ end
33
+ end
34
+
35
+ module LifecycleValidationMethods
36
+ def validate_on_create(name)
37
+ validate(name, :on => :create)
38
+ end
39
+ def validate_on_update(name)
40
+ validate(name, :on => :update)
41
+ end
42
+ end
43
+
44
+ class ValidatesUniquenessOf < ActiveModel::EachValidator
45
+ attr_accessor :scope, :allow_blank, :allow_nil, :attributes, :case_sensitive
46
+ def case_sensitive
47
+ @case_sensitive.nil? ? true : @case_sensitive
48
+ end
49
+
50
+ def initialize(ops)
51
+ ops.each { |k,v| send("#{k}=",v) }
52
+ end
53
+
54
+ def validate(record)
55
+ [attributes].flatten.each do |attribute|
56
+ record.errors.add(*message(record,attribute)) unless valid?(record,attribute)
57
+ end
58
+ end
59
+
60
+ def valid?(instance,attribute)
61
+ value = instance[attribute]
62
+ return true if allow_blank && value.blank?
63
+ return true if allow_nil && value.nil?
64
+ base_conditions = case_sensitive ? {attribute => value} : {}
65
+ doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance,attribute)))
66
+ doc.nil? || instance._id == doc._id
67
+ end
68
+
69
+ def message(instance,attribute)
70
+ [attribute,"has already been taken"]
71
+ end
72
+
73
+ def scope_conditions(instance)
74
+ return {} unless scope
75
+ Array(scope).inject({}) do |conditions, key|
76
+ conditions.merge(key => instance[key])
77
+ end
78
+ end
79
+
80
+ def where_conditions(instance, attribute)
81
+ conditions = {}
82
+ conditions[attribute] = /#{instance[attribute].to_s}/i unless case_sensitive
83
+ conditions
84
+ end
85
+ end
86
+ end
87
+ end
88
+ 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