mongo_mapper-rails3 0.7.0.1

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