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
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
- gem "bson_ext", '~> 1.1.5'
4
+ gem "bson_ext", '~> 1.2.0'
@@ -12,6 +12,8 @@ require 'irb/completion'
12
12
 
13
13
  require 'mongomodel'
14
14
 
15
+ MongoModel.logger = ActiveSupport::BufferedLogger.new(STDERR)
16
+
15
17
  IRB.setup(nil)
16
18
 
17
19
  IRB.conf[:USE_READLINE] = true
@@ -5,6 +5,7 @@ require 'mongo'
5
5
 
6
6
  require 'mongomodel/support/core_extensions'
7
7
  require 'mongomodel/support/exceptions'
8
+ require 'mongomodel/log_subscriber'
8
9
 
9
10
  require 'active_support/core_ext/module/attribute_accessors'
10
11
 
@@ -38,6 +39,7 @@ module MongoModel
38
39
  autoload :Types, 'mongomodel/support/types'
39
40
  autoload :Configuration, 'mongomodel/support/configuration'
40
41
  autoload :DynamicFinder, 'mongomodel/support/dynamic_finder'
42
+ autoload :InstrumentedCollection, 'mongomodel/support/instrumented_collection'
41
43
 
42
44
  autoload :Collection, 'mongomodel/support/collection'
43
45
  autoload :Map, 'mongomodel/support/map'
@@ -72,13 +74,14 @@ module MongoModel
72
74
  end
73
75
 
74
76
  module DocumentExtensions
75
- autoload :Persistence, 'mongomodel/document/persistence'
76
- autoload :OptimisticLocking, 'mongomodel/document/optimistic_locking'
77
- autoload :DynamicFinders, 'mongomodel/document/dynamic_finders'
78
- autoload :Indexes, 'mongomodel/document/indexes'
79
- autoload :Scopes, 'mongomodel/document/scopes'
80
- autoload :Validations, 'mongomodel/document/validations'
81
- autoload :Callbacks, 'mongomodel/document/callbacks'
77
+ autoload :Persistence, 'mongomodel/document/persistence'
78
+ autoload :OptimisticLocking, 'mongomodel/document/optimistic_locking'
79
+ autoload :DynamicFinders, 'mongomodel/document/dynamic_finders'
80
+ autoload :Indexes, 'mongomodel/document/indexes'
81
+ autoload :Scopes, 'mongomodel/document/scopes'
82
+ autoload :Validations, 'mongomodel/document/validations'
83
+ autoload :Callbacks, 'mongomodel/document/callbacks'
84
+ autoload :CollectionModifiers, 'mongomodel/document/collection_modifiers'
82
85
  end
83
86
 
84
87
  mattr_accessor :logger
@@ -26,10 +26,11 @@ module MongoModel
26
26
 
27
27
  if property
28
28
  child = store(property.name, property.from_mongo(v))
29
- child.parent_document = instance if child.respond_to?(:parent_document=)
30
29
  else
31
- self[k.to_sym] = v
30
+ child = store(k.to_sym, v)
32
31
  end
32
+
33
+ child.parent_document = instance if child.respond_to?(:parent_document=)
33
34
  end
34
35
  end
35
36
 
@@ -27,10 +27,15 @@ module MongoModel
27
27
  end
28
28
 
29
29
  def before_type_cast(key)
30
- values_before_typecast[key].presence || self[key]
30
+ values_before_typecast[key]
31
31
  end
32
32
 
33
33
  private
34
+ def store(key, value)
35
+ values_before_typecast[key] = value
36
+ super(key, value)
37
+ end
38
+
34
39
  def typecast(key, value)
35
40
  unless value.nil?
36
41
  property = properties[key]
@@ -40,64 +40,3 @@ module MongoModel
40
40
  end
41
41
  end
42
42
  end
