mongoid 4.0.0.alpha2 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/README.md +3 -3
  4. data/lib/config/locales/en.yml +13 -0
  5. data/lib/mongoid.rb +3 -1
  6. data/lib/mongoid/atomic.rb +1 -1
  7. data/lib/mongoid/atomic/paths/embedded/many.rb +1 -1
  8. data/lib/mongoid/atomic/paths/embedded/one.rb +1 -1
  9. data/lib/mongoid/attributes.rb +23 -1
  10. data/lib/mongoid/attributes/processing.rb +1 -1
  11. data/lib/mongoid/composable.rb +3 -2
  12. data/lib/mongoid/contextual/command.rb +0 -26
  13. data/lib/mongoid/contextual/geo_near.rb +1 -1
  14. data/lib/mongoid/contextual/mongo.rb +6 -29
  15. data/lib/mongoid/contextual/text_search.rb +3 -5
  16. data/lib/mongoid/criteria.rb +1 -1
  17. data/lib/mongoid/criteria/modifiable.rb +27 -7
  18. data/lib/mongoid/criteria/permission.rb +70 -0
  19. data/lib/mongoid/document.rb +5 -6
  20. data/lib/mongoid/errors.rb +2 -0
  21. data/lib/mongoid/errors/document_not_destroyed.rb +25 -0
  22. data/lib/mongoid/errors/readonly_document.rb +24 -0
  23. data/lib/mongoid/extensions/boolean.rb +1 -0
  24. data/lib/mongoid/extensions/hash.rb +1 -1
  25. data/lib/mongoid/factory.rb +5 -3
  26. data/lib/mongoid/fields.rb +32 -0
  27. data/lib/mongoid/fields/localized.rb +1 -1
  28. data/lib/mongoid/fields/standard.rb +1 -1
  29. data/lib/mongoid/findable.rb +1 -0
  30. data/lib/mongoid/interceptable.rb +11 -6
  31. data/lib/mongoid/log_subscriber.rb +34 -1
  32. data/lib/mongoid/persistable/deletable.rb +1 -0
  33. data/lib/mongoid/persistable/destroyable.rb +7 -2
  34. data/lib/mongoid/persistable/updatable.rb +27 -26
  35. data/lib/mongoid/query_cache.rb +246 -0
  36. data/lib/mongoid/railties/database.rake +4 -26
  37. data/lib/mongoid/relations.rb +8 -22
  38. data/lib/mongoid/relations/accessors.rb +0 -3
  39. data/lib/mongoid/relations/binding.rb +1 -1
  40. data/lib/mongoid/relations/bindings/embedded/in.rb +1 -1
  41. data/lib/mongoid/relations/eager.rb +5 -6
  42. data/lib/mongoid/relations/eager/base.rb +97 -5
  43. data/lib/mongoid/relations/eager/belongs_to.rb +1 -0
  44. data/lib/mongoid/relations/eager/has_and_belongs_to_many.rb +16 -9
  45. data/lib/mongoid/relations/eager/has_many.rb +1 -0
  46. data/lib/mongoid/relations/eager/has_one.rb +1 -0
  47. data/lib/mongoid/relations/embedded/batchable.rb +1 -1
  48. data/lib/mongoid/relations/embedded/in.rb +4 -4
  49. data/lib/mongoid/relations/embedded/many.rb +7 -5
  50. data/lib/mongoid/relations/embedded/one.rb +1 -1
  51. data/lib/mongoid/relations/macros.rb +1 -0
  52. data/lib/mongoid/relations/marshalable.rb +3 -3
  53. data/lib/mongoid/relations/proxy.rb +12 -10
  54. data/lib/mongoid/relations/referenced/in.rb +2 -2
  55. data/lib/mongoid/relations/referenced/many.rb +9 -9
  56. data/lib/mongoid/relations/referenced/many_to_many.rb +7 -7
  57. data/lib/mongoid/relations/referenced/one.rb +4 -4
  58. data/lib/mongoid/{state.rb → stateful.rb} +13 -1
  59. data/lib/mongoid/tasks/database.rake +31 -0
  60. data/lib/mongoid/tasks/database.rb +107 -0
  61. data/lib/mongoid/threaded.rb +0 -47
  62. data/lib/mongoid/validatable/uniqueness.rb +4 -16
  63. data/lib/mongoid/version.rb +1 -1
  64. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +0 -3
  65. data/lib/rails/mongoid.rb +0 -124
  66. data/spec/app/models/edit.rb +5 -0
  67. data/spec/app/models/even.rb +7 -0
  68. data/spec/app/models/line_item.rb +1 -1
  69. data/spec/app/models/note.rb +2 -0
  70. data/spec/app/models/odd.rb +7 -0
  71. data/spec/app/models/record.rb +5 -0
  72. data/spec/app/models/wiki_page.rb +1 -1
  73. data/spec/mongoid/attributes_spec.rb +76 -1
  74. data/spec/mongoid/changeable_spec.rb +6 -2
  75. data/spec/mongoid/contextual/mongo_spec.rb +3 -1
  76. data/spec/mongoid/contextual/text_search_spec.rb +3 -1
  77. data/spec/mongoid/criteria/modifiable_spec.rb +192 -0
  78. data/spec/mongoid/criteria_spec.rb +6 -2
  79. data/spec/mongoid/errors/document_not_destroyed_spec.rb +33 -0
  80. data/spec/mongoid/errors/readonly_document_spec.rb +29 -0
  81. data/spec/mongoid/fields/localized_spec.rb +15 -0
  82. data/spec/mongoid/fields_spec.rb +88 -2
  83. data/spec/mongoid/log_subscriber_spec.rb +3 -3
  84. data/spec/mongoid/persistable/deletable_spec.rb +14 -1
  85. data/spec/mongoid/persistable/destroyable_spec.rb +45 -1
  86. data/spec/mongoid/persistable/savable_spec.rb +34 -5
  87. data/spec/mongoid/query_cache_spec.rb +197 -0
  88. data/spec/mongoid/relations/bindings/embedded/in_spec.rb +2 -2
  89. data/spec/mongoid/relations/builders/referenced/many_spec.rb +1 -1
  90. data/spec/mongoid/relations/eager/has_and_belongs_to_many_spec.rb +11 -37
  91. data/spec/mongoid/relations/eager/has_one_spec.rb +1 -1
  92. data/spec/mongoid/relations/embedded/in_spec.rb +1 -1
  93. data/spec/mongoid/relations/embedded/many_spec.rb +10 -10
  94. data/spec/mongoid/relations/embedded/one_spec.rb +10 -2
  95. data/spec/mongoid/relations/referenced/in_spec.rb +1 -1
  96. data/spec/mongoid/relations/referenced/many_spec.rb +37 -2
  97. data/spec/mongoid/relations/touchable_spec.rb +20 -0
  98. data/spec/mongoid/{state_spec.rb → stateful_spec.rb} +26 -1
  99. data/spec/mongoid/tasks/database_rake_spec.rb +285 -0
  100. data/spec/mongoid/tasks/database_spec.rb +148 -0
  101. data/spec/mongoid/validatable/uniqueness_spec.rb +7 -0
  102. data/spec/rails/mongoid_spec.rb +0 -316
  103. data/spec/spec_helper.rb +1 -0
  104. metadata +30 -8
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ class Criteria
4
+ module Permission
5
+
6
+ [:all,
7
+ :all_in,
8
+ :and,
9
+ :all_of,
10
+ :between,
11
+ :elem_match,
12
+ :exists,
13
+ :gt,
14
+ :gte,
15
+ :in,
16
+ :any_in,
17
+ :lt,
18
+ :lte,
19
+ :max_distance,
20
+ :mod,
21
+ :ne,
22
+ :excludes,
23
+ :near,
24
+ :near_sphere,
25
+ :nin,
26
+ :not_in,
27
+ :nor,
28
+ :negating?,
29
+ :not,
30
+ :or,
31
+ :any_of,
32
+ :with_size,
33
+ :with_type,
34
+ :where,
35
+ :within_box,
36
+ :within_circle,
37
+ :within_polygon,
38
+ :within_spherical_circle
39
+ ].each do |method|
40
+ define_method(method) do |*criteria|
41
+ raise Errors::CriteriaNotPermitted.new(klass, method, criteria) unless should_permit?(criteria)
42
+ super(*criteria)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Ensure that the criteria are permitted.
49
+ #
50
+ # @example Ignoring ActionController::Parameters
51
+ # should_permit?({_id: ActionController::Parameters.new("$size" => 1)})
52
+ #
53
+ # @api private
54
+ #
55
+ # @param [ Object ] criteria
56
+ # @return [ Boolean ] if should permit
57
+ def should_permit?(criteria)
58
+ if criteria.respond_to?(:permitted?)
59
+ return criteria.permitted?
60
+ elsif criteria.respond_to?(:each)
61
+ criteria.each do |criterion|
62
+ return false unless should_permit?(criterion)
63
+ end
64
+ end
65
+
66
+ true
67
+ end
68
+ end
69
+ end
70
+ end
@@ -13,7 +13,6 @@ require "mongoid/equality"
13
13
  require "mongoid/criteria"
