mongomodel 0.5.3 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mongomodel/concerns/attribute_methods/nested.rb +4 -0
- data/lib/mongomodel/document/indexes.rb +8 -3
- data/lib/mongomodel/document/persistence.rb +12 -6
- data/lib/mongomodel/document/validations/uniqueness.rb +11 -11
- data/lib/mongomodel/support/configuration.rb +14 -3
- data/lib/mongomodel/support/scope.rb +7 -3
- data/lib/mongomodel/version.rb +1 -1
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +1 -1
- data/spec/mongomodel/concerns/attribute_methods/nested_spec.rb +29 -0
- data/spec/mongomodel/document/indexes_spec.rb +42 -30
- data/spec/mongomodel/document/persistence_spec.rb +15 -0
- data/spec/mongomodel/document/validations/uniqueness_spec.rb +26 -0
- data/spec/mongomodel/support/configuration_spec.rb +13 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d006082bc3f5bf2357182fecea49893a06e5994
|
4
|
+
data.tar.gz: d53384261c1968e770c65c4eb4e8faa32ade2c22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e97ed321bde11ad2f30311b4e80a8ea853894e720673d2634142b3e29e1f5088286185169d95fa9bd233aab9637a1d28f69c1b3bd9ef726f69d3b5f6f089a2ff
|
7
|
+
data.tar.gz: b787c2bdaa482398da09028b9093ac4514bc596a446a115d58879ab103fd8c116a078415ba0c85022bd9ca14638421a5dfd4ac4844a42d65b1e4bd59d5e9b1c1
|
@@ -59,6 +59,8 @@ module MongoModel
|
|
59
59
|
end
|
60
60
|
|
61
61
|
collection = send(property)
|
62
|
+
collection.clear if options[:clear]
|
63
|
+
|
62
64
|
attributes_collection.each_with_index do |attributes, index|
|
63
65
|
if collection[index]
|
64
66
|
collection[index].attributes = attributes
|
@@ -85,6 +87,8 @@ module MongoModel
|
|
85
87
|
end
|
86
88
|
|
87
89
|
association = send(association)
|
90
|
+
association.clear if options[:clear]
|
91
|
+
|
88
92
|
attributes_collection.each do |attributes|
|
89
93
|
association.build(attributes)
|
90
94
|
end
|
@@ -55,6 +55,7 @@ module MongoModel
|
|
55
55
|
def initialize(*keys)
|
56
56
|
options = keys.extract_options!
|
57
57
|
|
58
|
+
@name = options.delete(:name)
|
58
59
|
@unique = options.delete(:unique)
|
59
60
|
@min = options.delete(:min)
|
60
61
|
@max = options.delete(:max)
|
@@ -82,6 +83,7 @@ module MongoModel
|
|
82
83
|
|
83
84
|
def to_args
|
84
85
|
args = []
|
86
|
+
options = {}
|
85
87
|
|
86
88
|
if geo2d?
|
87
89
|
args << [[keys.keys.first, Mongo::GEO2D]]
|
@@ -92,11 +94,14 @@ module MongoModel
|
|
92
94
|
end
|
93
95
|
|
94
96
|
if geo2d? && @min && @max
|
95
|
-
|
96
|
-
|
97
|
-
args << { :unique => true }
|
97
|
+
options[:min] = @min
|
98
|
+
options[:max] = @max
|
98
99
|
end
|
99
100
|
|
101
|
+
options[:unique] = true if unique?
|
102
|
+
options[:name] = @name if @name
|
103
|
+
|
104
|
+
args << options if options.any?
|
100
105
|
args
|
101
106
|
end
|
102
107
|
|
@@ -102,14 +102,11 @@ module MongoModel
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def collection_name
|
105
|
-
|
106
|
-
@_collection_name || name.tableize.gsub(/\//, '.')
|
107
|
-
else
|
108
|
-
superclass.collection_name
|
109
|
-
end
|
105
|
+
@_collection_name || inferred_collection_name
|
110
106
|
end
|
111
107
|
|
112
108
|
def collection_name=(name)
|
109
|
+
@_collection = nil
|
113
110
|
@_collection_name = name
|
114
111
|
end
|
115
112
|
|
@@ -130,12 +127,21 @@ module MongoModel
|
|
130
127
|
end
|
131
128
|
|
132
129
|
def save_safely?
|
133
|
-
@_save_safely
|
130
|
+
defined?(@_save_safely) ? @_save_safely : true
|
134
131
|
end
|
135
132
|
|
136
133
|
def save_safely=(val)
|
137
134
|
@_save_safely = val
|
138
135
|
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
def inferred_collection_name
|
139
|
+
if superclass.abstract_class?
|
140
|
+
name.tableize.gsub(/\//, '.')
|
141
|
+
else
|
142
|
+
superclass.collection_name
|
143
|
+
end
|
144
|
+
end
|
139
145
|
end
|
140
146
|
|
141
147
|
private
|
@@ -40,17 +40,16 @@ module MongoModel
|
|
40
40
|
def setup!(klass)
|
41
41
|
@klass = klass
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
klass.index *[lowercase_key] + Array.wrap(options[:scope]) << { :unique => true }
|
43
|
+
unless options[:index] == false
|
44
|
+
# Create unique indexes to deal with race condition
|
45
|
+
attributes.each do |attr_name|
|
46
|
+
if options[:case_sensitive]
|
47
|
+
klass.index *[attr_name] + Array.wrap(options[:scope]) << { :unique => true }
|
48
|
+
else
|
49
|
+
lowercase_key = "_lowercase_#{attr_name}"
|
50
|
+
klass.before_save { attributes[lowercase_key] = send(attr_name).downcase }
|
51
|
+
klass.index *[lowercase_key] + Array.wrap(options[:scope]) << { :unique => true }
|
52
|
+
end
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
@@ -93,6 +92,7 @@ module MongoModel
|
|
93
92
|
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
|
94
93
|
# * <tt>:scope</tt> - One or more properties by which to limit the scope of the uniqueness constraint.
|
95
94
|
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
|
95
|
+
# * <tt>:index</tt> - If set to false, disables the unique index constraint (default is +true+).
|
96
96
|
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
|
97
97
|
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
|
98
98
|
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
@@ -27,9 +27,12 @@ module MongoModel
|
|
27
27
|
options['password']
|
28
28
|
end
|
29
29
|
|
30
|
+
def replicas
|
31
|
+
options['replicas'] || []
|
32
|
+
end
|
33
|
+
|
30
34
|
def establish_connection
|
31
|
-
@
|
32
|
-
@database = @connection.db(database)
|
35
|
+
@database = connection.db(database)
|
33
36
|
@database.authenticate(username, password) if username.present?
|
34
37
|
@database
|
35
38
|
end
|
@@ -39,8 +42,16 @@ module MongoModel
|
|
39
42
|
establish_connection
|
40
43
|
end
|
41
44
|
|
45
|
+
def connection
|
46
|
+
if replicas.any?
|
47
|
+
@connection ||= Mongo::MongoReplicaSetClient.new(replicas, connection_options)
|
48
|
+
else
|
49
|
+
@connection ||= Mongo::MongoClient.new(host, port, connection_options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
42
53
|
def connection_options
|
43
|
-
options.except('host', 'port', 'database', 'username', 'password').symbolize_keys
|
54
|
+
options.except('host', 'port', 'database', 'username', 'password', 'replicas').symbolize_keys
|
44
55
|
end
|
45
56
|
|
46
57
|
def options
|
@@ -80,9 +80,13 @@ module MongoModel
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def update_all(updates)
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
if updates.any?
|
84
|
+
selector = MongoOptions.new(klass, :conditions => finder_conditions).selector
|
85
|
+
collection.update(selector, { "$set" => updates }, { :multi => true })
|
86
|
+
reset
|
87
|
+
else
|
88
|
+
self
|
89
|
+
end
|
86
90
|
end
|
87
91
|
|
88
92
|
def update(ids, updates)
|
data/lib/mongomodel/version.rb
CHANGED
@@ -22,7 +22,7 @@ module MongoModel
|
|
22
22
|
end
|
23
23
|
define_class(:IllustratedChapter, :Chapter)
|
24
24
|
define_class(:Book, Document) do
|
25
|
-
has_many :chapters, :by => :foreign_key, :limit => 5
|
25
|
+
has_many :chapters, :by => :foreign_key, :limit => 5, :order => :id.asc
|
26
26
|
end
|
27
27
|
define_class(:NonChapter, Document)
|
28
28
|
|
@@ -96,6 +96,21 @@ module MongoModel
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
+
describe "embedded collection with :clear => true" do
|
100
|
+
define_user(EmbeddedDocument)
|
101
|
+
define_class(:Account, described_class) do
|
102
|
+
property :owners, Collection[User]
|
103
|
+
accepts_nested_attributes_for :owners, :clear => true
|
104
|
+
end
|
105
|
+
|
106
|
+
subject { Account.new(:owners => [User.new, User.new]) }
|
107
|
+
|
108
|
+
it "clears the collection first when assigning attributes" do
|
109
|
+
subject.owners_attributes = [{ :age => 18 }]
|
110
|
+
subject.owners.size.should == 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
99
114
|
describe "belongs_to association" do
|
100
115
|
define_user(Document)
|
101
116
|
define_class(:Account, described_class) do
|
@@ -175,6 +190,20 @@ module MongoModel
|
|
175
190
|
}.should raise_error(MongoModel::TooManyDocuments, "Maximum 2 documents are allowed. Got 3 documents instead.")
|
176
191
|
end
|
177
192
|
end
|
193
|
+
|
194
|
+
describe "has_many association with :clear => true" do
|
195
|
+
define_class(:Account, described_class) do
|
196
|
+
has_many :owners, :class => User
|
197
|
+
accepts_nested_attributes_for :owners, :clear => true
|
198
|
+
end
|
199
|
+
|
200
|
+
subject { Account.new(:owners => [User.new, User.new]) }
|
201
|
+
|
202
|
+
it "clears the collection first when assigning attributes" do
|
203
|
+
subject.owners_attributes = [{ :age => 18 }]
|
204
|
+
subject.owners.size.should == 1
|
205
|
+
end
|
206
|
+
end
|
178
207
|
end
|
179
208
|
end
|
180
209
|
end
|
@@ -87,46 +87,58 @@ module MongoModel
|
|
87
87
|
end
|
88
88
|
|
89
89
|
describe Index do
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
describe "#to_args" do
|
91
|
+
it "converts index with single key" do
|
92
|
+
Index.new(:title).to_args.should == [:title]
|
93
|
+
end
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
|
95
|
+
it "converts nested index with single key" do
|
96
|
+
Index.new('page.title').to_args.should == [:'page.title']
|
97
|
+
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
it "converts index with unique option" do
|
100
|
+
Index.new(:title, :unique => true).to_args.should == [:title, { :unique => true }]
|
101
|
+
end
|
102
|
+
|
103
|
+
it "converts index with name option" do
|
104
|
+
Index.new(:title, :name => "my_name").to_args.should == [:title, { :name => "my_name" }]
|
105
|
+
end
|
106
|
+
|
107
|
+
it "converts index with multiple options" do
|
108
|
+
Index.new(:title, :name => "my_name", :unique => true).to_args.should == [:title, { :name => "my_name", :unique => true }]
|
109
|
+
end
|
101
110
|
|
102
|
-
|
103
|
-
|
104
|
-
|
111
|
+
it "converts index with descending key" do
|
112
|
+
Index.new(:title => :descending).to_args.should == [[[:title, Mongo::DESCENDING]]]
|
113
|
+
end
|
105
114
|
|
106
|
-
|
107
|
-
|
108
|
-
|
115
|
+
it "converts index with multiple keys" do
|
116
|
+
Index.new(:title, :age).to_args.should == [[[:age, Mongo::ASCENDING], [:title, Mongo::ASCENDING]]]
|
117
|
+
end
|
109
118
|
|
110
|
-
|
111
|
-
|
112
|
-
|
119
|
+
it "converts index with multiple keys (ascending and descending)" do
|
120
|
+
Index.new(:title => :ascending, :age => :descending).to_args.should == [[[:age, Mongo::DESCENDING], [:title, Mongo::ASCENDING]]]
|
121
|
+
end
|
113
122
|
|
114
|
-
|
115
|
-
|
116
|
-
|
123
|
+
it "converts geospatial index with no options" do
|
124
|
+
Index.new(:position => :geo2d).to_args.should == [[[:position, Mongo::GEO2D]]]
|
125
|
+
end
|
117
126
|
|
118
|
-
|
119
|
-
|
127
|
+
it "converts geospatial index with min/max options" do
|
128
|
+
Index.new(:position => :geo2d, :min => -50, :max => 50).to_args.should == [[[:position, Mongo::GEO2D]], { :min => -50, :max => 50 }]
|
129
|
+
end
|
120
130
|
end
|
121
131
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
132
|
+
describe "equality" do
|
133
|
+
it "is equal to an equivalent index" do
|
134
|
+
Index.new(:title).should == Index.new(:title)
|
135
|
+
Index.new(:title, :age).should == Index.new(:title => :ascending, :age => :ascending)
|
136
|
+
end
|
126
137
|
|
127
|
-
|
128
|
-
|
129
|
-
|
138
|
+
it "is not equal to an non-equivalent index" do
|
139
|
+
Index.new(:title).should_not == Index.new(:age)
|
140
|
+
Index.new(:title, :age).should_not == Index.new(:title => :ascending, :age => :descending)
|
141
|
+
end
|
130
142
|
end
|
131
143
|
end
|
132
144
|
end
|
@@ -94,6 +94,15 @@ module MongoModel
|
|
94
94
|
|
95
95
|
::CustomCollectionNameSubclass.collection_name.should == 'foobar'
|
96
96
|
end
|
97
|
+
|
98
|
+
it "allows subclasses to set a custom collection name" do
|
99
|
+
class ::CustomCollectionName < Document; end
|
100
|
+
class ::CustomCollectionNameSubclass < ::CustomCollectionName
|
101
|
+
self.collection_name = 'custom'
|
102
|
+
end
|
103
|
+
|
104
|
+
::CustomCollectionNameSubclass.collection_name.should == 'custom'
|
105
|
+
end
|
97
106
|
end
|
98
107
|
|
99
108
|
describe "#collection" do
|
@@ -104,6 +113,12 @@ module MongoModel
|
|
104
113
|
it "uses the correct collection name" do
|
105
114
|
User.collection.name.should == 'users'
|
106
115
|
end
|
116
|
+
|
117
|
+
it "is updated when the collection name changes" do
|
118
|
+
collection = User.collection
|
119
|
+
User.collection_name = "custom"
|
120
|
+
User.collection.name.should == "custom"
|
121
|
+
end
|
107
122
|
end
|
108
123
|
|
109
124
|
describe "#database" do
|
@@ -31,6 +31,32 @@ module MongoModel
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
describe "indexes" do
|
35
|
+
define_class(:Article, Document) do
|
36
|
+
property :title, String
|
37
|
+
end
|
38
|
+
|
39
|
+
it "creates an index on the attribute" do
|
40
|
+
Article.should_receive(:index).with(:title, :unique => true)
|
41
|
+
Article.validates_uniqueness_of :title
|
42
|
+
end
|
43
|
+
|
44
|
+
it "creates an index on the lowercase attribute if :case_sensitive => false" do
|
45
|
+
Article.should_receive(:index).with("_lowercase_title", :unique => true)
|
46
|
+
Article.validates_uniqueness_of :title, :case_sensitive => false
|
47
|
+
end
|
48
|
+
|
49
|
+
it "creates a compound index when a scope is passed" do
|
50
|
+
Article.should_receive(:index).with(:title, :author_id, :unique => true)
|
51
|
+
Article.validates_uniqueness_of :title, :scope => :author_id
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not create an index if :index => false" do
|
55
|
+
Article.should_not_receive(:index)
|
56
|
+
Article.validates_uniqueness_of :title, :index => false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
34
60
|
describe "basic case" do
|
35
61
|
define_class(:Article, Document) do
|
36
62
|
property :title, String
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module MongoModel
|
4
|
+
describe Configuration do
|
5
|
+
it "uses a standard connection (Mongo::MongoClient) if no replicas are specified" do
|
6
|
+
Configuration.new({}).connection.should be_an_instance_of(Mongo::MongoClient)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "uses a replica set connection (Mongo::MongoReplicaSetClient) if replicas are specified" do
|
10
|
+
Configuration.new({ :replicas => ['127.0.0.1:27017'], :connect => false }).connection.should be_an_instance_of(Mongo::MongoReplicaSetClient)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongomodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Pohlenz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -259,6 +259,7 @@ files:
|
|
259
259
|
- spec/mongomodel/embedded_document_spec.rb
|
260
260
|
- spec/mongomodel/mongomodel_spec.rb
|
261
261
|
- spec/mongomodel/support/collection_spec.rb
|
262
|
+
- spec/mongomodel/support/configuration_spec.rb
|
262
263
|
- spec/mongomodel/support/map_spec.rb
|
263
264
|
- spec/mongomodel/support/mongo_operator_spec.rb
|
264
265
|
- spec/mongomodel/support/mongo_options_spec.rb
|