mongo_mapper 0.15.0 → 0.15.1

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -8
  3. data/lib/mongo_mapper.rb +2 -0
  4. data/lib/mongo_mapper/plugins/accessible.rb +1 -1
  5. data/lib/mongo_mapper/plugins/associations/base.rb +10 -2
  6. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +36 -6
  7. data/lib/mongo_mapper/plugins/associations/in_foreign_array_proxy.rb +136 -0
  8. data/lib/mongo_mapper/plugins/associations/many_association.rb +4 -2
  9. data/lib/mongo_mapper/plugins/associations/proxy.rb +5 -1
  10. data/lib/mongo_mapper/plugins/associations/single_association.rb +5 -4
  11. data/lib/mongo_mapper/plugins/identity_map.rb +3 -1
  12. data/lib/mongo_mapper/plugins/keys.rb +8 -0
  13. data/lib/mongo_mapper/plugins/keys/key.rb +13 -8
  14. data/lib/mongo_mapper/plugins/modifiers.rb +12 -4
  15. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +4 -3
  16. data/lib/mongo_mapper/plugins/strong_parameters.rb +26 -0
  17. data/lib/mongo_mapper/railtie.rb +1 -0
  18. data/lib/mongo_mapper/version.rb +1 -1
  19. data/spec/examples.txt +1655 -1581
  20. data/spec/functional/accessible_spec.rb +6 -0
  21. data/spec/functional/associations/belongs_to_proxy_spec.rb +18 -1
  22. data/spec/functional/associations/in_array_proxy_spec.rb +135 -0
  23. data/spec/functional/associations/in_foreign_array_proxy_spec.rb +321 -0
  24. data/spec/functional/document_spec.rb +0 -3
  25. data/spec/functional/identity_map_spec.rb +2 -2
  26. data/spec/functional/keys_spec.rb +29 -11
  27. data/spec/functional/modifiers_spec.rb +14 -0
  28. data/spec/functional/querying_spec.rb +22 -0
  29. data/spec/functional/strong_parameters_spec.rb +49 -0
  30. data/spec/unit/associations/proxy_spec.rb +0 -4
  31. data/spec/unit/keys_spec.rb +10 -1
  32. data/spec/unit/validations_spec.rb +18 -18
  33. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 143438e47efd4cc910b443067a35aff261a7c32baa12bbe8836b0505edb6c8ad
4
- data.tar.gz: 0d8d33172da4805fa47544585d2ea5369a82e9154be19dcd9be2789b5c2747cb
3
+ metadata.gz: 77d669d6c4286995fb2afbed7cf23475c6dc496cb3faf97a57f3b8f115e263b3
4
+ data.tar.gz: d11040bb05eeb6cb57465c4585c0667b16d750b16d7fd5fedca5e03734b769ac
5
5
  SHA512:
6
- metadata.gz: 50b970107ed41cc982e24fe9d171fed471847c593db3c5239b7e60fa88fb67ef799473eb1a088d2eb92d6d2cda49d8ec391b61a24e92bb5b9a9e7eea399ed61b
7
- data.tar.gz: 1674cc8cdc27d2f3bc7460a8f32017903bde7263feeae62bf3074386d5fa0b080b3fc52c4407c200a7b4df8fb083fd917e6b5611b19f8538ec10033c789667ab
6
+ metadata.gz: 56a665e63c65da52238cc18348333a2a9c6b6d1c5acf564308f60f8fcec8a1be759c5b7546a0259092a7f6acc9170659d55c462ac59fffe6520f3339a650df15
7
+ data.tar.gz: 8dceac60d1ef1159585453450d2960e4d9d50e472ec7728f3b703da8e40e80af625b01bb6c4022327e16631960faa698da93c7d37c563cd1c839eb3ebfc28ddb
data/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  A Ruby Object Mapper for Mongo.
4
4
 
