mongomodel 0.2.20 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Gemfile +1 -1
  2. data/bin/console +2 -0
  3. data/lib/mongomodel.rb +10 -7
  4. data/lib/mongomodel/attributes/mongo.rb +3 -2
  5. data/lib/mongomodel/attributes/typecasting.rb +6 -1
  6. data/lib/mongomodel/concerns/associations.rb +0 -61
  7. data/lib/mongomodel/concerns/associations/base/association.rb +1 -2
  8. data/lib/mongomodel/concerns/associations/belongs_to.rb +8 -3
  9. data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +7 -3
  10. data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +5 -1
  11. data/lib/mongomodel/document.rb +1 -0
  12. data/lib/mongomodel/document/collection_modifiers.rb +83 -0
  13. data/lib/mongomodel/document/optimistic_locking.rb +1 -1
  14. data/lib/mongomodel/document/persistence.rb +4 -5
  15. data/lib/mongomodel/log_subscriber.rb +48 -0
  16. data/lib/mongomodel/railtie.rb +8 -0
  17. data/lib/mongomodel/railties/controller_runtime.rb +33 -0
  18. data/lib/mongomodel/support/collection.rb +1 -1
  19. data/lib/mongomodel/support/configuration.rb +1 -2
  20. data/lib/mongomodel/support/instrumented_collection.rb +122 -0
  21. data/lib/mongomodel/support/mongo_options.rb +18 -4
  22. data/lib/mongomodel/support/reference.rb +48 -6
  23. data/lib/mongomodel/support/scope.rb +8 -3
  24. data/lib/mongomodel/support/scope/finder_methods.rb +3 -2
  25. data/lib/mongomodel/support/scope/load_methods.rb +13 -0
  26. data/lib/mongomodel/support/scope/query_methods.rb +1 -1
  27. data/lib/mongomodel/support/types.rb +13 -10
  28. data/lib/mongomodel/support/types/array.rb +1 -1
  29. data/lib/mongomodel/support/types/hash.rb +1 -1
  30. data/lib/mongomodel/support/types/rational.rb +42 -0
  31. data/lib/mongomodel/tasks/database.rake +54 -2
  32. data/lib/mongomodel/version.rb +1 -1
  33. data/spec/mongomodel/attributes/store_spec.rb +15 -2
  34. data/spec/mongomodel/concerns/logging_spec.rb +1 -1
  35. data/spec/mongomodel/document/collection_modifiers_spec.rb +103 -0
  36. data/spec/mongomodel/document/persistence_spec.rb +3 -3
  37. data/spec/mongomodel/mongomodel_spec.rb +1 -1
  38. data/spec/mongomodel/support/scope_spec.rb +5 -1
  39. data/spec/support/helpers/document_finder_stubs.rb +4 -4
  40. metadata +13 -6
@@ -0,0 +1,33 @@
1
+ require 'active_support/core_ext/module/attr_internal'
2
+
3
+ module MongoModel
4
+ module Railties
5
+ module ControllerRuntime
6
+ extend ActiveSupport::Concern
7
+
8
+ protected
9
+ attr_internal :db_runtime
10
+
11
+ def cleanup_view_runtime
12
+ db_rt_before_render = MongoModel::LogSubscriber.reset_runtime
13
+ runtime = super
14
+ db_rt_after_render = MongoModel::LogSubscriber.reset_runtime
15
+ self.db_runtime = db_rt_before_render + db_rt_after_render
16
+ runtime - db_rt_after_render
17
+ end
18
+
19
+ def append_info_to_payload(payload)
20
+ super
21
+ payload[:db_runtime] = db_runtime
22
+ end
23
+
24
+ module ClassMethods
25
+ def log_process_action(payload)
26
+ messages, db_runtime = super, payload[:db_runtime]
27
+ messages << ("MongoModel: %.1fms" % db_runtime.to_f) if db_runtime
28
+ messages
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -31,7 +31,7 @@ module MongoModel
31
31
  super(convert_for_add(value))
32
32
  end
33
33
 
34
- def build (value)
34
+ def build(value={})
35
35
  value = convert(value)
36
36
  self << value
37
37
  value
@@ -57,8 +57,7 @@ module MongoModel
57
57
  'port' => 27017,
58
58
  'database' => 'mongomodel-default',
59
59
  'pool_size' => 5,
60
- 'timeout' => 5,
61
- 'logger' => MongoModel.logger
60
+ 'timeout' => 5
62
61
  }
63
62
 
64
63
  def self.defaults