14
14
  require "mongoid/factory"
15
15
  require "mongoid/fields"
16
- require "mongoid/state"
17
16
  require "mongoid/timestamps"
18
17
  require "mongoid/composable"
19
18
 
@@ -25,7 +24,7 @@ module Mongoid
25
24
  extend ActiveSupport::Concern
26
25
  include Composable
27
26
 
28
- attr_accessor :criteria_instance_id
27
+ attr_accessor :__selected_fields
29
28
  attr_reader :new_record
30
29
 
31
30
  included do
@@ -301,16 +300,16 @@ module Mongoid
301
300
  # Person.instantiate(:title => "Sir", :age => 30)
302
301
  #
303
302
  # @param [ Hash ] attrs The hash of attributes to instantiate with.
304
- # @param [ Integer ] criteria_instance_id The criteria id that
305
- # instantiated the document.
303
+ # @param [ Integer ] selected_fields The selected fields from the
304
+ # criteria.
306
305
  #
307
306
  # @return [ Document ] A new document.
308
307
  #
309
308
  # @since 1.0.0
310
- def instantiate(attrs = nil, criteria_instance_id = nil)
309
+ def instantiate(attrs = nil, selected_fields = nil)
311
310
  attributes = attrs || {}
312
311
  doc = allocate
313
- doc.criteria_instance_id = criteria_instance_id
312
+ doc.__selected_fields = selected_fields
314
313
  doc.instance_variable_set(:@attributes, attributes)
