numon 0.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 (99) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +31 -0
  4. data/Rakefile +52 -0
  5. data/bin/mmconsole +60 -0
  6. data/lib/mongo_mapper.rb +138 -0
  7. data/lib/mongo_mapper/document.rb +359 -0
  8. data/lib/mongo_mapper/embedded_document.rb +61 -0
  9. data/lib/mongo_mapper/plugins.rb +34 -0
  10. data/lib/mongo_mapper/plugins/associations.rb +105 -0
  11. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  12. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  13. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  14. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  15. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +39 -0
  16. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +144 -0
  17. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +120 -0
  19. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  20. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  21. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  22. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  23. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  24. data/lib/mongo_mapper/plugins/callbacks.rb +234 -0
  25. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  26. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  27. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  28. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  29. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  30. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  31. data/lib/mongo_mapper/plugins/keys.rb +336 -0
  32. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  33. data/lib/mongo_mapper/plugins/modifiers.rb +87 -0
  34. data/lib/mongo_mapper/plugins/pagination.rb +24 -0
  35. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  36. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  37. data/lib/mongo_mapper/plugins/rails.rb +53 -0
  38. data/lib/mongo_mapper/plugins/serialization.rb +75 -0
  39. data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
  40. data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
  41. data/lib/mongo_mapper/plugins/validations.rb +46 -0
  42. data/lib/mongo_mapper/query.rb +130 -0
  43. data/lib/mongo_mapper/support.rb +216 -0
  44. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  45. data/lib/mongo_mapper/support/find.rb +77 -0
  46. data/lib/mongo_mapper/version.rb +3 -0
  47. data/numon.gemspec +207 -0
  48. data/performance/read_write.rb +52 -0
  49. data/specs.watchr +51 -0
  50. data/test/NOTE_ON_TESTING +1 -0
  51. data/test/active_model_lint_test.rb +11 -0
  52. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  53. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  54. data/test/functional/associations/test_in_array_proxy.rb +325 -0
  55. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  56. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  57. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  58. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  59. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  60. data/test/functional/associations/test_one_proxy.rb +161 -0
  61. data/test/functional/test_associations.rb +44 -0
  62. data/test/functional/test_binary.rb +27 -0
  63. data/test/functional/test_callbacks.rb +151 -0
  64. data/test/functional/test_dirty.rb +163 -0
  65. data/test/functional/test_document.rb +1165 -0
  66. data/test/functional/test_embedded_document.rb +130 -0
  67. data/test/functional/test_identity_map.rb +508 -0
  68. data/test/functional/test_indexing.rb +44 -0
  69. data/test/functional/test_logger.rb +20 -0
  70. data/test/functional/test_modifiers.rb +322 -0
  71. data/test/functional/test_pagination.rb +93 -0
  72. data/test/functional/test_protected.rb +161 -0
  73. data/test/functional/test_string_id_compatibility.rb +67 -0
  74. data/test/functional/test_timestamps.rb +64 -0
  75. data/test/functional/test_userstamps.rb +28 -0
  76. data/test/functional/test_validations.rb +329 -0
  77. data/test/models.rb +232 -0
  78. data/test/support/custom_matchers.rb +55 -0
  79. data/test/support/timing.rb +16 -0
  80. data/test/test_helper.rb +61 -0
  81. data/test/unit/associations/test_base.rb +207 -0
  82. data/test/unit/associations/test_proxy.rb +105 -0
  83. data/test/unit/serializers/test_json_serializer.rb +202 -0
  84. data/test/unit/test_descendant_appends.rb +71 -0
  85. data/test/unit/test_document.rb +231 -0
  86. data/test/unit/test_dynamic_finder.rb +123 -0
  87. data/test/unit/test_embedded_document.rb +663 -0
  88. data/test/unit/test_keys.rb +173 -0
  89. data/test/unit/test_mongo_mapper.rb +155 -0
  90. data/test/unit/test_pagination.rb +160 -0
  91. data/test/unit/test_plugins.rb +50 -0
  92. data/test/unit/test_query.rb +340 -0
  93. data/test/unit/test_rails.rb +123 -0
  94. data/test/unit/test_rails_compatibility.rb +52 -0
  95. data/test/unit/test_serialization.rb +51 -0
  96. data/test/unit/test_support.rb +366 -0
  97. data/test/unit/test_time_zones.rb +39 -0
  98. data/test/unit/test_validations.rb +544 -0
  99. metadata +305 -0