@@ -0,0 +1,122 @@
1
+ # MongoModel::InstrumentedCursor & MongoModel::InstrumentedCollection are wrappers
2
+ # around Mongo::Cursor & Mongo::Collection respectively to add in support for
3
+ # ActiveSupport notifications.
4
+ #
5
+ # They are primarily used in MongoModel to implement logging.
6
+ module MongoModel
7
+ class InstrumentedCursor
8
+ attr_reader :cursor
9
+
10
+ def initialize(cursor)
11
+ @cursor = cursor
12
+ end
13
+
14
+ def to_a
15
+ instrument(query_description) do
16
+ cursor.to_a
17
+ end
18
+ end
19
+
20
+ def count
21
+ instrument("count(#{cursor.selector.inspect})") do
22
+ cursor.count
23
+ end
24
+ end
25
+
26
+ private
27
+ def method_missing(method, *args, &block)
28
+ cursor.send(method, *args, &block)
29
+ end
30
+
31
+ def query_description
32
+ "find(#{cursor.selector.inspect}, #{cursor.fields ? cursor.fields.inspect : '{}'})" +
33
+ "#{cursor.skip != 0 ? ('.skip(' + cursor.skip.to_s + ')') : ''}#{cursor.limit != 0 ? ('.limit(' + cursor.limit.to_s + ')') : ''}" +
34
+ "#{cursor.order ? ('.sort(' + cursor.order.inspect + ')') : ''}"
35
+ end
36
+
37
+ def instrument(query, &block)
38
+ ActiveSupport::Notifications.instrument("query.mongomodel", :collection => cursor.collection.name, :query => query, &block)
39
+ end
40
+ end
41
+
42
+ class InstrumentedCollection
43
+ attr_reader :collection
44
+
45
+ def initialize(collection)
46
+ @collection = collection
47
+ end
48
+
49
+ def ==(other)
50
+ case other
51
+ when self.class
52
+ collection == other.collection
53
+ else
54
+ collection == other
55
+ end
56
+ end
57
+
58
+ def find(selector={}, options={})
59
+ cursor = InstrumentedCursor.new(collection.find(selector, options))
60
+
61
+ if block_given?
62
+ yield cursor
63
+ cursor.close
64
+ nil
65
+ else
66
+ cursor
67
+ end
68
+ end
69
+
70
+ def save(doc, options={})
71
+ if doc.has_key?(:_id) || doc.has_key?('_id')
72
+ selector = { '_id' => doc[:_id] || doc['_id'] }
73
+ instrument("update(#{selector.inspect}, #{doc.inspect})") do
74
+ collection.save(doc, options)
75
+ end
76
+ else
77
+ instrument("insert(#{doc})") do
78
+ collection.insert(doc, options)
79
+ end
80
+ end
81
+ end
82
+
83
+ def remove(selector={}, options={})
84
+ instrument("remove(#{selector.inspect})") do
85
+ collection.remove(selector, options)
86
+ end
87
+ end
88
+
89
+ def update(selector, document, options={})
90
+ instrument("update(#{selector.inspect}, #{document.inspect})") do
91
+ collection.update(selector, document, options)
92
+ end
93
+ end
94
+
95
+ def create_index(spec, options={})
96
+ instrument("create_index(#{spec.inspect})") do
97
+ collection.create_index(spec, options)
98
+ end
99
+ end
100
+
101
+ def group(key, condition, initial, reduce, finalize=nil)
102
+ instrument("group(#{key.inspect}, #{condition.inspect})") do
103
+ collection.group(key, condition, initial, reduce, finalize)
104
+ end
105
+ end
106
+
107
+ def distinct(key, query=nil)
108
+ instrument("distinct(#{key.inspect}#{query.present? ? ', ' + query.inspect : ''})") do
109
+ collection.distinct(key, query)
110
+ end
111
+ end
112
+
113
+ private
114
+ def method_missing(method, *args, &block)
115
+ collection.send(method, *args, &block)
116
+ end
117
+
118
+ def instrument(query, &block)
119
+ ActiveSupport::Notifications.instrument("query.mongomodel", :collection => collection.name, :query => query, &block)
120
+ end
121
+ end
122
+ end
@@ -28,15 +28,29 @@ module MongoModel
28
28
  (options[:conditions] || {}).each do |k, v|
29
29
  if k.is_a?(MongoOperator)
30
30
  key = k.field
31
- value = k.to_mongo_selector(v)
32
31
  else
33
32
  key = k