315
314
  doc.apply_defaults
316
315
  yield(doc) if block_given?
@@ -2,6 +2,7 @@
2
2
  require "mongoid/errors/mongoid_error"
3
3
  require "mongoid/errors/ambiguous_relationship"
4
4
  require "mongoid/errors/callback"
5
+ require "mongoid/errors/document_not_destroyed"
5
6
  require "mongoid/errors/document_not_found"
6
7
  require "mongoid/errors/eager_load"
7
8
  require "mongoid/errors/invalid_collection"
@@ -32,6 +33,7 @@ require "mongoid/errors/no_sessions_config"
32
33
  require "mongoid/errors/no_session_database"
33
34
  require "mongoid/errors/no_session_hosts"
34
35
  require "mongoid/errors/readonly_attribute"
36
+ require "mongoid/errors/readonly_document"
35
37
  require "mongoid/errors/scope_overwrite"
36
38
  require "mongoid/errors/too_many_nested_attribute_records"
37
39
  require "mongoid/errors/unknown_attribute"
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Errors
4
+
5
+ # Raised when attempting to destroy a document that had destory callbacks
6
+ # return false.
7
+ #
8
+ # @since 4.0.0
9
+ class DocumentNotDestroyed < MongoidError
10
+
11
+ # Instnatiate the exception.
12
+ #
13
+ # @example Create the error.
14
+ # DocumentNotDestroyed.new(Band)
15
+ #
16
+ # @param [ Object ] id The document id.
17
+ # @param [ Class ] klass The document class.
18
+ #
19
+ # @since 4.0.0
20
+ def initialize(id, klass)
21
+ super(compose_message("document_not_destroyed", { id: id.inspect, klass: klass }))
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Errors
4
+
5
+ # Raised when attempting to persist a document that was loaded from the
6
+ # database with partial fields.
7
+ #
8
+ # @since 4.0.0
9
+ class ReadonlyDocument < MongoidError
10
+
11
+ # Instnatiate the exception.
12
+ #
13
+ # @example Create the error.
14
+ # ReadonlyDocument.new(Band)
15
+ #
16
+ # @param [ Class ] klass The document class.
17
+ #
18
+ # @since 4.0.0
19
+ def initialize(klass)
20
+ super(compose_message("readonly_document", { klass: klass }))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -16,6 +16,7 @@ module Mongoid
16
16
  def mongoize(object)