43
-
44
-
45
-
46
- #
47
- # belongs_to :user
48
- # => property :user_id
49
- # belongs_to :author, :class_name => 'User'
50
- # => property :author_id
51
- # belongs_to :commentable, :polymorphic => true
52
- # => property :commentable_id
53
- # => property :commentable_type
54
- #
55
-
56
- #
57
- # has_many :contributors, :class_name => 'User', :foreign_key => :publication_id
58
- # has_many :comments, :as => :commentable
59
- # has_many :roles
60
- #
61
-
62
-
63
-
64
-
65
- #
66
- # Documents and EmbeddedDocuments can:
67
- # - belong to any Document
68
- # - have many Documents by ids
69
- #
70
- # Documents can also:
71
- # - have many Documents by foreign key
72
- #
73
-
74
- # class Author < Document; end
75
- #
76
- # class Comment < Document
77
- # # Creates properties :commentable_id and :commentable_type
78
- # belongs_to :commentable, :polymorphic => true
79
- # end
80
- #
81
- # class Page < EmbeddedDocument
82
- # # Creates property :editor_id
83
- # belongs_to :editor, :class => 'Author'
84
- #
85
- # # Creates property :related_article_ids
86
- # has_many :related_articles, :class => 'Article'
87
- #
88
- # # Polymorphic association, uses commentable_id/commentable_type on Comment class
89
- # has_many :comments, :as => :commentable
90
- # end
91
- #
92
- # class Article < Document
93
- # # Creates property :author_id
94
- # belongs_to :author
95
- #
96
- # # Creates property :recommended_by_ids
97
- # has_many :recommended_by, :by => :ids, :class => 'Author'
98
- #
99
- # # Creates property :parent_article_id
100
- # belongs_to :parent_article, :class => 'Article'
101
- # # Uses parent_article_id property on referenced class
102
- # has_many :child_articles, :by => :foreign_key, :foreign_key => :parent_article_id, :class => 'Article'
103
- # end
@@ -24,8 +24,7 @@ module MongoModel
24
24
  def ensure_class(value)
25
25
  raise AssociationTypeMismatch, "expected instance of #{klass} but got #{value.class}" unless value.is_a?(klass)
26
26
  end
27
-
28
- private
27
+
29
28
  def proxy_class
30
29
  self.class.parent::Proxy rescue Base::Proxy
31
30
  end
@@ -2,16 +2,16 @@ module MongoModel
2
2
  module Associations
3
3
  class BelongsTo < Base::Definition
4
4
  def foreign_key
5
- :"#{name}_id"
5
+ @foreign_key ||= :"#{name}_id"
6
6
  end
7
7
 
8
8
  def type_key
9
- :"#{name}_type"
9
+ @type_key ||= :"#{name}_type"
10
10
  end
11
11
 
12
12
  properties do |association|
13
13
  property association.foreign_key, MongoModel::Reference, :internal => true
14
- property association.type_key, MongoModel::Reference, :internal => true if association.polymorphic?
14
+ property association.type_key, String, :internal => true if association.polymorphic?
15
15
  end
16
16
 
17
17
  methods do |association|
@@ -64,6 +64,11 @@ module MongoModel
64
64
  def find_target
65
65
  target_class.find(target_id) if target_id && target_class
66
66
  end
67
+
68
+ protected
69
+ def proxy_class
70
+ Base::Proxy
71
+ end
67
72
  end
68
73
  end
69
74
  end
@@ -2,11 +2,11 @@ module MongoModel
2
2
  module Associations
3
3
  class HasManyByForeignKey < Base::Definition
4
4
  def foreign_key
5
- options[:foreign_key] || :"#{inverse_of}_id"
5
+ @foreign_key ||= options[:foreign_key] || :"#{inverse_of}_id"
6
6
  end
7
7
 
8
8
  def inverse_of
9
- options[:inverse_of] || owner.to_s.demodulize.underscore.singularize.to_sym
9
+ @inverse_of ||= options[:inverse_of] || owner.to_s.demodulize.underscore.singularize.to_sym
10
10
  end
11
11
 
12
12
  def define!
@@ -88,7 +88,7 @@ module MongoModel
88
88
  end
89
89
 
90
90
  def scoped