34
- value = v
35
33
  end
36
34
 
37
- property = @model.properties[key]
35
+ if property = @model.properties[key]
36
+ key = property.as
37
+
38
+ if k.is_a?(MongoOperator)
39
+ value = k.to_mongo_selector(v.is_a?(Array) ? v.map { |i| property.to_mongo(property.cast(i)) } : property.to_mongo(property.cast(v)))
40
+ else
41
+ value = property.to_mongo(property.cast(v))
42
+ end
43
+ else
44
+ converter = Types.converter_for(value.class)
45
+
46
+ if k.is_a?(MongoOperator)
47
+ value = k.to_mongo_selector(converter.to_mongo(v))
48
+ else
49
+ value = converter.to_mongo(v)
50
+ end
51
+ end
38
52
 
39
- result[property ? property.as : key] = value
53
+ result[key] = value
40
54
  end
41
55
 
42
56
  result
@@ -1,14 +1,56 @@
1
1
  module MongoModel
2
- class Reference < String
2
+ class Reference
3
+ attr_reader :id
4
+
5
+ def initialize(id)
6
+ @id = id
7
+ end
8
+
9
+ def to_s
10
+ id.to_s
11
+ end
12
+
13
+ def hash
14
+ id.hash
15
+ end
16
+
17
+ def as_json(*)
18
+ to_s
19
+ end
20
+
21
+ def blank?
22
+ id.blank?
23
+ end
24
+
25
+ def eql?(other)
26
+ case other
27
+ when Reference
28
+ id.to_s == other.id.to_s
29
+ else
30
+ id.to_s == other.to_s
31
+ end
32
+ end
33
+ alias_method :==, :eql?
34
+
35
+ def to_mongo
36
+ id.blank? ? nil : id
37
+ end
38
+
3
39
  def self.cast(value)
4
- value = value.to_s if value.respond_to?(:to_s)
5
-
6
40
  case value
7
- when "", nil
8
- nil
9
- else
41
+ when BSON::ObjectId
10
42
  new(value)
43
+ else
44
+ if BSON::ObjectId.legal?(value.to_s)
45
+ new(BSON::ObjectId(value.to_s))
46
+ else
47
+ new(value.to_s)
48
+ end
11
49
  end
12
50
  end
51
+
52
+ def self.from_mongo(value)
53
+ cast(value)
54
+ end
13
55
  end
14
56
  end
@@ -8,11 +8,12 @@ module MongoModel
8
8
  autoload :SpawnMethods, 'mongomodel/support/scope/spawn_methods'
9
9
  autoload :QueryMethods, 'mongomodel/support/scope/query_methods'
10
10
  autoload :FinderMethods, 'mongomodel/support/scope/finder_methods'
11
+ autoload :LoadMethods, 'mongomodel/support/scope/load_methods'
11
12
  autoload :DynamicFinders, 'mongomodel/support/scope/dynamic_finders'
12
13
  autoload :Pagination, 'mongomodel/support/scope/pagination'
13
14
  autoload :Batches, 'mongomodel/support/scope/batches'
14
15
 
15
- include Batches, Pagination, DynamicFinders, FinderMethods, QueryMethods, SpawnMethods
16
+ include Batches, Pagination, DynamicFinders, LoadMethods, FinderMethods, QueryMethods, SpawnMethods
16
17
 
17
18
  delegate :inspect, :as_json, :to => :to_a
18
19
 
@@ -76,7 +77,7 @@ module MongoModel
76
77
 
77
78
  def delete_all
78
79
  selector = MongoOptions.new(klass, :conditions => finder_conditions).selector
79
- collection.remove(selector)
80
+ collection.remove(selector, {})
80
81
  reset
81
82
  end
82
83
 
@@ -179,7 +180,11 @@ module MongoModel
179
180
  end
180
181
 
181
182
  def _find_and_instantiate
182
- _find.to_a.map { |doc| klass.from_mongo(doc) }
183
+ _find.to_a.map { |doc|
184
+ klass.from_mongo(doc).tap { |instance|
185
+ on_load_proc.call(instance) if on_load_proc
186
+ }
187
+ }
183
188
  end
184
189
 
185
190
  def finder_conditions
@@ -11,10 +11,11 @@ module MongoModel
11
11
  when 0
12
12
  raise ArgumentError, "At least one id must be specified"
13
13
  when 1
14
- id = ids.first.to_s
14
+ id = ids.first
15
15
  where(:id => id).first || raise(DocumentNotFound, "Couldn't find document with id: #{id}")
