mongoid 1.1.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/HISTORY +69 -1
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongoid.rb +39 -13
  5. data/lib/mongoid/associations.rb +1 -0
  6. data/lib/mongoid/associations/has_many.rb +19 -3
  7. data/lib/mongoid/attributes.rb +6 -1
  8. data/lib/mongoid/collection.rb +106 -0
  9. data/lib/mongoid/collections/cyclic_iterator.rb +34 -0
  10. data/lib/mongoid/collections/master.rb +28 -0
  11. data/lib/mongoid/collections/mimic.rb +46 -0
  12. data/lib/mongoid/collections/operations.rb +39 -0
  13. data/lib/mongoid/collections/slaves.rb +44 -0
  14. data/lib/mongoid/commands.rb +1 -0
  15. data/lib/mongoid/config.rb +61 -9
  16. data/lib/mongoid/contexts/enumerable.rb +24 -15
  17. data/lib/mongoid/contexts/mongo.rb +25 -31
  18. data/lib/mongoid/contexts/paging.rb +2 -2
  19. data/lib/mongoid/criteria.rb +48 -7
  20. data/lib/mongoid/criterion/exclusion.rb +2 -0
  21. data/lib/mongoid/criterion/optional.rb +2 -2
  22. data/lib/mongoid/cursor.rb +82 -0
  23. data/lib/mongoid/document.rb +12 -13
  24. data/lib/mongoid/errors.rb +35 -4
  25. data/lib/mongoid/extensions.rb +1 -0
  26. data/lib/mongoid/extensions/array/aliasing.rb +4 -0
  27. data/lib/mongoid/extensions/string/inflections.rb +44 -1
  28. data/lib/mongoid/factory.rb +17 -0
  29. data/lib/mongoid/finders.rb +7 -2
  30. data/lib/mongoid/matchers/default.rb +6 -0
  31. data/lib/mongoid/matchers/gt.rb +1 -1
  32. data/lib/mongoid/matchers/gte.rb +1 -1
  33. data/lib/mongoid/matchers/lt.rb +1 -1
  34. data/lib/mongoid/matchers/lte.rb +1 -1
  35. data/mongoid.gemspec +30 -5
  36. data/perf/benchmark.rb +5 -3
  37. data/spec/integration/mongoid/associations_spec.rb +12 -0
  38. data/spec/integration/mongoid/contexts/enumerable_spec.rb +20 -0
  39. data/spec/integration/mongoid/criteria_spec.rb +28 -0
  40. data/spec/integration/mongoid/document_spec.rb +1 -1
  41. data/spec/integration/mongoid/inheritance_spec.rb +2 -2
  42. data/spec/models/person.rb +1 -1
  43. data/spec/spec_helper.rb +9 -4
  44. data/spec/unit/mongoid/associations/has_many_spec.rb +19 -0
  45. data/spec/unit/mongoid/associations_spec.rb +9 -0
  46. data/spec/unit/mongoid/attributes_spec.rb +4 -4
  47. data/spec/unit/mongoid/collection_spec.rb +113 -0
  48. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +75 -0
  49. data/spec/unit/mongoid/collections/master_spec.rb +41 -0
  50. data/spec/unit/mongoid/collections/mimic_spec.rb +43 -0
  51. data/spec/unit/mongoid/collections/slaves_spec.rb +81 -0
  52. data/spec/unit/mongoid/commands_spec.rb +7 -0
  53. data/spec/unit/mongoid/config_spec.rb +52 -1
  54. data/spec/unit/mongoid/contexts/enumerable_spec.rb +38 -12
  55. data/spec/unit/mongoid/contexts/mongo_spec.rb +38 -31
  56. data/spec/unit/mongoid/criteria_spec.rb +20 -12
  57. data/spec/unit/mongoid/criterion/exclusion_spec.rb +26 -0
  58. data/spec/unit/mongoid/criterion/optional_spec.rb +13 -0
  59. data/spec/unit/mongoid/cursor_spec.rb +74 -0
  60. data/spec/unit/mongoid/document_spec.rb +4 -5
  61. data/spec/unit/mongoid/errors_spec.rb +5 -9
  62. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +21 -2
  63. data/spec/unit/mongoid/factory_spec.rb +16 -0
  64. data/spec/unit/mongoid_spec.rb +4 -4
  65. metadata +28 -3