@@ -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,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,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.to_sym) }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Rails
4
+ def self.configure(model)
5
+ model.extend ActiveModel::Naming if defined?(ActiveModel)
6
+ end
7
+
8
+ module InstanceMethods
9
+ def to_param
10
+ id.to_s
11
+ end
12
+
13
+ def to_model
14
+ self
15
+ end
16
+
17
+ def new_record?
18
+ new?
19
+ end
20
+
21
+ def read_attribute(name)
22
+ self[name]
23
+ end
24
+
25
+ def read_attribute_before_typecast(name)
26
+ read_key_before_typecast(name)
27
+ end
28
+
29
+ def write_attribute(name, value)
30
+ self[name] = value
31
+ end
32
+ end
33
+
34
+ module ClassMethods
35
+ def has_one(*args)
36
+ one(*args)
37
+ end
38
+
39
+ def has_many(*args)
40
+ many(*args)
41
+ end
42
+
43
+ def column_names
44
+ keys.keys
45
+ end
46
+
47
+ def human_name
48
+ self.name.demodulize.titleize
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,75 @@
1
+ require 'active_support/json'
2
+
3
+ module MongoMapper
4
+ module Plugins
5
+ module Serialization
6
+ def self.configure(model)
7
+ model.class_eval { cattr_accessor :include_root_in_json, :instance_writer => true }
8
+ end
9
+
10
+ module InstanceMethods
11
+ def as_json options={}
12
+ options ||= {}
13
+ unless options[:only]
14
+ methods = [options.delete(:methods)].flatten.compact
15
+ methods << :id
16
+ options[:methods] = methods.uniq
17
+ end
18
+
19
+ except = [options.delete(:except)].flatten.compact
20
+ except << :_id
21
+ options[:except] = except
22
+
23
+ # Direct rip from Rails 3 ActiveModel Serialization (#serializable_hash)
24
+ hash = begin
25
+ options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
26
+ options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
27
+
28
+ attribute_names = attributes.keys.sort
29
+ if options[:only].any?
30
+ attribute_names &= options[:only]
31
+ elsif options[:except].any?
32
+ attribute_names -= options[:except]
33
+ end
34
+
35
+ method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
36
+ methods << name if respond_to?(name.to_s)
37
+ methods
38
+ end
39
+
40
+ (attribute_names + method_names).inject({}) { |hash, name|
41
+ hash[name] = send(name)
42
+ hash
43
+ }
44
+ end
45
+ # End rip
46
+
47
+ options.delete(:only) if options[:only].nil? or options[:only].empty?
48
+
49
+ hash.each do |key, value|
50
+ if value.is_a?(Array)
51
+ hash[key] = value.map do |item|
52
+ item.respond_to?(:as_json) ? item.as_json(options) : item
53
+ end
54
+ elsif value.is_a? Mongo::ObjectID
55
+ hash[key] = value.to_s
56
+ elsif value.respond_to?(:as_json)
57
+ hash[key] = value.as_json(options)
58
+ end
59
+ end
60
+
61
+ # Replicate Rails 3 naming - and also bin anytihng after : for use in our dynamic classes from unit tests
62
+ hash = { ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).gsub(/:.*/,'') => hash } if include_root_in_json
63
+ hash
64
+ end
65
+ end
66
+
67
+ module ClassMethods
68
+ def from_json(json)
69
+ self.new(ActiveSupport::JSON.decode(json))
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,21 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Timestamps
4
+ module ClassMethods
5
+ def timestamps!
6
+ key :created_at, Time
7
+ key :updated_at, Time
8
+ class_eval { before_save :update_timestamps }
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def update_timestamps
14
+ now = Time.now.utc
15
+ self[:created_at] = now if new? && !created_at?
16
+ self[:updated_at] = now
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Userstamps
4
+ module ClassMethods
5
+ def userstamps!
6
+ key :creator_id, ObjectId
7
+ key :updater_id, ObjectId
8
+ belongs_to :creator, :class_name => 'User'
9
+ belongs_to :updater, :class_name => 'User'
10
+ end
11
+ end
12
+ end
13
+ end
14
+ 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
+ [normalized_key(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