91
- definition.scope.where(foreign_key => instance.id)
91
+ definition.scope.where(foreign_key => instance.id).on_load { |doc| set_inverse_association(doc) }
92
92
  end
93
93
 
94
94
  protected
@@ -107,6 +107,10 @@ module MongoModel
107
107
  def ensure_class(array)
108
108
  array.is_a?(Array) ? array.each { |i| super(i) } : super
109
109
  end
110
+
111
+ def proxy_class
112
+ Proxy
113
+ end
110
114
  end
111
115
 
112
116
  class Proxy < Base::Proxy
@@ -2,7 +2,7 @@ module MongoModel
2
2
  module Associations
3
3
  class HasManyByIds < Base::Definition
4
4
  def property_name
5
- :"#{singular_name}_ids"
5
+ @property_name ||= :"#{singular_name}_ids"
6
6
  end
7
7
 
8
8
  def define!
@@ -78,6 +78,10 @@ module MongoModel
78
78
  def ensure_class(array)
79
79
  array.is_a?(Array) ? array.each { |i| super(i) } : super
80
80
  end
81
+
82
+ def proxy_class
83
+ Proxy
84
+ end
81
85
  end
82
86
 
83
87
  class Proxy < Base::Proxy
@@ -7,6 +7,7 @@ module MongoModel
7
7
  class Document < EmbeddedDocument
8
8
  include DocumentExtensions::Persistence
9
9
  include DocumentExtensions::OptimisticLocking
10
+ include DocumentExtensions::CollectionModifiers
10
11
 
11
12
  extend DocumentExtensions::DynamicFinders
12
13
  include DocumentExtensions::Indexes
@@ -0,0 +1,83 @@
1
+ module MongoModel
2
+ module DocumentExtensions
3
+ module CollectionModifiers
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Post.increase!(:hits => 1, :available => -1)
8
+ def increase!(args)
9
+ collection_modifier_update('$inc', args)
10
+ end
11
+
12
+ # Post.set!(:hits => 0, :available => 100)
13
+ def set!(args)
14
+ collection_modifier_update('$set', args)
15
+ end
16
+
17
+ # Post.unset!(:hits, :available)
18
+ def unset!(*args)
19
+ values = args.each_with_object({}) { |key, hash| hash[key.to_s] = 1 }
20
+ collection_modifier_update('$unset', values)
21
+ end
22
+
23
+ # Post.push!(:tags => 'xxx')
24
+ def push!(args)
25
+ collection_modifier_update('$push', args)
26
+ end
27
+
28
+ # Post.push_all!(:tags => ['xxx', 'yyy', 'zzz'])
29
+ def push_all!(args)
30
+ collection_modifier_update('$pushAll', args)
31
+ end
32
+
33
+ # Post.add_to_set!(:tags => 'xxx')
34
+ def add_to_set!(args)
35
+ collection_modifier_update('$addToSet', args)
36
+ end
37
+
38
+ # Post.pull!(:tags => 'xxx')
39
+ def pull!(args)
40
+ collection_modifier_update('$pull', args)
41
+ end
42
+
43
+ # Post.pull_all!(:tags => ['xxx', 'yyy', 'zzz'])
44
+ def pull_all!(args)
45
+ collection_modifier_update('$pullAll', args)
46
+ end
47
+
48
+ # Post.pop!(:tags)
49
+ def pop!(*args)
50
+ values = args.each_with_object({}) { |key, hash| hash[key.to_s] = 1 }
51
+ collection_modifier_update('$pop', values)
52
+ end
53
+
54
+ # Post.shift!(:tags, :data)
55
+ def shift!(*args)
56
+ values = args.each_with_object({}) { |key, hash| hash[key.to_s] = -1 }
57
+ collection_modifier_update('$pop', values)
58
+ end
59
+
60
+ # requires mongodb 1.7.2
61
+ # Post.rename!(:tags => :tag_collection)
62
+ def rename!(args)
63
+ collection_modifier_update('$rename', args)
64
+ end
65
+
66
+ private
67
+ def collection_modifier_update(modifier, args)
68
+ selector = MongoModel::MongoOptions.new(self, scoped.finder_options).selector
69
+ collection.update(selector, { modifier => args.stringify_keys! }, :multi => true)
70
+ end
71
+ end
72
+
73
+ module InstanceMethods
74
+ delegate :increase!, :set!, :unset!, :push!, :push_all!, :add_to_set!, :pull!, :pull_all!, :pop!, :shift!, :rename!, :to => :instance_scope
75
+
76
+ private
77
+ def instance_scope
78
+ self.class.where(:id => id)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -30,7 +30,7 @@ module MongoModel
30
30
  def save_to_collection