data/HISTORY CHANGED
@@ -1,4 +1,72 @@
1
- === 1.0.7
1
+ === 1.2.0
2
+ - Fixed composite key generation not to replace all
3
+ special chars with dashes.
4
+
5
+ - Memory optimizations, now wrapping the mongo cursor.
6
+
7
+ - Fixed memoization on has_many_related assocations.
8
+
9
+ - Fixed pagination on embedded associations
10
+
11
+ - Fixed after_update callback not getting fired.
12
+
13
+ - When a connection failure occurs, Mongoid tried to
14
+ retry the operation up to a configurable time.
15
+
16
+ - Mongoid now supports a configuration with a master
17
+ and multiple read slaves. It will direct all writes
18
+ to the master and all reads to the slaves. In the case
19
+ of a write, subsequent reads will be directed to the
20
+ master up to configurable number to try and counter
21
+ the 2 second slave sync delay.
22
+
23
+ - Fixed issue with criteria exclusion queries with ids.
24
+
25
+ - Mongoid only executes a count when explicitly paginating.
26
+
27
+ - Fixed Criteria offset to be a getter or setter
28
+
29
+ - Added indexes to foreign keys on belongs_to_related
30
+ associations.
31
+
32
+ - General code refactorings/cleanup
33
+
34
+ === 1.1.4
35
+ - Refactorings in preparation for next feature push.
36
+
37
+ - Associations may now have anonymous extensions.
38
+
39
+ - Ruby 1.9 compatibility updates. (brainopia)
40
+
41
+ - Document.to_json accepts options. (jsmestad)
42
+
43
+ === 1.1.3
44
+ - Nil can be passed into methods that set attributes.
45
+
46
+ - Mongoid.config can now take a block.
47
+
48
+ - Allow named_scopes and criteria class methods on
49
+ has_many_related if related is a Mongoid document.
50
+
51
+ - Document.find can now take an array of ids.
52
+
53
+ === 1.1.2
54
+ - Fixing issues with updates to parents.
55
+
56
+ === 1.1.1
57
+ - Document.create raises validations error, not no
58
+ method error on self.errors (Rick Frankel)
59
+
60
+ - Support default values that respond_to?(:call)
61
+ (procs/lambda) (ahoward)
62
+
63
+ - Added Mongoid.persist_in_safe_mode global config
64
+ option.
65
+
66
+ - Minor optimization: don't evaluate default procs
67
+ if it's not needed (brainopia)
68
+
69
+ === 1.1.0
2
70
  - Nil attributes no longer persist nil values to the
3
71
  database - the field will just not exist.
4
72
 
data/Rakefile CHANGED
@@ -15,7 +15,7 @@ begin
15
15
  gem.add_dependency("activesupport", "<= 2.3.5")
16
16
  gem.add_dependency("mongo", ">= 0.18.2")
17
17
  gem.add_dependency("durran-validatable", ">= 2.0.1")
18
- gem.add_dependency("leshill-will_paginate", ">= 2.3.11")
18
+ gem.add_dependency("will_paginate", ">= 2.3.11")
19
19
 
20
20
  gem.add_development_dependency("rspec", ">= 1.2.9")
21
21
  gem.add_development_dependency("mocha", ">= 0.9.8")
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.4
1
+ 1.2.0
@@ -24,7 +24,7 @@ require "rubygems"
24
24
  gem "activesupport", ">= 2.2.2", "<3.0.pre"
25
25
  gem "mongo", ">= 0.18.2"