16
16
  else
17
- docs = where(:id.in => ids.map { |id| id.to_s }).to_a
17
+ ids = ids.map { |id| Reference.cast(id) }
18
+ docs = where(:id.in => ids).to_a
18
19
  raise DocumentNotFound if docs.size != ids.size
19
20
  docs.sort_by { |doc| ids.index(doc.id) }
20
21
  end
@@ -0,0 +1,13 @@
1
+ module MongoModel
2
+ class Scope
3
+ module LoadMethods
4
+ attr_accessor :on_load_proc
5
+
6
+ def on_load(&block)
7
+ new_scope = clone
8
+ new_scope.on_load_proc = block
9
+ new_scope
10
+ end
11
+ end
12
+ end
13
+ end
@@ -33,7 +33,7 @@ module MongoModel
33
33
 
34
34
  def from(value, &block)
35
35
  new_scope = clone
36
- new_scope.from_value = value.is_a?(String) ? klass.database.collection(value) : value
36
+ new_scope.from_value = InstrumentedCollection.new(value.is_a?(String) ? klass.database.collection(value) : value)
37
37
  new_scope
38
38
  end
39
39
 
@@ -1,3 +1,4 @@
1
+ require 'rational' unless RUBY_VERSION >= '1.9.2'
1
2
  require 'set'
2
3
 
3
4
  require 'mongomodel/support/types/object'
@@ -12,20 +13,22 @@ require 'mongomodel/support/types/custom'
12
13
  require 'mongomodel/support/types/array'
13
14
  require 'mongomodel/support/types/set'
14
15
  require 'mongomodel/support/types/hash'
16
+ require 'mongomodel/support/types/rational'
15
17
 
16
18
  module MongoModel
17
19
  module Types
18
20
  CONVERTERS = {
19
- ::String => Types::String.new,
20
- ::Integer => Types::Integer.new,
21
- ::Float => Types::Float.new,
22
- ::Boolean => Types::Boolean.new,
23
- ::Symbol => Types::Symbol.new,
24
- ::Date => Types::Date.new,
25
- ::Time => Types::Time.new,
26
- ::Array => Types::Array.new,
27
- ::Set => Types::Set.new,
28
- ::Hash => Types::Hash.new
21
+ ::String => Types::String.new,
22
+ ::Integer => Types::Integer.new,
23
+ ::Float => Types::Float.new,
24
+ ::Boolean => Types::Boolean.new,
25
+ ::Symbol => Types::Symbol.new,
26
+ ::Date => Types::Date.new,
27
+ ::Time => Types::Time.new,
28
+ ::Array => Types::Array.new,
29
+ ::Set => Types::Set.new,
30
+ ::Hash => Types::Hash.new,
31
+ ::Rational => Types::Rational.new
29
32
  }
30
33
 
31
34
  def self.converter_for(type)
@@ -4,7 +4,7 @@ module MongoModel
4
4
  def to_mongo(array)
5
5
  array.map { |i|
6
6
  Types.converter_for(i.class).to_mongo(i)
7
- }
7
+ } if array
8
8
  end
9
9
  end
10
10
  end
@@ -7,7 +7,7 @@ module MongoModel
7
7
  hash.inject({}) { |result, (k, v)|
8
8
  result[k] = Types.converter_for(v.class).to_mongo(v)
9
9
  result
10
- }
10
+ } if hash
11
11
  end
12
12
 
13
13
  def from_mongo(hash)
@@ -0,0 +1,42 @@
1
+ module MongoModel
2
+ module Types
3
+ class Rational < Object
4
+ if RUBY_VERSION >= '1.9.2'
5
+ def cast(value)
6
+ Rational(value)
7
+ end
8
+ else
9
+ def cast(value)
10
+ case value
11
+ when ::Rational
12
+ value
13
+ when ::String
14
+ rational_from_string(value)
15
+ else
16
+ Rational(value)
17
+ end
18
+ end
19
+ end
20
+
21
+ def from_mongo(value)
22
+ rational_from_string(value)
23
+ end
24
+
25
+ def to_mongo(value)
26
+ value.to_s
27
+ end
28
+
29
+ private
30
+ if RUBY_VERSION >= '1.9.2'
31
+ def rational_from_string(str)
32
+ Rational(str)
33
+ end
34
+ else
35
+ def rational_from_string(str)
36
+ numerator, denominator = str.split("/", 2)
37
+ Rational(numerator.to_i, (denominator || 1).to_i)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end