31
31
  if locking_enabled? && _lock_version > 1
32
32
  begin
33
- collection.update({ '_id' => id, '_lock_version' => _lock_version-1 }, to_mongo)
33
+ collection.update({ '_id' => id.to_mongo, '_lock_version' => _lock_version-1 }, to_mongo)
34
34
  success = database.get_last_error['updatedExisting']
35
35
 
36
36
  self._lock_version -= 1 unless success
@@ -85,9 +85,9 @@ module MongoModel
85
85
  end
86
86
  end
87
87
 
88
- def from_mongo(document)
88
+ def from_mongo(hash)
89
89
  instance = super
90
- instance.send(:instantiate, document) if instance
90
+ instance.send(:instantiate) if instance
91
91
  instance
92
92
  end
93
93
 
@@ -108,7 +108,7 @@ module MongoModel
108
108
  end
109
109
 
110
110
  def collection
111
- @_collection ||= database.collection(collection_name)
111
+ @_collection ||= InstrumentedCollection.new(database.collection(collection_name))
112
112
  end
113
113
 
114
114
  def database
@@ -146,8 +146,7 @@ module MongoModel
146
146
  false
147
147
  end
148
148
 
149
- def instantiate(document)
150
- attributes.load!(document)
149
+ def instantiate
151
150
  set_new_record(false)
152
151
  end
153
152
  end
@@ -0,0 +1,48 @@
1
+ module MongoModel
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def self.runtime=(value)
4
+ Thread.current["mongomodel_runtime"] = value
5
+ end
6
+
7
+ def self.runtime
8
+ Thread.current["mongomodel_runtime"] ||= 0
9
+ end
10
+
11
+ def self.reset_runtime
12
+ rt, self.runtime = runtime, 0
13
+ rt
14
+ end
15
+
16
+ def initialize
17
+ super
18
+ @odd_or_even = false
19
+ end
20
+
21
+ def query(event)
22
+ self.class.runtime += event.duration
23
+ return unless logger.debug?
24
+
25
+ collection = '%s (%.1fms)' % [event.payload[:collection], event.duration]
26
+ query = event.payload[:query]
27
+
28
+ if odd?
29
+ collection = color(collection, CYAN, true)
30
+ query = color(query, nil, true)
31
+ else
32
+ collection = color(collection, MAGENTA, true)
33
+ end
34
+
35
+ debug " #{collection} #{query}"
36
+ end
37
+
38
+ def odd?
39
+ @odd_or_even = !@odd_or_even
40
+ end
41
+
42
+ def logger
43
+ MongoModel.logger
44
+ end
45
+ end
46
+ end
47
+
48
+ MongoModel::LogSubscriber.attach_to :mongomodel
@@ -26,6 +26,14 @@ module MongoModel
26
26
  end
27
27
  end
28
28
 
29
+ # Expose database runtime to controller for logging.
30
+ initializer "mongomodel.log_runtime" do |app|
31
+ require "mongomodel/railties/controller_runtime"
32
+ ActiveSupport.on_load(:action_controller) do
33
+ include MongoModel::Railties::ControllerRuntime
34
+ end
35
+ end
36
+
29
37
  initializer "mongomodel.passenger_forking" do |app|
30
38
  if defined?(PhusionPassenger)
31
39
  PhusionPassenger.on_event(:starting_worker_process) do |forked|