26
26
  gem "durran-validatable", ">= 2.0.1"
27
- gem "leshill-will_paginate", ">= 2.3.11"
27
+ gem "will_paginate", ">= 2.3.11"
28
28
 
29
29
  require "delegate"
30
30
  require "observer"
@@ -41,12 +41,15 @@ require "mongoid/associations"
41
41
  require "mongoid/associations/options"
42
42
  require "mongoid/attributes"
43
43
  require "mongoid/callbacks"
44
+ require "mongoid/collection"
44
45
  require "mongoid/commands"
45
46
  require "mongoid/config"
46
47
  require "mongoid/contexts"
47
48
  require "mongoid/criteria"
49
+ require "mongoid/cursor"
48
50
  require "mongoid/extensions"
49
51
  require "mongoid/errors"
52
+ require "mongoid/factory"
50
53
  require "mongoid/field"
51
54
  require "mongoid/fields"
52
55
  require "mongoid/finders"
@@ -65,20 +68,43 @@ module Mongoid #:nodoc
65
68
 
66
69
  class << self
67
70
 
68
- delegate \
69
- :allow_dynamic_fields,
70
- :allow_dynamic_fields=,
71
- :database,
72
- :database=,
73
- :persist_in_safe_mode,
74
- :persist_in_safe_mode=,
75
- :raise_not_found_error,
76
- :raise_not_found_error=, :to => :config
77
-
78
- def config
79
- yield Config.instance if block_given?; Config.instance
71
+ # Sets the Mongoid configuration options. Best used by passing a block.
72
+ #
73
+ # Example:
74
+ #
75
+ # Mongoid.configure do |config|
76
+ # name = "mongoid_test"
77
+ # host = "localhost"
78
+ # config.allow_dynamic_fields = false
79
+ # config.master = Mongo::Connection.new.db(name)
80
+ # config.slaves = [
81
+ # Mongo::Connection.new(host, 27018, :slave_ok => true).db(name),
82
+ # Mongo::Connection.new(host, 27019, :slave_ok => true).db(name)
83
+ # ]
84
+ # end
85
+ #
86
+ # Returns:
87
+ #
88
+ # The Mongoid +Config+ singleton instance.
89
+ def configure
90
+ config = Config.instance
91
+ block_given? ? yield(config) : config
80
92
  end
81
93
 
94
+ alias :config :configure
82
95
  end
83
96
 
97
+ # Take all the public instance methods from the Config singleton and allow
98
+ # them to be accessed through the Mongoid module directly.
99
+ #
100
+ # Example:
101
+ #
102
+ # <tt>Mongoid.database = Mongo::Connection.new.db("test")</tt>
103
+ Config.public_instance_methods(false).each do |name|
104
+ (class << self; self; end).class_eval <<-EOT
105
+ def #{name}(*args)
106
+ configure.send("#{name}", *args)
107
+ end
108
+ EOT
109
+ end
84
110
  end
@@ -88,6 +88,7 @@ module Mongoid # :nodoc:
88
88
  #
89
89
  def belongs_to_related(name, options = {}, &block)
90
90
  field "#{name.to_s}_id"
