mongomodel 0.2.20 → 0.3.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 (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