mongo_mapper 0.15.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
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