5
- [<img src="https://badge.fury.io/rb/mongo_mapper.png" alt="RubyGems">](https://rubygems.org/gems/mongo_mapper)
5
+ [<img src="https://badge.fury.io/rb/mongo_mapper.svg" alt="RubyGems">](https://rubygems.org/gems/mongo_mapper)
6
6
 
7
- [<img src="https://travis-ci.org/mongomapper/mongomapper.png?branch=master" alt="Build Status" />](https://travis-ci.org/mongomapper/mongomapper)
7
+ [<img src="https://travis-ci.org/mongomapper/mongomapper.svg?branch=master" alt="Build Status" />](https://travis-ci.org/mongomapper/mongomapper)
8
8
 
9
- [<img src="https://coveralls.io/repos/mongomapper/mongomapper/badge.png" alt="Coverage Status" />](https://coveralls.io/r/mongomapper/mongomapper)
9
+ [<img src="https://coveralls.io/repos/mongomapper/mongomapper/badge.svg" alt="Coverage Status" />](https://coveralls.io/r/mongomapper/mongomapper)
10
10
 
11
11
  ## Install
12
12
 
@@ -45,8 +45,6 @@ Additionally, MongoMapper is tested against:
45
45
 
46
46
  Hit up the Google group: http://groups.google.com/group/mongomapper
47
47
 
48
- Hop on IRC: irc://chat.freenode.net/#mongomapper
49
-
50
48
  ## Copyright
51
49
 
52
50
  Copyright (c) 2009-2020 MongoMapper. See LICENSE for details.
@@ -59,6 +57,5 @@ MongoMapper/Plucky is:
59
57
  * Chris Heald
60
58
  * Scott Taylor
61
59
 
62
- With contributions from:
63
-
64
- * Frederick Cheung
60
+ But all open source projects are a team effort and could not happen without
61
+ everyone who has contributed. See `CONTRIBUTORS` for the full list. Thank you!
@@ -62,6 +62,7 @@ module MongoMapper
62
62
  autoload :Scopes, 'mongo_mapper/plugins/scopes'
63
63
  autoload :Serialization, 'mongo_mapper/plugins/serialization'
64
64
  autoload :Stats, 'mongo_mapper/plugins/stats'
65
+ autoload :StrongParameters, 'mongo_mapper/plugins/strong_parameters'
65
66
  autoload :Timestamps, 'mongo_mapper/plugins/timestamps'
66
67
  autoload :Userstamps, 'mongo_mapper/plugins/userstamps'
67
68
  autoload :Validations, 'mongo_mapper/plugins/validations'
@@ -87,6 +88,7 @@ module MongoMapper
87
88
  autoload :OneEmbeddedProxy, 'mongo_mapper/plugins/associations/one_embedded_proxy'
88
89
  autoload :OneEmbeddedPolymorphicProxy, 'mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy'
89
90
  autoload :InArrayProxy, 'mongo_mapper/plugins/associations/in_array_proxy'
91
+ autoload :InForeignArrayProxy, 'mongo_mapper/plugins/associations/in_foreign_array_proxy'
90
92
  end
91
93
  end
92
94
 
@@ -59,4 +59,4 @@ module MongoMapper
59
59
  end
60
60
  end
61
61
  end
62
- end
62
+ end
@@ -6,7 +6,7 @@ module MongoMapper
6
6
  attr_reader :name, :options, :query_options
7
7
 
8
8
  # Options that should not be considered MongoDB query options/criteria
9
- AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :polymorphic, :autosave, :touch, :counter_cache]
9
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :from, :polymorphic, :autosave, :touch, :counter_cache, :ordered]
10
10
 
11
11
  def initialize(name, options={}, &extension)
12
12
  @name, @options, @query_options, @original_options = name.to_sym, {}, {}, options
@@ -28,13 +28,17 @@ module MongoMapper
28
28
  end
29
29
 
30
30
  def as?
31
- !!@options[:as]
31
+ !in_foreign_array? && !!@options[:as]
32
32
  end
33
33
 
34
34
  def in_array?
35
35
  !!@options[:in]
36
36
  end
37
37
 
38
+ def in_foreign_array?
39
+ !!@options[:from]
40
+ end
41
+
38
42
  def embeddable?
39
43
  klass.embeddable?
40
44
  end
@@ -47,6 +51,10 @@ module MongoMapper
47
51
  !!@options[:counter_cache]
48
52
  end
49
53
 
54
+ def ordered?
55
+ !!@options[:ordered]
56
+ end
57
+
50
58
  def type_key_name
51
59
  "_type"
52
60
  end
@@ -6,11 +6,11 @@ module MongoMapper
6
6
  include DynamicQuerying::ClassMethods
7
7
 
8
8
  def find(*args)
9
- query.find(*scoped_ids(args))
9
+ order_results(query.find(*scoped_ids(args)))
10
10
  end
11
11
 
12
12
  def find!(*args)
13
- query.find!(*scoped_ids(args))
13
+ order_results(query.find!(*scoped_ids(args)))
14
14
  end
15
15
 
16
16
  def paginate(options)
@@ -20,17 +20,29 @@ module MongoMapper
20
20
 
21
21
  def all(options={})
22
22
  return [] if ids.blank?
23
- query(options).all
23
+ order_results(query(options).all)
24
24
  end
25
25
 
26
26
  def first(options={})
27
27
  return nil if ids.blank?
28
- query(options).first
28
+
29
+ if ordered?
30
+ ids = find_ordered_ids(options)
31
+ find!(ids.first) if ids.any?
32
+ else
33
+ query(options).first
34
+ end
29
35
  end
30
36
 
31
37
  def last(options={})
32
38
  return nil if ids.blank?
33
- query(options).last
39
+
40
+ if ordered?
41
+ ids = find_ordered_ids(options)
42
+ find!(ids.last) if ids.any?
43
+ else
44
+ query(options).last
45
+ end
34
46
  end
35
47
 
36
48
  def count(options={})
@@ -120,6 +132,13 @@ module MongoMapper
120
132
  valid.empty? ? nil : valid
121
133
  end
122
134
 
135
+ def find_ordered_ids(options={})
136
+ return ids if options.empty?
137
+
138
+ matched_ids = klass.collection.distinct(:_id, query(options).criteria.to_hash)
139
+ matched_ids.sort_by! { |matched_id| ids.index(matched_id) }
140
+ end
141
+
123
142
  def find_target
124
143
  return [] if ids.blank?
125
144
  all
@@ -128,7 +147,18 @@ module MongoMapper
128
147
  def ids
129
148
  proxy_owner[options[:in]]
130
149
  end
150
+
151
+ def order_results(objects)
152
+ return objects if !ordered?
153
+ return objects unless objects.respond_to?(:to_a) && objects.respond_to?(:sort_by)
154
+ objects.sort_by { |obj| ids.index(obj.id) }
155
+ end
156
+
157
+ def ordered?
158
+ association.ordered?
159
+ end
160
+
131
161
  end
132
162
  end
133
163
  end
134
- end
164
+ end
@@ -0,0 +1,136 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module Associations
5
+ class InForeignArrayProxy < Collection
6
+ include DynamicQuerying::ClassMethods
7
+
8
+ def find(*args)
9
+ query.find(*scoped_ids(args))
10
+ end
11
+
12
+ def find!(*args)
13
+ query.find!(*scoped_ids(args))
14
+ end
15
+
16
+ def paginate(options)
17
+ query.paginate(options)
18
+ end
19
+
20
+ def all(options={})
21
+ query(options).all
22
+ end
23
+
24
+ def first(options={})
25
+ query(options).first
26
+ end
27
+
28
+ def last(options={})
29
+ query(options).last
30
+ end
31
+
32
+ def count(options={})
33
+ query(options).count
34
+ end
35
+
36
+ def destroy_all(options={})
37
+ all(options).each do |doc|
38
+ doc.destroy
39
+ end
40
+ reset
41
+ end
42
+
43
+ def delete_all(options={})
44
+ docs = query(options).fields(:_id).all
45
+ klass.delete(docs.map { |d| d.id })
46
+ reset
47
+ end
48
+
49
+ def nullify
50
+ replace([])
51
+ reset
52
+ end
53
+
54
+ def create(attrs={})
55
+ doc = klass.create(attrs)
56
+ if doc.persisted?
57
+ inverse_association(doc) << proxy_owner
58
+ doc.save
59
+ reset
60
+ end
61
+ doc
62
+ end
63
+
64
+ def create!(attrs={})
65
+ doc = klass.create!(attrs)
66
+
67
+ if doc.persisted?
68
+ inverse_association(doc) << proxy_owner
69
+ doc.save
70
+ reset
71
+ end
72
+ doc
73
+ end
74
+
75
+ def <<(*docs)
76
+ flatten_deeper(docs).each do |doc|
77
+ inverse_association(doc) << proxy_owner
78
+ doc.save
79
+ end
80
+ reset
81
+ end
82
+ alias_method :push, :<<
83
+ alias_method :concat, :<<
84
+
85
+ def replace(docs)
86
+ doc_ids = docs.map do |doc|
87
+ doc.save unless doc.persisted?
88
+ inverse_association(doc) << proxy_owner
89
+ doc.save
90
+ doc.id
91
+ end
92
+
93
+ replace_selector = { options[:from] => proxy_owner.id }
94
+ unless doc_ids.empty?
95
+ replace_selector[:_id] = {"$not" => {"$in" => doc_ids}}
96
+ end
97
+
98
+ klass.collection.update_many(replace_selector, {
99
+ "$pull" => { options[:from] => proxy_owner.id }
100
+ })
101
+
102
+ reset
103
+ end
104
+
105
+ private
106
+
107
+ def query(options={})
108
+ klass.query({}.
109
+ merge(association.query_options).
110
+ merge(options).
111
+ merge(criteria))
112
+ end
113
+
114
+ def criteria
115
+ {options[:from] => proxy_owner.id}
116
+ end
117
+
118
+ def scoped_ids(args)
119
+ valid = args.flatten.map do |id|
120
+ id = ObjectId.to_mongo(id) if klass.using_object_id?
121
+ id
122
+ end
123
+ valid.empty? ? nil : valid
124
+ end
125
+
126
+ def find_target
127
+ all
128
+ end
129
+
130
+ def inverse_association(doc)
131
+ doc.send(options[:as].to_s.pluralize)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -17,6 +17,8 @@ module MongoMapper
17
17
  ManyPolymorphicProxy
18
18
  elsif as?
19
19
  ManyDocumentsAsProxy
20
+ elsif in_foreign_array?
21
+ InForeignArrayProxy
20
22
  elsif in_array?
21
23
  InArrayProxy
22
24
  else
@@ -26,7 +28,7 @@ module MongoMapper
26
28
  end
27
29
 
28
30
  def setup(model)
29
- model.associations_module.module_eval <<-end_eval
31
+ model.associations_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
30
32
  def #{name}
31
33
  get_proxy(associations[#{name.inspect}])
32
34
  end
@@ -60,4 +62,4 @@ module MongoMapper
60
62
  end
61
63
  end
62
64
  end
63
- end
65
+ end
@@ -13,7 +13,6 @@ module MongoMapper
13
13
 
14
14
  attr_reader :proxy_owner, :association, :target
15
15
 
16
- alias :proxy_target :target
17
16
  alias :proxy_association :association
18
17
 
19
18
  def_delegators :proxy_association, :klass, :options
@@ -100,6 +99,11 @@ module MongoMapper
100
99
  end
101
100
  end
102
101
 
102
+ def read
103
+ load_target
104
+ @target
105
+ end
106
+
103
107
  protected
104
108
 
105
109
  def load_target
@@ -5,10 +5,11 @@ module MongoMapper
5
5
  class SingleAssociation < Base
6
6
  def setup(model)
7
7
  @model = model
8
- model.associations_module.module_eval <<-end_eval
8
+
9
+ model.associations_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
9
10
  def #{name}
10
11
  proxy = get_proxy(associations[#{name.inspect}])
11
- proxy.nil? ? nil : proxy
12
+ proxy.nil? ? nil : proxy.read
12
13
  end
13
14
 
14
15
  def #{name}=(value)
@@ -20,7 +21,7 @@ module MongoMapper
20
21
  end
21
22
 
22
23
  proxy.replace(value)
23
- value
24
+ proxy.read
24
25
  end
25
26
 
26
27
  def #{name}?
@@ -43,4 +44,4 @@ module MongoMapper
43
44
  end
44
45
  end
45
46
  end
46
- end
47
+ end
@@ -133,10 +133,12 @@ module PluckyMethods
133
133
  end
134
134
 
135
135
  def find_each(opts={})
136
+ return super if !block_given?
137
+
136
138
  query = clone.amend(opts)
137
139
  super(opts) do |doc|
138
140
  doc.remove_from_identity_map if doc && query.fields?
139
- yield doc if block_given?
141
+ yield doc
140
142
  end
141
143
  end
142
144
  end
@@ -295,6 +295,14 @@ module MongoMapper
295
295
  end
296
296
  end
297
297
 
298
+ # NOTE: We can't use alias_method here as we need the #attributes=
299
+ # superclass method to get called (for example:
300
+ # MongoMapper::Plugins::Accessible filters non-permitted parameters
301
+ # through `attributes=`
302
+ def assign_attributes(new_attributes)
303
+ self.attributes = new_attributes
304
+ end
305
+
298
306
  def to_mongo(include_abbreviatons = true)
299
307
  Hash.new.tap do |attrs|
300
308
  self.class.unaliased_keys.each do |name, key|
@@ -3,10 +3,11 @@ module MongoMapper
3
3
  module Plugins
4
4
  module Keys
5
5
  class Key
6
- attr_accessor :name, :type, :options, :default, :ivar, :abbr, :accessors
7
-
6
+ RESERVED_KEYS = %w( id class object_id attributes )
8
7
  ID_STR = '_id'
9
8
 
9
+ attr_accessor :name, :type, :options, :default, :ivar, :abbr, :accessors
10
+
10
11
  def initialize(*args)
11
12
  options_from_args = args.extract_options!
12
13
  @name, @type = args.shift.to_s, args.shift
@@ -60,12 +61,17 @@ module MongoMapper
60
61
  # Special Case: Generate default _id on access
61
62
  value = default_value if @is_id and !value
62
63
 
64
+ value = type.from_mongo(value)
65
+
63
66
  if @typecast
64
- klass = typecast_class # Don't make this lookup on every call
65
- type.from_mongo(value).map { |v| klass.from_mongo(v) }
66
- else
67
- type.from_mongo(value)
67
+ klass = typecast_class # Don't make this lookup on every call
68
+ # typecast assumes array-ish object.
69
+ value = value.map { |v| klass.from_mongo(v) }
70
+ # recast it in the original type
71
+ value = type.from_mongo(value)
68
72
  end
73
+
74
+ value
69
75
  end
70
76
 
71
77
  def set(value)
@@ -93,7 +99,6 @@ module MongoMapper
93
99
  !!@name.match(/\A[a-z_][a-z0-9_]*\z/i)
94
100
  end
95
101
 
96
- RESERVED_KEYS = %w( id class object_id )
97
102
  def reserved_name?
98
103
  RESERVED_KEYS.include?(@name)
99
104
  end
@@ -124,7 +129,7 @@ module MongoMapper
124
129
 
125
130
  def validate_key_name!
126
131
  if reserved_name?
127
- raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name (did you mean to use _id?)")
132
+ raise MongoMapper::InvalidKey.new("`#{@name}` is a reserved key name")
128
133
  elsif !valid_ruby_name?
129
134
  raise MongoMapper::InvalidKey.new("`#{@name}` is not a valid key name. Keys must match [a-z][a-z0-9_]*")
130
135
  end