jmonteiro-mongo_mapper 0.1.0

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 (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/jmonteiro-mongo_mapper.gemspec +195 -0
  8. data/lib/mongo_mapper.rb +128 -0
  9. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  10. data/lib/mongo_mapper/document.rb +402 -0
  11. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  12. data/lib/mongo_mapper/embedded_document.rb +61 -0
  13. data/lib/mongo_mapper/finder_options.rb +127 -0
  14. data/lib/mongo_mapper/plugins.rb +19 -0
  15. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  16. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +23 -0
  19. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  20. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +49 -0
  21. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  23. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  25. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  26. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  27. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +66 -0
  28. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  29. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  30. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  31. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  32. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  33. data/lib/mongo_mapper/plugins/equality.rb +11 -0
  34. data/lib/mongo_mapper/plugins/identity_map.rb +66 -0
  35. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  36. data/lib/mongo_mapper/plugins/keys.rb +295 -0
  37. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  38. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  39. data/lib/mongo_mapper/plugins/protected.rb +31 -0
  40. data/lib/mongo_mapper/plugins/rails.rb +80 -0
  41. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  42. data/lib/mongo_mapper/plugins/validations.rb +48 -0
  43. data/lib/mongo_mapper/support.rb +213 -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 +93 -0
  49. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  50. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  51. data/test/functional/associations/test_many_documents_proxy.rb +437 -0
  52. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +175 -0
  53. data/test/functional/associations/test_many_embedded_proxy.rb +216 -0
  54. data/test/functional/associations/test_many_polymorphic_proxy.rb +340 -0
  55. data/test/functional/associations/test_one_proxy.rb +149 -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 +156 -0
  60. data/test/functional/test_document.rb +1171 -0
  61. data/test/functional/test_embedded_document.rb +125 -0
  62. data/test/functional/test_identity_map.rb +233 -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 +41 -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 +60 -0
  73. data/test/unit/associations/test_base.rb +207 -0
  74. data/test/unit/associations/test_proxy.rb +103 -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 +203 -0
  78. data/test/unit/test_dynamic_finder.rb +125 -0
  79. data/test/unit/test_embedded_document.rb +628 -0
  80. data/test/unit/test_finder_options.rb +325 -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 +42 -0
  85. data/test/unit/test_rails.rb +139 -0
  86. data/test/unit/test_rails_compatibility.rb +42 -0
  87. data/test/unit/test_serialization.rb +51 -0
  88. data/test/unit/test_support.rb +350 -0
  89. data/test/unit/test_time_zones.rb +39 -0
  90. data/test/unit/test_validations.rb +492 -0
  91. metadata +260 -0
@@ -0,0 +1,109 @@
1
+ require 'active_support/json'
2
+
3
+ module MongoMapper
4
+ module Plugins
5
+ module Serialization
6
+ class Serializer
7
+ attr_reader :options
8
+
9
+ def initialize(record, options={})
10
+ @record, @options = record, options.dup
11
+ end
12
+
13
+ def serializable_key_names
14
+ key_names = @record.attributes.keys
15
+
16
+ if options[:only]
17
+ options.delete(:except)
18
+ key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
19
+ else
20
+ options[:except] = Array(options[:except])
21
+ key_names = key_names - options[:except].collect { |n| n.to_s }
22
+ end
23
+
24
+ key_names
25
+ end
26
+
27
+ def serializable_method_names
28
+ Array(options[:methods]).inject([]) do |method_attributes, name|
29
+ method_attributes << name if @record.respond_to?(name.to_s)
30
+ method_attributes
31
+ end
32
+ end
33
+
34
+ def serializable_names
35
+ serializable_key_names + serializable_method_names
36
+ end
37
+
38
+ def serializable_record
39
+ returning(serializable_record = {}) do
40
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
41
+ end
42
+ end
43
+
44
+ def serialize
45
+ # overwrite to implement
46
+ end
47
+
48
+ def to_s(&block)
49
+ serialize(&block)
50
+ end
51
+ end
52
+
53
+ module Json
54
+ def self.included(base)
55
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
56
+ base.extend ClassMethods
57
+ end
58
+
59
+ module ClassMethods
60
+ def json_class_name
61
+ @json_class_name ||= name.demodulize.underscore.inspect
62
+ end
63
+ end
64
+
65
+ def to_json(options={})
66
+ apply_to_json_defaults(options)
67
+
68
+ if include_root_in_json
69
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
70
+ else
71
+ JsonSerializer.new(self, options).to_s
72
+ end
73
+ end
74
+
75
+ def from_json(json)
76
+ self.attributes = ActiveSupport::JSON.decode(json)
77
+ self
78
+ end
79
+
80
+ class JsonSerializer < Serializer
81
+ def serialize
82
+ serializable_record.to_json
83
+ end
84
+ end
85
+
86
+ private
87
+ def apply_to_json_defaults(options)
88
+ unless options[:only]
89
+ methods = [options.delete(:methods)].flatten.compact
90
+ methods << :id
91
+ options[:methods] = methods.uniq
92
+ end
93
+
94
+ except = [options.delete(:except)].flatten.compact
95
+ except << :_id
96
+ options[:except] = except
97
+ end
98
+ end
99
+
100
+ module InstanceMethods
101
+ def self.included(model)
102
+ model.class_eval do
103
+ include Json
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,48 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Validations
4
+ module InstanceMethods
5
+ def self.included(model)
6
+ model.class_eval { include Validatable }
7
+ end
8
+ end
9
+
10
+ module DocumentMacros
11
+ def validates_uniqueness_of(*args)
12
+ add_validations(args, MongoMapper::Plugins::Validations::ValidatesUniquenessOf)
13
+ end
14
+ end
15
+
16
+ class ValidatesUniquenessOf < Validatable::ValidationBase
17
+ option :scope, :case_sensitive
18
+ default :case_sensitive => true
19
+
20
+ def valid?(instance)
21
+ value = instance[attribute]
22
+ return true if allow_blank && value.blank?
23
+ return true if allow_nil && value.nil?
24
+ base_conditions = case_sensitive ? {self.attribute => value} : {}
25
+ doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
26
+ doc.nil? || instance._id == doc._id
27
+ end
28
+
29
+ def message(instance)
30
+ super || "has already been taken"
31
+ end
32
+
33
+ def scope_conditions(instance)
34
+ return {} unless scope
35
+ Array(scope).inject({}) do |conditions, key|
36
+ conditions.merge(key => instance[key])
37
+ end
38
+ end
39
+
40
+ def where_conditions(instance)
41
+ conditions = {}
42
+ conditions[attribute] = /#{instance[attribute].to_s}/i unless case_sensitive
43
+ conditions
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,213 @@
1
+ class Array
2
+ def self.to_mongo(value)
3
+ value = value.respond_to?(:lines) ? value.lines : value
4
+ value.to_a
5
+ end
6
+
7
+ def self.from_mongo(value)
8
+ value || []
9
+ end
10
+ end
11
+
12
+ class Binary
13
+ def self.to_mongo(value)
14
+ if value.is_a?(ByteBuffer)
15
+ value
16
+ else
17
+ value.nil? ? nil : ByteBuffer.new(value)
18
+ end
19
+ end
20
+
21
+ def self.from_mongo(value)
22
+ value
23
+ end
24
+ end
25
+
26
+ class Boolean
27
+ BOOLEAN_MAPPING = {
28
+ true => true, 'true' => true, 'TRUE' => true, 'True' => true, 't' => true, 'T' => true, '1' => true, 1 => true, 1.0 => true,
29
+ false => false, 'false' => false, 'FALSE' => false, 'False' => false, 'f' => false, 'F' => false, '0' => false, 0 => false, 0.0 => false, nil => false
30
+ }
31
+
32
+ def self.to_mongo(value)
33
+ if value.is_a?(Boolean)
34
+ value
35
+ else
36
+ v = BOOLEAN_MAPPING[value]
37
+ v = value.to_s.downcase == 'true' if v.nil? # Check all mixed case spellings for true
38
+ v
39
+ end
40
+ end
41
+
42
+ def self.from_mongo(value)
43
+ !!value
44
+ end
45
+ end
46
+
47
+ class Date
48
+ def self.to_mongo(value)
49
+ if value.nil? || value == ''
50
+ nil
51
+ else
52
+ date = value.is_a?(Date) || value.is_a?(Time) ? value : Date.parse(value.to_s)
53
+ Time.utc(date.year, date.month, date.day)
54
+ end
55
+ rescue
56
+ nil
57
+ end
58
+
59
+ def self.from_mongo(value)
60
+ value.to_date if value.present?
61
+ end
62
+ end
63
+
64
+ class Float
65
+ def self.to_mongo(value)
66
+ value.to_f
67
+ end
68
+ end
69
+
70
+ class Hash
71
+ def self.from_mongo(value)
72
+ HashWithIndifferentAccess.new(value || {})
73
+ end
74
+
75
+ def to_mongo
76
+ self
77
+ end
78
+ end
79
+
80
+ class Integer
81
+ def self.to_mongo(value)
82
+ value_to_i = value.to_i
83
+ if value_to_i == 0 && value != value_to_i
84
+ value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
85
+ else
86
+ value_to_i
87
+ end
88
+ end
89
+ end
90
+
91
+ class NilClass
92
+ def to_mongo(value)
93
+ value
94
+ end
95
+
96
+ def from_mongo(value)
97
+ value
98
+ end
99
+ end
100
+
101
+ class Object
102
+ # The hidden singleton lurks behind everyone
103
+ def metaclass
104
+ class << self; self end
105
+ end
106
+
107
+ def meta_eval(&blk)
108
+ metaclass.instance_eval(&blk)
109
+ end
110
+
111
+ # Adds methods to a metaclass
112
+ def meta_def(name, &blk)
113
+ meta_eval { define_method(name, &blk) }
114
+ end
115
+
116
+ # Defines an instance method within a class
117
+ def class_def(name, &blk)
118
+ class_eval { define_method(name, &blk) }
119
+ end
120
+
121
+ def self.to_mongo(value)
122
+ value
123
+ end
124
+
125
+ def self.from_mongo(value)
126
+ value
127
+ end
128
+ end
129
+
130
+ class ObjectId
131
+ def self.to_mongo(value)
132
+ if value.blank?
133
+ nil
134
+ elsif value.is_a?(Mongo::ObjectID)
135
+ value
136
+ else
137
+ Mongo::ObjectID.from_string(value.to_s)
138
+ end
139
+ end
140
+
141
+ def self.from_mongo(value)
142
+ value
143
+ end
144
+ end
145
+
146
+ class Set
147
+ def self.to_mongo(value)
148
+ value.to_a
149
+ end
150
+
151
+ def self.from_mongo(value)
152
+ Set.new(value || [])
153
+ end
154
+ end
155
+
156
+ class String
157
+ def self.to_mongo(value)
158
+ value.nil? ? nil : value.to_s
159
+ end
160
+
161
+ def self.from_mongo(value)
162
+ value.nil? ? nil : value.to_s
163
+ end
164
+ end
165
+
166
+ class SymbolOperator
167
+ def initialize(field, operator, options={})
168
+ @field, @operator = field, operator
169
+ end unless method_defined?(:initialize)
170
+
171
+ def to_mm_criteria(value)
172
+ {MongoMapper::FinderOptions.normalized_field(@field) => {"$#{@operator}" => value}}
173
+ end
174
+
175
+ def to_mm_order
176
+ [@field.to_s, MongoMapper::FinderOptions.normalized_order_direction(@operator)]
177
+ end
178
+ end
179
+
180
+ class Symbol
181
+ %w(gt lt gte lte ne in nin mod size where exists asc desc).each do |operator|
182
+ define_method(operator) do
183
+ SymbolOperator.new(self, operator)
184
+ end unless method_defined?(operator)
185
+ end
186
+ end
187
+
188
+ class Time
189
+ def self.to_mongo(value)
190
+ if value.nil? || value == ''
191
+ nil
192
+ else
193
+ time = value.is_a?(Time) ? value : MongoMapper.time_class.parse(value.to_s)
194
+ # Convert time to milliseconds since BSON stores dates with that accurracy, but Ruby uses microseconds
195
+ Time.at((time.to_f * 1000).round / 1000.0).utc if time
196
+ end
197
+ end
198
+
199
+ def self.from_mongo(value)
200
+ if MongoMapper.use_time_zone? && value.present?
201
+ value.in_time_zone(Time.zone)
202
+ else
203
+ value
204
+ end
205
+ end
206
+ end
207
+
208
+ # TODO: Remove when patch accepted into driver
209
+ class Mongo::ObjectID
210
+ def to_json(options = nil)
211
+ %Q("#{to_s}")
212
+ end
213
+ end
@@ -0,0 +1,52 @@
1
+ # The purpose of this is to check finding, initializing,
2
+ # and creating objects (typecasting times/dates and booleans).
3
+
4
+ require 'pp'
5
+ require 'benchmark'
6
+ require 'rubygems'
7
+
8
+ # to test with slow version just do this:
9
+ # gem 'mongo_mapper', '0.6.10'
10
+ # and comment out this:
11
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
12
+
13
+ require 'mongo_mapper'
14
+
15
+ MongoMapper.database = 'testing'
16
+
17
+ class Foo
18
+ include MongoMapper::Document
19
+ key :approved, Boolean
20
+ key :count, Integer
21
+ key :approved_at, Time
22
+ key :expire_on, Date
23
+ timestamps!
24
+ end
25
+ Foo.collection.remove
26
+
27
+ Benchmark.bm(5) do |x|
28
+ ids = []
29
+ x.report("write") do
30
+ 1000.times { |i| ids << Foo.create(:count => 0, :approved => true, :approved_at => Time.now, :expire_on => Date.today).id }
31
+ end
32
+
33
+ x.report("read ") do
34
+ ids.each { |id| Foo.first(:id => id) }
35
+ end
36
+ end
37
+
38
+ # I was get something like this on my puny macbook air:
39
+ # user system total real
40
+ # write 4.810000 0.090000 4.900000 ( 5.039949)
41
+ # read 2.730000 0.070000 2.800000 ( 2.990749)
42
+ #
43
+ #
44
+ # After these commits:
45
+ #
46
+ # * http://github.com/jnunemaker/mongomapper/commit/e5091fa140d5fae2721017b53027092233694ee5
47
+ # * http://github.com/jnunemaker/mongomapper/commit/c22bbde4fa1cfbc310d79cb0e50203310ffb03d1
48
+ #
49
+ # I'm now getting something like this:
50
+ # user system total real
51
+ # write 1.660000 0.050000 1.710000 ( 1.752676)
52
+ # read 1.060000 0.050000 1.110000 ( 1.263429)
@@ -0,0 +1,51 @@
1
+ def growl(title, msg, img)
2
+ %x{growlnotify -m #{ msg.inspect} -t #{title.inspect} --image ~/.watchr/#{img}.png}
3
+ end
4
+
5
+ def form_growl_message(str)
6
+ results = str.split("\n").last
7
+ if results =~ /[1-9]\s(failure|error)s?/
8
+ growl "Test Results", "#{results}", "fail"
9
+ elsif results != ""
10
+ growl "Test Results", "#{results}", "pass"
11
+ end
12
+ end
13
+
14
+ def run(cmd)
15
+ puts(cmd)
16
+ output = ""
17
+ IO.popen(cmd) do |com|
18
+ com.each_char do |c|
19
+ print c
20
+ output << c
21
+ $stdout.flush
22
+ end
23
+ end
24
+ form_growl_message output
25
+ end
26
+
27
+ def run_test_file(file)
28
+ run %Q(ruby -I"lib:test" -rubygems #{file})
29
+ end
30
+
31
+ def run_all_tests
32
+ run "rake test"
33
+ end
34
+
35
+ def related_test_files(path)
36
+ Dir['test/**/*.rb'].select { |file| file =~ /test_#{File.basename(path)}/ }
37
+ end
38
+
39
+ watch('test/test_helper\.rb') { system('clear'); run_all_tests }
40
+ watch('test/.*/test_.*\.rb') { |m| system('clear'); run_test_file(m[0]) }
41
+ watch('lib/.*') { |m| related_test_files(m[0]).each { |file| run_test_file(file) } }
42
+
43
+ # Ctrl-\
44
+ Signal.trap('QUIT') do
45
+ puts " --- Running all tests ---\n\n"
46
+ run_all_tests
47
+ end
48
+
49
+ # Ctrl-C
50
+ Signal.trap('INT') { abort("\n") }
51
+