91
+ index "#{name.to_s}_id" unless self.embedded
91
92
  add_association(
92
93
  Associations::BelongsToRelated,
93
94
  Associations::Options.new(
@@ -97,9 +97,9 @@ module Mongoid #:nodoc:
97
97
  # If the method exists on the array, use the default proxy behavior.
98
98
  def method_missing(name, *args, &block)
99
99
  unless @target.respond_to?(name)
100
- criteria = @klass.send(name, *args)
101
- criteria.documents = @target
102
- return criteria
100
+ object = @klass.send(name, *args)
101
+ object.documents = @target
102
+ return object
103
103
  end
104
104
  super
105
105
  end
@@ -120,6 +120,22 @@ module Mongoid #:nodoc:
120
120
  end
121
121
  end
122
122
 
123
+ # Paginate the association. Will create a new criteria, set the documents
124
+ # on it and execute in an enumerable context.
125
+ #
126
+ # Options:
127
+ #
128
+ # options: A +Hash+ of pagination options.
129
+ #
130
+ # Returns:
131
+ #
132
+ # A +WillPaginate::Collection+.
133
+ def paginate(options)
134
+ criteria = Mongoid::Criteria.translate(@klass, options)
135
+ criteria.documents = @target
136
+ criteria.paginate
137
+ end
138
+
123
139
  protected
124
140
  # Initializes each of the attributes in the hash.
125
141
  def initialize_each(attributes)
@@ -40,7 +40,7 @@ module Mongoid #:nodoc:
40
40
  # put into the document's attributes.
41
41
  def process(attrs = nil)
42
42
  (attrs || {}).each_pair do |key, value|
43
- if Mongoid.allow_dynamic_fields && !respond_to?("#{key}=")
43
+ if set_allowed?(key)
44
44
  @attributes[key.to_s] = value
45
45
  else
46
46
  send("#{key}=", value) if value
@@ -131,6 +131,11 @@ module Mongoid #:nodoc:
131
131
  end
132
132
 
133
133
  protected
134
+ # Return true is dynamic field setting is enabled.
135
+ def set_allowed?(key)
136
+ Mongoid.allow_dynamic_fields && !respond_to?("#{key}=")
137
+ end
138
+
134
139
  # Used when supplying a :reject_if block as an option to
135
140
  # accepts_nested_attributes_for
136
141
  def reject(attributes, options)
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+ require "mongoid/collections/operations"
3
+ require "mongoid/collections/cyclic_iterator"
4
+ require "mongoid/collections/mimic"
5
+ require "mongoid/collections/master"
6
+ require "mongoid/collections/slaves"
7
+
8
+ module Mongoid #:nodoc
9
+ class Collection
10
+ include Collections::Mimic
11
+ attr_reader :counter, :name
12
+
13
+ # All write operations should delegate to the master connection. These
14
+ # operations mimic the methods on a Mongo:Collection.
15
+ #
16
+ # Example:
17
+ #
18
+ # <tt>collection.save({ :name => "Al" })</tt>
19
+ proxy(:master, Collections::Operations::WRITE)
20
+
21
+ # All read operations should be intelligently directed to either the master
22
+ # or the slave, depending on where the read counter is and what it's
23
+ # maximum was configured at.
24
+ #
25
+ # Example:
26
+ #
27
+ # <tt>collection.find({ :name => "Al" })</tt>
28
+ proxy(:directed, (Collections::Operations::READ - [:find]))
29
+
30
+ # Determines where to send the next read query. If the slaves are not
31
+ # defined then send to master. If the read counter is under the configured
32
+ # maximum then return the master. In any other case return the slaves.
33
+ #
34
+ # Example:
35
+ #
36
+ # <tt>collection.directed</tt>
37
+ #
38
+ # Return:
39
+ #
40
+ # Either a +Master+ or +Slaves+ collection.
41
+ def directed
42
+ if under_max_counter? || slaves.empty?
43
+ @counter = @counter + 1
44
+ master
45
+ else
46
+ @counter = 0
47
+ slaves
48
+ end
49
+ end
50
+
51
+ # Find documents from the database given a selector and options.
52
+ #
53
+ # Options:
54
+ #
55
+ # selector: A +Hash+ selector that is the query.
56
+ # options: The options to pass to the db.
57
+ #
58
+ # Example:
59
+ #
60
+ # <tt>collection.find({ :test => "value" })</tt>
61
+ def find(selector = {}, options = {})
62
+ cursor = Mongoid::Cursor.new(self, directed.find(selector, options))
63
+ if block_given?
64
+ yield cursor; cursor.close
65
+ else
66
+ cursor
67
+ end
68
+ end
69
+
70
+ # Initialize a new Mongoid::Collection, setting up the master, slave, and
71
+ # name attributes. Masters will be used for writes, slaves for reads.
72
+ #
73
+ # Example:
74
+ #
75
+ # <tt>Mongoid::Collection.new(masters, slaves, "test")</tt>
76
+ def initialize(name)
77
+ @name, @counter = name, 0
78
+ end
79
+
80
+ # Return the object responsible for reading documents from the database.
81
+ # This is usually the slave databases, but in their absence the master will
82
+ # handle the task.
83
+ #
84
+ # Example:
85
+ #
86
+ # <tt>collection.reader</tt>
87
+ def slaves
88
+ @slaves ||= Collections::Slaves.new(Mongoid.slaves, @name)
89
+ end
90
+
91
+ # Return the object responsible for writes to the database. This will
92
+ # always return a collection associated with the Master DB.
93
+ #
94
+ # Example:
95
+ #
96
+ # <tt>collection.writer</tt>
97
+ def master
98
+ @master ||= Collections::Master.new(Mongoid.master, @name)
99
+ end
100
+
101
+ protected
102
+ def under_max_counter?
103
+ @counter < Mongoid.max_successive_reads
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Collections #:nodoc:
4
+ class CyclicIterator
5
+
6
+ attr_reader :counter
7
+
8
+ # Performs iteration over an array, if the array gets to the end then loop
9
+ # back to the first.
10
+ #
11
+ # Example:
12
+ #
13
+ # <tt>CyclicIterator.new([ first, second ])</tt>
14
+ def initialize(array)
15
+ @array, @counter = array, -1
16
+ end
17
+
18
+ # Get the next element in the array. If the element is the last in the
19
+ # array then return the first.
20
+ #
21
+ # Example:
22
+ #
23
+ # <tt>iterator.next</tt>
24
+ #
25
+ # Returns:
26
+ #
27
+ # The next element in the array.
28
+ def next
29
+ (@counter == @array.size - 1) ? @counter = 0 : @counter = @counter + 1
30
+ @array[@counter]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Collections #:nodoc:
4
+ class Master
5
+ include Mimic
6
+
7
+ attr_reader :collection
8
+
9
+ # All read and write operations should delegate to the master connection.
10
+ # These operations mimic the methods on a Mongo:Collection.
11
+ #
12
+ # Example:
13
+ #
14
+ # <tt>collection.save({ :name => "Al" })</tt>
15
+ proxy(:collection, Operations::ALL)
16
+
17
+ # Create the new database writer. Will create a collection from the
18
+ # master database.
19
+ #
20
+ # Example:
21
+ #
22
+ # <tt>Master.new(master, "mongoid_people")</tt>
23
+ def initialize(master, name)
24
+ @collection = master.collection(name)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Collections #:nodoc:
4
+ module Mimic #:nodoc:
5
+ def self.included(base)
6
+ base.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module InstanceMethods #:nodoc:
13
+ # Retry the supplied operation until the reconnect time has expired,
14
+ # defined in the mongoid Config module.
15
+ #
16
+ # Example:
17
+ #
18
+ # <tt>master.attempt(operation)</tt>
19
+ def attempt(operation, start)
20
+ begin
21
+ elapsed = (Time.now - start)
22
+ operation.call
23
+ rescue Mongo::ConnectionFailure => error
24
+ (elapsed < Mongoid.reconnect_time) ? retry : (raise error)
25
+ end
26
+ end
27
+ end
28
+
29
+ module ClassMethods #:nodoc:
30
+ # Proxy all the supplied operations to the internal collection or target.
31
+ #
32
+ # Example:
33
+ #
34
+ # <tt>proxy Operations::ALL, :collection</tt>
35
+ def proxy(target, operations)
36
+ operations.each do |name|
37
+ define_method(name) do |*args|
38
+ operation = lambda { send(target).send(name, *args) }
39
+ attempt(operation, Time.now)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end