17
17
  ::Boolean.evolve(object)
18
18
  end
19
+ alias :evolve :mongoize
19
20
  end
20
21
  end
21
22
  end
@@ -40,7 +40,7 @@ module Mongoid
40
40
  each_pair do |key, value|
41
41
  if key =~ /\$/
42
42
  value.each_pair do |_key, _value|
43
- value[_key] = mongoize_for(key, klass, _key, _value)
43
+ value[_key] = (key == "$rename") ? _value.to_s : mongoize_for(key, klass, _key, _value)
44
44
  end
45
45
  (consolidated[key] ||= {}).merge!(value)
46
46
  else
@@ -32,14 +32,16 @@ module Mongoid
32
32
  #
33
33
  # @param [ Class ] klass The class to instantiate from if _type is not present.
34
34
  # @param [ Hash ] attributes The document attributes.
35
+ # @param [ Array ] selected_fields If instantiated from a criteria using
36
+ # #only we give the document a list of the selected fields.
35
37
  #
36
38
  # @return [ Document ] The instantiated document.
37
- def from_db(klass, attributes = nil, criteria_instance_id = nil)
39
+ def from_db(klass, attributes = nil, selected_fields = nil)
38
40
  type = (attributes || {})["_type"]
39
41
  if type.blank?
40
- klass.instantiate(attributes, criteria_instance_id)
42
+ klass.instantiate(attributes, selected_fields)
41
43
  else
42
- type.camelize.constantize.instantiate(attributes, criteria_instance_id)
44
+ type.camelize.constantize.instantiate(attributes, selected_fields)
43
45
  end
44
46
  end
45
47
  end
@@ -10,6 +10,28 @@ module Mongoid
10
10
  module Fields
11
11
  extend ActiveSupport::Concern
12
12
 
13
+ # For fields defined with symbols use the correct class.
14
+ #
15
+ # @since 4.0.0
16
+ TYPE_MAPPINGS = {
17
+ array: Array,
18
+ big_decimal: BigDecimal,
19
+ binary: BSON::Binary,
20
+ boolean: Mongoid::Boolean,
21
+ date: Date,
22
+ date_time: DateTime,
23
+ float: Float,
24
+ hash: Hash,
25
+ integer: Integer,
26
+ object_id: BSON::ObjectId,
27
+ range: Range,
28
+ regexp: Regexp,
29
+ set: Set,
30
+ string: String,
31
+ symbol: Symbol,
32
+ time: Time
33
+ }.with_indifferent_access
34
+
13
35
  included do
14
36
  class_attribute :aliased_fields
15
37
  class_attribute :localized_fields
@@ -533,10 +555,20 @@ module Mongoid
533
555
 
534
556
  def field_for(name, options)
535
557
  opts = options.merge(klass: self)
558
+ type_mapping = TYPE_MAPPINGS[options[:type]]
559
+ opts[:type] = type_mapping || unmapped_type(options)
536
560
  return Fields::Localized.new(name, opts) if options[:localize]
537
561
  return Fields::ForeignKey.new(name, opts) if options[:identity]
538
562
  Fields::Standard.new(name, opts)
539
563
  end
564
+
565
+ def unmapped_type(options)
566
+ if "Boolean" == options[:type].to_s
567
+ Mongoid::Boolean
568
+ else
569
+ options[:type] || Object
570
+ end
571
+ end
540
572
  end
541
573
  end
542
574
  end
@@ -63,7 +63,7 @@ module Mongoid
63
63
  def lookup(object)
64
64
  locale = ::I18n.locale
