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
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|