65
65
  if ::I18n.respond_to?(:fallbacks)
66
- object[::I18n.fallbacks[locale].map(&:to_s).find{ |loc| object[loc] }]
66
+ object[::I18n.fallbacks[locale].map(&:to_s).find{ |loc| object.has_key?(loc) }]
67
67
  else
68
68
  object[locale.to_s]
69
69
  end
@@ -50,7 +50,7 @@ module Mongoid
50
50
  #
51
51
  # @since 2.1.8
52
52
  def eval_default(doc)
53
- if fields = Threaded.selection(doc.criteria_instance_id)
53
+ if fields = doc.__selected_fields
54
54
  evaluated_default(doc) if included?(fields)
55
55
  else
56
56
  evaluated_default(doc)
@@ -21,6 +21,7 @@ module Mongoid
21
21
  :extras,
22
22
  :find_and_modify,
23
23
  :find_or_create_by,
24
+ :find_or_create_by!,
24
25
  :find_or_initialize_by,
25
26
  :first_or_create,
26
27
  :first_or_create!,
@@ -125,7 +125,13 @@ module Mongoid
125
125
  # @since 2.3.0
126
126
  def run_callbacks(kind, *args, &block)
127
127
  cascadable_children(kind).each do |child|
128
- unless child.run_callbacks(child_callback_type(kind, child), *args)
128
+ # This is returning false for some destroy tests on 4.1.0.beta1,
129
+ # causing them to fail since 4.1.0 expects a block to be passed if the
130
+ # callbacks for the type are empty. If no block is passed then the nil
131
+ # return value gets interpreted as false and halts the chain.
132
+ #
133
+ # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/callbacks.rb#L79
134
+ if child.run_callbacks(child_callback_type(kind, child), *args) == false
129
135
  return false
130
136
  end
131
137
  end
@@ -170,7 +176,7 @@ module Mongoid
170
176
  relation = send(name)
171
177
  Array.wrap(relation).each do |child|
172
178
  next if children.include?(child)
173
- children.add(child) if cascadable_child?(kind, child)
179
+ children.add(child) if cascadable_child?(kind, child, metadata)
174
180
  child.send(:cascadable_children, kind, children)
175
181
  end
176
182
  end
@@ -189,10 +195,9 @@ module Mongoid
189
195
  # @return [ true, false ] If the child should fire the callback.
190
196
  #
191
197
  # @since 2.3.0
192
- def cascadable_child?(kind, child)
193
- if kind == :initialize || kind == :find
194
- return false
195
- end
198
+ def cascadable_child?(kind, child, metadata)
199
+ return false if kind == :initialize || kind == :find
200
+ return false if kind == :validate && metadata.validate?
196
201
  child.callback_executable?(kind) ? child.in_callback_state?(kind) : false
197
202
  end
198
203
 
@@ -1,6 +1,13 @@
1
+ # encoding: utf-8
1
2
  module Mongoid
2
-
3
+ # A Log subscriber to the moped queries
4
+ #
5
+ # @since 4.0.0
3
6
  class LogSubscriber < ActiveSupport::LogSubscriber
7
+
8
+ # Log the query operation on moped
9
+ #
10
+ # @since 4.0.0
4
11
  def query(event)
5
12
  return unless logger.debug?
6
13
 
@@ -9,10 +16,35 @@ module Mongoid
9
16
  debug(payload[:prefix], payload[:ops], runtime)
10
17
  end
11
18
 
19
+ def query_cache(event)
20
+ return unless logger.debug?
21
+
22
+ database, collection, selector = event.payload[:key]
23
+ operation = "%-12s database=%s collection=%s selector=%s" % ["QUERY CACHE", database, collection, selector.inspect]
24
+ logger.debug operation
25
+ end
26
+ # Log the provided operations.
27
+ #
28
+ # @example Delegates the operation to moped so it can log it.
29
+ # subscriber.debug("MOPED", {}, 30)
30
+ #
31
+ # @param [ String ] prefix The prefix for all operations in the log.
32
+ # @param [ Array ] ops The operations.
33
+ # @param [ String ] runtime The runtime in formatted ms.
34
+ #
35
+ # @since 4.0.0
12
36
  def debug(prefix, operations, runtime)
13
37
  Moped::Loggable.log_operations(prefix, operations, runtime)
14
38
  end
15
39
 
40
+ # Get the logger.
41
+ #
42
+ # @example Get the logger.
43
+ # subscriber.logger
44
+ #
45
+ # @return [ Logger ] The logger.
46
+ #
47
+ # @since 4.0.0
16
48
  def logger
17
49
  Moped.logger
18
50
  end
@@ -20,3 +52,4 @@ module Mongoid
20
52
  end
21
53
 
22
54
  Mongoid::LogSubscriber.attach_to :moped
55
+ Mongoid::LogSubscriber.attach_to :mongoid
@@ -19,6 +19,7 @@ module Mongoid
19
19
  #
20
20
  # @since 1.0.0
21
21
  def delete(options = {})
22
+ raise Errors::ReadonlyDocument.new(self.class) if readonly?
22
23
  prepare_delete do
23
24
  if embedded?
24
25
  delete_as_embedded(options)
@@ -18,13 +18,18 @@ module Mongoid
18
18
  # @return [ true, false ] True if successful, false if not.
19
19
  #
20
20
  # @since 1.0.0
21
- def destroy(options = {})
21
+ def destroy(options = nil)
22
+ raise Errors::ReadonlyDocument.new(self.class) if readonly?
22
23
  self.flagged_for_destroy = true
23
- result = run_callbacks(:destroy) { delete(options) }
24
+ result = run_callbacks(:destroy) { delete(options || {}) }
24
25
  self.flagged_for_destroy = false
25
26
  result
26
27
  end
27
28
 
29
+ def destroy!(options = {})
30
+ destroy || raise(Errors::DocumentNotDestroyed.new(id, self.class))
31
+ end
32
+
28
33
  module ClassMethods
29
34
 
30
35
  # Delete all documents given the supplied conditions. If no conditions
@@ -8,32 +8,6 @@ module Mongoid
8
8
  # @since 4.0.0
9
9
  module Updatable
10
10
 
11
- # Update the document in the database.
12
- #
13
- # @example Update an existing document.
14
- # document.update
15
- #
16
- # @param [ Hash ] options Options to pass to update.
17
- #
18
- # @option options [ true, false ] :validate Whether or not to validate.
19
- #
20
- # @return [ true, false ] True if succeeded, false if not.
21
- #
22
- # @since 1.0.0
23
- def update_document(options = {})
24
- prepare_update(options) do
25
- updates, conflicts = init_atomic_updates
26
- unless updates.empty?
27
- coll = _root.collection
28
- selector = atomic_selector
29
- coll.find(selector).update(positionally(selector, updates))
30
- conflicts.each_pair do |key, value|
31
- coll.find(selector).update(positionally(selector, { key => value }))
32
- end
33
- end
34
- end
35
- end
36
-
37
11
  # Update a single attribute and persist the entire document.
38
12
  # This skips validation but fires the callbacks.
39
13
  #
@@ -146,6 +120,33 @@ module Mongoid
146
120
  end
147
121
  post_process_persist(result, options) and result
148
122
  end
123
+
124
+ # Update the document in the database.
125
+ #
126
+ # @example Update an existing document.
127
+ # document.update
128
+ #
129
+ # @param [ Hash ] options Options to pass to update.
130
+ #
131
+ # @option options [ true, false ] :validate Whether or not to validate.
132
+ #
133
+ # @return [ true, false ] True if succeeded, false if not.
134
+ #
135
+ # @since 1.0.0
136
+ def update_document(options = {})
137
+ raise Errors::ReadonlyDocument.new(self.class) if readonly?
138
+ prepare_update(options) do
139
+ updates, conflicts = init_atomic_updates
140
+ unless updates.empty?
141
+ coll = _root.collection
142
+ selector = atomic_selector
143
+ coll.find(selector).update(positionally(selector, updates))
144
+ conflicts.each_pair do |key, value|
145
+ coll.find(selector).update(positionally(selector, { key => value }))
146
+ end
147
+ end
148
+ end
149
+ end
149
150
  end
150
151
  end
151
152
  end