mongomodel 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/Appraisals CHANGED
@@ -1,8 +1,3 @@
1
- appraise "rails-3.0" do
2
- gem "activesupport", "3.0.10"
3
- gem "activemodel", "3.0.10"
4
- end
5
-
6
1
  appraise "rails-3.1" do
7
2
  gem "activesupport", "3.1.0"
8
3
  gem "activemodel", "3.1.0"
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -1,9 +1,9 @@
1
1
  PATH
2
- remote: /Users/sam/Scratch/mongomodel
2
+ remote: /Users/sam/Development/MongoDB/mongomodel
3
3
  specs:
4
- mongomodel (0.4.0)
5
- activemodel (~> 3.0)
6
- activesupport (~> 3.0)
4
+ mongomodel (0.4.1)
5
+ activemodel (~> 3.1)
6
+ activesupport (~> 3.1)
7
7
  mongo (~> 1.4)
8
8
  will_paginate (~> 2.3.15)
9
9
 
@@ -25,6 +25,10 @@ GEM
25
25
  bson_ext (1.4.0)
26
26
  builder (3.0.0)
27
27
  diff-lcs (1.1.3)
28
+ guard (0.8.6)
29
+ thor (~> 0.14.6)
30
+ guard-rspec (0.5.0)
31
+ guard (>= 0.8.4)
28
32
  i18n (0.6.0)
29
33
  mongo (1.4.0)
30
34
  bson (= 1.4.0)
@@ -38,6 +42,7 @@ GEM
38
42
  rspec-expectations (2.6.0)
39
43
  diff-lcs (~> 1.1.2)
40
44
  rspec-mocks (2.6.0)
45
+ thor (0.14.6)
41
46
  tzinfo (0.3.29)
42
47
  will_paginate (2.3.16)
43
48
 
@@ -50,6 +55,7 @@ DEPENDENCIES
50
55
  appraisal (~> 0.3.6)
51
56
  bson_ext (~> 1.4)
52
57
  bundler (>= 1.0.0)
58
+ guard-rspec (~> 0.5.0)
53
59
  mongomodel!
54
60
  rspec (~> 2.6.0)
55
61
  tzinfo
@@ -12,11 +12,11 @@ GIT
12
12
  multi_json (~> 1.0)
13
13
 
14
14
  PATH
15
- remote: /Users/sam/Scratch/mongomodel
15
+ remote: /Users/sam/Development/MongoDB/mongomodel
16
16
  specs:
17
- mongomodel (0.4.0)
18
- activemodel (~> 3.0)
19
- activesupport (~> 3.0)
17
+ mongomodel (0.4.1)
18
+ activemodel (~> 3.1)
19
+ activesupport (~> 3.1)
20
20
  mongo (~> 1.4)
21
21
  will_paginate (~> 2.3.15)
22
22
 
@@ -31,6 +31,10 @@ GEM
31
31
  bson_ext (1.4.0)
32
32
  builder (3.0.0)
33
33
  diff-lcs (1.1.2)
34
+ guard (0.8.6)
35
+ thor (~> 0.14.6)
36
+ guard-rspec (0.5.0)
37
+ guard (>= 0.8.4)
34
38
  i18n (0.6.0)
35
39
  mongo (1.4.0)
36
40
  bson (= 1.4.0)
@@ -44,6 +48,7 @@ GEM
44
48
  rspec-expectations (2.6.0)
45
49
  diff-lcs (~> 1.1.2)
46
50
  rspec-mocks (2.6.0)
51
+ thor (0.14.6)
47
52
  tzinfo (0.3.29)
48
53
  will_paginate (2.3.16)
49
54
 
@@ -56,6 +61,7 @@ DEPENDENCIES
56
61
  appraisal (~> 0.3.6)
57
62
  bson_ext (~> 1.4)
58
63
  bundler (>= 1.0.0)
64
+ guard-rspec (~> 0.5.0)
59
65
  mongomodel!
60
66
  rspec (~> 2.6.0)
61
67
  tzinfo
@@ -51,6 +51,7 @@ module MongoModel
51
51
  autoload :BeforeTypeCast, 'mongomodel/concerns/attribute_methods/before_type_cast'
52
52
  autoload :Protected, 'mongomodel/concerns/attribute_methods/protected'
53
53
  autoload :Dirty, 'mongomodel/concerns/attribute_methods/dirty'
54
+ autoload :Nested, 'mongomodel/concerns/attribute_methods/nested'
54
55
  autoload :MultiParameterAssignment, 'mongomodel/concerns/attribute_methods/multi_parameter_assignment'
55
56
  end
56
57
 
@@ -38,6 +38,10 @@ module MongoModel
38
38
  options[:polymorphic]
39
39
  end
40
40
 
41
+ def collection?
42
+ true
43
+ end
44
+
41
45
  def scope
42
46
  klass.scoped.apply_finder_options(scope_options)
43
47
  end
@@ -9,6 +9,10 @@ module MongoModel
9
9
  @type_key ||= :"#{name}_type"
10
10
  end
11
11
 
12
+ def collection?
13
+ false
14
+ end
15
+
12
16
  properties do |association|
13
17
  property association.foreign_key, MongoModel::Reference, :internal => true
14
18
  property association.type_key, String, :internal => true if association.polymorphic?
@@ -0,0 +1,103 @@
1
+ module MongoModel
2
+ module AttributeMethods
3
+ module Nested
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :nested_attributes_options, :instance_writer => false
8
+ self.nested_attributes_options = {}
9
+ end
10
+
11
+ module ClassMethods
12
+ def accepts_nested_attributes_for(*attr_names)
13
+ options = attr_names.extract_options!
14
+
15
+ attr_names.each do |attr_name|
16
+ type = property_type(attr_name)
17
+
18
+ nested_attributes_options = self.nested_attributes_options.dup
19
+ nested_attributes_options[attr_name.to_sym] = options
20
+ self.nested_attributes_options = nested_attributes_options
21
+
22
+ class_eval <<-EORUBY, __FILE__, __LINE__ + 1
23
+ if method_defined?(:#{attr_name}_attributes=)
24
+ remove_method(:#{attr_name}_attributes=)
25
+ end
26
+
27
+ def #{attr_name}_attributes=(attributes)
28
+ assign_nested_attributes_for_#{type}(:#{attr_name}, attributes)
29
+ end
30
+ EORUBY
31
+ end
32
+ end
33
+
34
+ private
35
+ def property_type(attr_name)
36
+ if property = properties[attr_name]
37
+ property.type <= Array ? :collection : :property
38
+ elsif association = associations[attr_name]
39
+ association.collection? ? :association_collection : :association
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+ def assign_nested_attributes_for_property(property, attributes)
46
+ if obj = send(property)
47
+ obj.attributes = attributes
48
+ else
49
+ send("#{property}=", attributes)
50
+ end
51
+ end
52
+
53
+ def assign_nested_attributes_for_collection(property, attributes_collection)
54
+ attributes_collection = convert_to_array(attributes_collection)
55
+ options = self.nested_attributes_options[property]
56
+
57
+ if options[:limit] && attributes_collection.size > options[:limit]
58
+ raise TooManyDocuments, "Maximum #{options[:limit]} documents are allowed. Got #{attributes_collection.size} documents instead."
59
+ end
60
+
61
+ collection = send(property)
62
+ attributes_collection.each_with_index do |attributes, index|
63
+ if collection[index]
64
+ collection[index].attributes = attributes
65
+ else
66
+ collection[index] = attributes
67
+ end
68
+ end
69
+ end
70
+
71
+ def assign_nested_attributes_for_association(association, attributes)
72
+ if obj = send(association)
73
+ obj.attributes = attributes
74
+ else
75
+ send("build_#{association}", attributes)
76
+ end
77
+ end
78
+
79
+ def assign_nested_attributes_for_association_collection(association, attributes_collection)
80
+ attributes_collection = convert_to_array(attributes_collection)
81
+ options = self.nested_attributes_options[association]
82
+
83
+ if options[:limit] && attributes_collection.size > options[:limit]
84
+ raise TooManyDocuments, "Maximum #{options[:limit]} documents are allowed. Got #{attributes_collection.size} documents instead."
85
+ end
86
+
87
+ association = send(association)
88
+ attributes_collection.each do |attributes|
89
+ association.build(attributes)
90
+ end
91
+ end
92
+
93
+ def convert_to_array(params)
94
+ case params
95
+ when Hash
96
+ params.sort.map(&:last)
97
+ else
98
+ Array(params)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -16,8 +16,12 @@ module MongoModel
16
16
  end
17
17
  end
18
18
 
19
- def attributes=(attrs)#:nodoc:
20
- super(sanitize_for_mass_assignment(attrs))
19
+ def assign_attributes(attrs, options={})
20
+ if options[:without_protection]
21
+ super
22
+ else
23
+ super(sanitize_for_mass_assignment(attrs, options[:as] || :default))
24
+ end
21
25
  end
22
26
  end
23
27
  end
@@ -13,7 +13,9 @@ module MongoModel
13
13
  @attributes ||= Attributes::Store.new(self)
14
14
  end
15
15
 
16
- def attributes=(attrs)
16
+ def assign_attributes(attrs, options={})
17
+ return unless attrs
18
+
17
19
  attrs.each do |attr, value|
18
20
  if respond_to?("#{attr}=")
19
21
  send("#{attr}=", value)
@@ -23,6 +25,10 @@ module MongoModel
23
25
  end
24
26
  end
25
27
 
28
+ def attributes=(attrs)
29
+ assign_attributes(attrs)
30
+ end
31
+
26
32
  def freeze
27
33
  attributes.freeze; self
28
34
  end
@@ -17,12 +17,8 @@ module MongoModel
17
17
  end
18
18
 
19
19
  def valid?(context=nil)
20
- errors.clear
21
-
22
- self.validation_context = new_record? ? :create : :update
23
- run_callbacks(:validate)
24
-
25
- errors.empty?
20
+ context ||= new_record? ? :create : :update
21
+ super
26
22
  end
27
23
  end
28
24
  end
@@ -1,5 +1,12 @@
1
1
  module MongoModel
2
2
  module Validations
3
+ class AssociatedValidator < ActiveModel::EachValidator
4
+ def validate_each(record, attribute, value)
5
+ return if Array(value).map { |r| r.nil? || r.valid?(record.validation_context) }.all?
6
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
7
+ end
8
+ end
9
+
3
10
  module ClassMethods
4
11
  # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
5
12
  #
@@ -33,13 +40,7 @@ module MongoModel
33
40
  # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
34
41
  # method, proc or string should return or evaluate to a true or false value.
35
42
  def validates_associated(*attr_names)
36
- configuration = attr_names.extract_options!
37
-
38
- validates_each(attr_names, configuration) do |record, attr_name, value|
39
- unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
40
- record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
41
- end
42
- end
43
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
43
44
  end
44
45
  end
45
46
  end
@@ -24,12 +24,12 @@ module MongoModel
24
24
  end
25
25
 
26
26
  # Save the document to the database. Returns +true+ on success.
27
- def save
27
+ def save(*)
28
28
  create_or_update
29
29
  end
30
30
 
31
31
  # Save the document to the database. Raises a DocumentNotSaved exception if it fails.
32
- def save!
32
+ def save!(*)
33
33
  create_or_update || raise(DocumentNotSaved)
34
34
  end
35
35
 
@@ -46,16 +46,28 @@ module MongoModel
46
46
 
47
47
  # Updates all the attributes from the passed-in Hash and saves the document.
48
48
  # If the object is invalid, the saving will fail and false will be returned.
49
- def update_attributes(attributes)
50
- self.attributes = attributes
49
+ #
50
+ # When updating model attributes, mass-assignment security protection is respected.
51
+ # If no +:as+ option is supplied then the +:default+ role will be used.
52
+ # If you want to bypass the protection given by +attr_protected+ and
53
+ # +attr_accessible+ then you can do so using the +:without_protection+ option.
54
+ def update_attributes(attributes, options={})
55
+ self.assign_attributes(attributes, options)
51
56
  save
52
57
  end
53
58
 
59
+ # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
60
+ # of +save+, so an exception is raised if the docuemnt is invalid.
61
+ def update_attributes!(attributes, options={})
62
+ self.assign_attributes(attributes, options)
63
+ save!
64
+ end
65
+
54
66
  # Updates a single attribute and saves the document without going through the normal validation procedure.
55
67
  # This is especially useful for boolean flags on existing documents.
56
68
  def update_attribute(name, value)
57
69
  send("#{name}=", value)
58
- save(false)
70
+ save(:validate => false)
59
71
  end
60
72
 
61
73
  def collection
@@ -2,12 +2,7 @@ module MongoModel
2
2
  module DocumentExtensions
3
3
  module Validations
4
4
  extend ActiveSupport::Concern
5
-
6
- included do
7
- alias_method_chain :save, :validation
8
- alias_method_chain :save!, :validation
9
- end
10
-
5
+
11
6
  module ClassMethods
12
7
  def property(name, *args, &block) #:nodoc:
13
8
  property = super
@@ -30,12 +25,12 @@ module MongoModel
30
25
  end
31
26
  end
32
27
 
33
- # The validation process on save can be skipped by passing false. The regular Document#save method is
28
+ # The validation process on save can be skipped by passing <tt>:validate => false</tt>. The regular Document#save method is
34
29
  # replaced with this when the validations module is mixed in, which it is by default.
35
- def save_with_validation(perform_validation = true)
36
- if perform_validation && valid? || !perform_validation
30
+ def save(options={})
31
+ if perform_validation(options)
37
32
  begin
38
- save_without_validation
33
+ super
39
34
  rescue DocumentNotSaved
40
35
  valid?
41
36
  false
@@ -47,17 +42,23 @@ module MongoModel
47
42
 
48
43
  # Attempts to save the document just like Document#save but will raise a DocumentInvalid exception
49
44
  # instead of returning false if the document is not valid.
50
- def save_with_validation!
51
- if valid?
45
+ def save!(options={})
46
+ if perform_validation(options)
52
47
  begin
53
- save_without_validation!
48
+ super
54
49
  rescue DocumentNotSaved => e
55
- raise valid? ? e : DocumentInvalid.new(self)
50
+ valid? ? raise : raise(DocumentInvalid.new(self))
56
51
  end
57
52
  else
58
53
  raise DocumentInvalid.new(self)
59
54
  end
60
55
  end
56
+
57
+ protected
58
+ def perform_validation(options={})
59
+ perform_validation = options != false && options[:validate] != false
60
+ perform_validation ? valid?(options[:context]) : true
61
+ end
61
62
  end
62
63
  end
63
64
  end
@@ -20,6 +20,7 @@ module MongoModel
20
20
  include AttributeMethods::BeforeTypeCast
21
21
  include AttributeMethods::Protected
22
22
  include AttributeMethods::Dirty
23
+ include AttributeMethods::Nested
23
24
  include AttributeMethods::MultiParameterAssignment
24
25
 
25
26
  include Logging
@@ -119,7 +119,7 @@ module MongoModel
119
119
  def [](type)
120
120
  @collection_class_cache ||= {}
121
121
  @collection_class_cache[type] ||= begin
122
- collection = Class.new(Collection)
122
+ collection = Class.new(self)
123
123
  collection.type = type
124
124
  collection
125
125
  end
@@ -127,8 +127,25 @@ module MongoModel
127
127
 
128
128
  alias of []
129
129
 
130
- def from_mongo(array)
131
- new(array.map { |i| instantiate(i) })
130
+ def cast(value)
131
+ case value
132
+ when Array
133
+ new(value)
134
+ when Hash
135
+ value.stringify_keys!
136
+ value['_collection'] ? cast(value['items']) : new([value])
137
+ else
138
+ new(Array(value))
139
+ end
140
+ end
141
+
142
+ def from_mongo(value)
143
+ case value
144
+ when Array
145
+ new(value.map { |i| instantiate(i) })
146
+ else
147
+ from_mongo([value])
148
+ end
132
149
  end
133
150
 
134
151
  def converter
@@ -8,23 +8,23 @@ module MongoModel
8
8
  end
9
9
 
10
10
  def host
11
- @options['host']
11
+ options['host']
12
12
  end
13
13
 
14
14
  def port
15
- @options['port']
15
+ options['port']
16
16
  end
17
17
 
18
18
  def database
19
- @options['database']
19
+ options['database']
20
20
  end
21
21
 
22
22
  def username
23
- @options['username']
23
+ options['username']
24
24
  end
25
25
 
26
26
  def password
27
- @options['password']
27
+ options['password']
28
28
  end
29
29
 
30
30
  def establish_connection
@@ -35,12 +35,16 @@ module MongoModel
35
35
  end
36
36
 
37
37
  def use_database(database)
38
- @options['database'] = database
38
+ options['database'] = database
39
39
  establish_connection
40
40
  end
41
41
 
42
42
  def connection_options
43
- @options.except('host', 'port', 'database', 'username', 'password').symbolize_keys
43
+ options.except('host', 'port', 'database', 'username', 'password').symbolize_keys
44
+ end
45
+
46
+ def options
47
+ @options ||= {}
44
48
  end
45
49
 
46
50
  def set_options!(options)
@@ -22,4 +22,6 @@ module MongoModel
22
22
  end
23
23
 
24
24
  class AssociationTypeMismatch < StandardError; end
25
+
26
+ class TooManyDocuments < StandardError; end
25
27
  end
@@ -1,3 +1,3 @@
1
1
  module MongoModel
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
@@ -14,13 +14,14 @@ Gem::Specification.new do |s|
14
14
  s.required_rubygems_version = ">= 1.3.6"
15
15
  s.rubyforge_project = "mongomodel"
16
16
 
17
- s.add_dependency "activesupport", "~> 3.0"
18
- s.add_dependency "activemodel", "~> 3.0"
17
+ s.add_dependency "activesupport", "~> 3.1"
18
+ s.add_dependency "activemodel", "~> 3.1"
19
19
  s.add_dependency "mongo", "~> 1.4"
20
20
  s.add_dependency "will_paginate", "~> 2.3.15"
21
21
 
22
- s.add_development_dependency "bundler", ">= 1.0.0"
23
- s.add_development_dependency "rspec", "~> 2.6.0"
22
+ s.add_development_dependency "bundler", ">= 1.0.0"
23
+ s.add_development_dependency "rspec", "~> 2.6.0"
24
+ s.add_development_dependency "guard-rspec", "~> 0.5.0"
24
25
 
25
26
  s.files = `git ls-files`.split("\n")
26
27
  s.require_path = 'lib'
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document, EmbeddedDocument) do
5
+ def self.define_user(type)
6
+ define_class(:User, type) do
7
+ property :name, String
8
+ property :age, Integer
9
+ end
10
+ end
11
+
12
+ describe ".accepts_nested_attributes_for" do
13
+ describe "single embedded property" do
14
+ define_user(EmbeddedDocument)
15
+ define_class(:Account, described_class) do
16
+ property :owner, User
17
+ accepts_nested_attributes_for :owner
18
+ end
19
+
20
+ subject { Account.new }
21
+
22
+ it "creates new model when property is blank" do
23
+ subject.owner_attributes = { :name => "John Smith", :age => 35 }
24
+ subject.owner.name.should == "John Smith"
25
+ subject.owner.age.should == 35
26
+ end
27
+
28
+ it "sets existing model attributes when property exists" do
29
+ subject.owner = User.new(:name => "Jane Doe")
30
+ subject.owner_attributes = { :age => 22 }
31
+ subject.owner.name.should == "Jane Doe"
32
+ subject.owner.age.should == 22
33
+ end
34
+ end
35
+
36
+ describe "embedded collection" do
37
+ define_user(EmbeddedDocument)
38
+ define_class(:Account, described_class) do
39
+ property :owners, Collection[User]
40
+ accepts_nested_attributes_for :owners
41
+ end
42
+
43
+ subject { Account.new }
44
+
45
+ it "accepts an array of hashes" do
46
+ subject.owners_attributes = [
47
+ { :name => "Fred", :age => 35 },
48
+ { :name => "Mary", :age => 22 }
49
+ ]
50
+
51
+ subject.owners[0].name.should == "Fred"
52
+ subject.owners[0].age.should == 35
53
+ subject.owners[1].name.should == "Mary"
54
+ subject.owners[1].age.should == 22
55
+ end
56
+
57
+ it "accepts a hash keyed by indexes" do
58
+ subject.owners_attributes = {
59
+ "1" => { :name => "Joe", :age => 15 },
60
+ "0" => { :name => "Peter", :age => 44 }
61
+ }
62
+
63
+ subject.owners[0].name.should == "Peter"
64
+ subject.owners[0].age.should == 44
65
+ subject.owners[1].name.should == "Joe"
66
+ subject.owners[1].age.should == 15
67
+ end
68
+
69
+ it "modifies existing collection" do
70
+ subject.owners << User.new(:name => "John")
71
+ subject.owners_attributes = [
72
+ { :age => 18 },
73
+ { :name => "Max", :age => 10 }
74
+ ]
75
+
76
+ subject.owners[0].name.should == "John"
77
+ subject.owners[0].age.should == 18
78
+ subject.owners[1].name.should == "Max"
79
+ subject.owners[1].age.should == 10
80
+ end
81
+ end
82
+
83
+ describe "embedded collection with limit" do
84
+ define_user(EmbeddedDocument)
85
+ define_class(:Account, described_class) do
86
+ property :owners, Collection[User]
87
+ accepts_nested_attributes_for :owners, :limit => 2
88
+ end
89
+
90
+ subject { Account.new }
91
+
92
+ it "raises a TooManyDocuments error if number of documents exceeds limit" do
93
+ lambda {
94
+ subject.owners_attributes = [{}, {}, {}]
95
+ }.should raise_error(MongoModel::TooManyDocuments, "Maximum 2 documents are allowed. Got 3 documents instead.")
96
+ end
97
+ end
98
+
99
+ describe "belongs_to association" do
100
+ define_user(Document)
101
+ define_class(:Account, described_class) do
102
+ belongs_to :owner, :class => User
103
+ accepts_nested_attributes_for :owner
104
+ end
105
+
106
+ subject { Account.new }
107
+
108
+ it "creates new model when property is blank" do
109
+ subject.owner_attributes = { :name => "John Smith", :age => 35 }
110
+ subject.owner.name.should == "John Smith"
111
+ subject.owner.age.should == 35
112
+ end
113
+
114
+ it "sets existing model attributes when property exists" do
115
+ subject.owner = User.new(:name => "Jane Doe")
116
+ subject.owner_attributes = { :age => 22 }
117
+ subject.owner.name.should == "Jane Doe"
118
+ subject.owner.age.should == 22
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ specs_for(Document) do
125
+ define_class(:User, Document) do
126
+ property :name, String
127
+ property :age, Integer
128
+ end
129
+
130
+ describe ".accepts_nested_attributes_for" do
131
+ describe "has_many association" do
132
+ define_class(:Account, described_class) do
133
+ has_many :owners, :class => User
134
+ accepts_nested_attributes_for :owners
135
+ end
136
+
137
+ subject { Account.new }
138
+
139
+ it "accepts an array of hashes" do
140
+ subject.owners_attributes = [
141
+ { :name => "Fred", :age => 35 },
142
+ { :name => "Mary", :age => 22 }
143
+ ]
144
+
145
+ subject.owners[0].name.should == "Fred"
146
+ subject.owners[0].age.should == 35
147
+ subject.owners[1].name.should == "Mary"
148
+ subject.owners[1].age.should == 22
149
+ end
150
+
151
+ it "accepts a hash keyed by indexes" do
152
+ subject.owners_attributes = {
153
+ "1" => { :name => "Joe", :age => 15 },
154
+ "0" => { :name => "Peter", :age => 44 }
155
+ }
156
+
157
+ subject.owners[0].name.should == "Peter"
158
+ subject.owners[0].age.should == 44
159
+ subject.owners[1].name.should == "Joe"
160
+ subject.owners[1].age.should == 15
161
+ end
162
+ end
163
+
164
+ describe "has_many association with limit" do
165
+ define_class(:Account, described_class) do
166
+ has_many :owners, :class => User
167
+ accepts_nested_attributes_for :owners, :limit => 2
168
+ end
169
+
170
+ subject { Account.new }
171
+
172
+ it "raises a TooManyDocuments error if number of documents exceeds limit" do
173
+ lambda {
174
+ subject.owners_attributes = [{}, {}, {}]
175
+ }.should raise_error(MongoModel::TooManyDocuments, "Maximum 2 documents are allowed. Got 3 documents instead.")
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -1,19 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module MongoModel
4
- module ValidationHelpers
5
- def clear_validations!
6
- reset_callbacks(:validate)
7
- end
8
- end
9
-
10
4
  specs_for(Document, EmbeddedDocument) do
11
5
  describe "validations" do
12
6
  define_class(:TestDocument, described_class) do
13
7
  property :title, String
14
8
  validates_presence_of :title
15
9
 
16
- extend MongoModel::ValidationHelpers
10
+ extend ValidationHelpers
17
11
  end
18
12
 
19
13
  if specing?(EmbeddedDocument)
@@ -84,6 +78,19 @@ module MongoModel
84
78
  it { should_not be_valid }
85
79
  end
86
80
  end
81
+
82
+ describe "validation on custom context" do
83
+ before(:each) do
84
+ TestDocument.clear_validations!
85
+ TestDocument.validates_presence_of :title, :on => :custom
86
+ end
87
+
88
+ it { should be_valid }
89
+
90
+ it "should not be valid in custom context" do
91
+ subject.valid?(:custom).should be_false
92
+ end
93
+ end
87
94
  end
88
95
 
89
96
  describe "validation shortcuts" do
@@ -111,7 +118,7 @@ module MongoModel
111
118
  property :title, String
112
119
  validates_presence_of :title
113
120
 
114
- extend MongoModel::ValidationHelpers
121
+ extend ValidationHelpers
115
122
  end
116
123
 
117
124
  define_class(:ParentDocument, Document) do
@@ -6,6 +6,8 @@ module MongoModel
6
6
  define_class(:TestDocument, Document) do
7
7
  property :title, String
8
8
  validates_presence_of :title
9
+
10
+ extend ValidationHelpers
9
11
  end
10
12
 
11
13
  context "when validations are not met" do
@@ -27,19 +29,50 @@ module MongoModel
27
29
  end
28
30
  end
29
31
 
30
- describe "#save(false)" do
32
+ shared_examples_for "saving without validation" do
31
33
  it "should not validate the document" do
32
34
  subject.should_not_receive(:valid?)
33
- subject.save(false)
35
+ save
34
36
  end
35
37
 
36
38
  it "should save the document" do
37
- subject.should_receive(:save_without_validation).and_return(true)
38
- subject.save(false)
39
+ subject.should_receive(:create_or_update).and_return(true)
40
+ save
39
41
  end
40
42
 
41
43
  it "should return true" do
42
- subject.save(false).should be_true
44
+ save.should be_true
45
+ end
46
+ end
47
+
48
+ describe "#save(false) [deprecated save without validations]" do
49
+ def save
50
+ subject.save(false)
51
+ end
52
+
53
+ it_should_behave_like "saving without validation"
54
+ end
55
+
56
+ describe "#save(:validate => false)" do
57
+ def save
58
+ subject.save(:validate => false)
59
+ end
60
+
61
+ it_should_behave_like "saving without validation"
62
+ end
63
+
64
+ describe "#save(:context => :custom)" do
65
+ before(:each) do
66
+ TestDocument.clear_validations!
67
+ TestDocument.validates_presence_of :title, :on => :custom
68
+ end
69
+
70
+ it "should save in default context" do
71
+ subject.save.should be_true
72
+ end
73
+
74
+ it "should not save in custom context" do
75
+ subject.save(:context => :custom).should be_false
43
76
  end
44
77
  end
45
78
  end
@@ -194,6 +194,12 @@ module MongoModel
194
194
  collection.should be_a(subject)
195
195
  collection.should == [CustomClass.new("abc"), CustomClass.new("123")]
196
196
  end
197
+
198
+ it "should load from mongo representation of single item" do
199
+ collection = subject.from_mongo({ :name => "abc" })
200
+ collection.should be_a(subject)
201
+ collection.should == [CustomClass.new("abc")]
202
+ end
197
203
  end
198
204
  end
199
205
 
@@ -249,6 +255,21 @@ module MongoModel
249
255
  it "should include the elements in the collection in the embedded documents list" do
250
256
  subject.embedded_documents.should include(embedded2, embedded3)
251
257
  end
258
+
259
+ it "should cast items to embedded document class when assigning array of hashes" do
260
+ subject.embedded_collection = [ { :number => 5 }, { :number => 99 } ]
261
+ subject.embedded_collection.should == [ Embedded.new(:number => 5), Embedded.new(:number => 99) ]
262
+ end
263
+
264
+ it "should cast items to embedded document class when assigning collection hash" do
265
+ subject.embedded_collection = { :_collection => true, :items => [ { :number => 49 }, { :number => 64 } ] }
266
+ subject.embedded_collection.should == [ Embedded.new(:number => 49), Embedded.new(:number => 64) ]
267
+ end
268
+
269
+ it "should cast item to embedded document class when assigning attributes hash" do
270
+ subject.embedded_collection = { :number => 8 }
271
+ subject.embedded_collection.should == [ Embedded.new(:number => 8) ]
272
+ end
252
273
  end
253
274
  end
254
275
  end
@@ -0,0 +1,5 @@
1
+ module ValidationHelpers
2
+ def clear_validations!
3
+ reset_callbacks(:validate)
4
+ end
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongomodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,33 +9,33 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-26 00:00:00.000000000Z
12
+ date: 2011-10-21 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &2156338320 !ruby/object:Gem::Requirement
16
+ requirement: &70171471424760 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '3.0'
21
+ version: '3.1'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2156338320
24
+ version_requirements: *70171471424760
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activemodel
27
- requirement: &2156336540 !ruby/object:Gem::Requirement
27
+ requirement: &70171471423920 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
31
31
  - !ruby/object:Gem::Version
32
- version: '3.0'
32
+ version: '3.1'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2156336540
35
+ version_requirements: *70171471423920
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: mongo
38
- requirement: &2156334360 !ruby/object:Gem::Requirement
38
+ requirement: &70171471423300 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '1.4'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *2156334360
46
+ version_requirements: *70171471423300
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: will_paginate
49
- requirement: &2156331960 !ruby/object:Gem::Requirement
49
+ requirement: &70171471422520 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 2.3.15
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *2156331960
57
+ version_requirements: *70171471422520
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: bundler
60
- requirement: &2156329880 !ruby/object:Gem::Requirement
60
+ requirement: &70171471421740 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.0.0
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2156329880
68
+ version_requirements: *70171471421740
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
- requirement: &2156294220 !ruby/object:Gem::Requirement
71
+ requirement: &70171471420620 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,7 +76,18 @@ dependencies:
76
76
  version: 2.6.0
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2156294220
79
+ version_requirements: *70171471420620
80
+ - !ruby/object:Gem::Dependency
81
+ name: guard-rspec
82
+ requirement: &70171471419380 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: 0.5.0
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70171471419380
80
91
  description: MongoModel is a MongoDB ORM for Ruby/Rails similar to ActiveRecord and
81
92
  DataMapper.
82
93
  email:
@@ -88,13 +99,11 @@ files:
88
99
  - .gitignore
89
100
  - Appraisals
90
101
  - Gemfile
102
+ - Guardfile
91
103
  - LICENSE
92
104
  - README.md
93
105
  - Rakefile
94
- - autotest/discover.rb
95
106
  - bin/console
96
- - gemfiles/rails-3.0.gemfile
97
- - gemfiles/rails-3.0.gemfile.lock
98
107
  - gemfiles/rails-3.1-edge.gemfile
99
108
  - gemfiles/rails-3.1-edge.gemfile.lock
100
109
  - gemfiles/rails-3.1-latest.gemfile
@@ -120,6 +129,7 @@ files:
120
129
  - lib/mongomodel/concerns/attribute_methods/before_type_cast.rb
121
130
  - lib/mongomodel/concerns/attribute_methods/dirty.rb
122
131
  - lib/mongomodel/concerns/attribute_methods/multi_parameter_assignment.rb
132
+ - lib/mongomodel/concerns/attribute_methods/nested.rb
123
133
  - lib/mongomodel/concerns/attribute_methods/protected.rb
124
134
  - lib/mongomodel/concerns/attribute_methods/query.rb
125
135
  - lib/mongomodel/concerns/attribute_methods/read.rb
@@ -200,6 +210,7 @@ files:
200
210
  - spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb
201
211
  - spec/mongomodel/concerns/attribute_methods/dirty_spec.rb
202
212
  - spec/mongomodel/concerns/attribute_methods/multi_parameter_assignment_spec.rb
213
+ - spec/mongomodel/concerns/attribute_methods/nested_spec.rb
203
214
  - spec/mongomodel/concerns/attribute_methods/protected_spec.rb
204
215
  - spec/mongomodel/concerns/attribute_methods/query_spec.rb
205
216
  - spec/mongomodel/concerns/attribute_methods/read_spec.rb
@@ -240,6 +251,7 @@ files:
240
251
  - spec/support/helpers/define_class.rb
241
252
  - spec/support/helpers/document_finder_stubs.rb
242
253
  - spec/support/helpers/specs_for.rb
254
+ - spec/support/helpers/validations.rb
243
255
  - spec/support/matchers/be_a_subclass_of.rb
244
256
  - spec/support/matchers/be_truthy.rb
245
257
  - spec/support/matchers/find_with.rb
@@ -260,7 +272,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
260
272
  version: '0'
261
273
  segments:
262
274
  - 0
263
- hash: 1437842966940713292
275
+ hash: 3076200504632961352
264
276
  required_rubygems_version: !ruby/object:Gem::Requirement
265
277
  none: false
266
278
  requirements:
@@ -269,7 +281,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
281
  version: 1.3.6
270
282
  requirements: []
271
283
  rubyforge_project: mongomodel
272
- rubygems_version: 1.8.10
284
+ rubygems_version: 1.8.6
273
285
  signing_key:
274
286
  specification_version: 3
275
287
  summary: MongoDB ORM for Ruby/Rails
@@ -1 +0,0 @@
1
- Autotest.add_discovery { "rspec" }
@@ -1,11 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "http://rubygems.org"
4
-
5
- gem "appraisal", "~> 0.3.6"
6
- gem "bson_ext", "~> 1.4"
7
- gem "tzinfo"
8
- gem "activesupport", "3.0.10"
9
- gem "activemodel", "3.0.10"
10
-
11
- gemspec :path=>"../"
@@ -1,75 +0,0 @@
1
- PATH
2
- remote: /Users/sam/Scratch/mongomodel
3
- specs:
4
- mongomodel (0.4.0)
5
- activemodel (~> 3.0)
6
- activesupport (~> 3.0)
7
- mongo (~> 1.4)
8
- will_paginate (~> 2.3.15)
9
-
10
- GEM
11
- remote: http://rubygems.org/
12
- specs:
13
- activemodel (3.0.10)
14
- activesupport (= 3.0.10)
15
- builder (~> 2.1.2)
16
- i18n (~> 0.5.0)
17
- activesupport (3.0.10)
18
- appraisal (0.3.6)
19
- aruba (~> 0.4.2)
20
- bundler
21
- rake
22
- aruba (0.4.3)
23
- bcat (>= 0.6.1)
24
- childprocess (>= 0.1.9)
25
- cucumber (>= 0.10.7)
26
- rdiscount (>= 1.6.8)
27
- rspec (>= 2.6.0)
28
- bcat (0.6.1)
29
- rack (~> 1.0)
30
- bson (1.4.0)
31
- bson_ext (1.4.0)
32
- builder (2.1.2)
33
- childprocess (0.1.9)
34
- ffi (~> 1.0.6)
35
- cucumber (1.0.0)
36
- builder (>= 2.1.2)
37
- diff-lcs (>= 1.1.2)
38
- gherkin (~> 2.4.1)
39
- json (>= 1.4.6)
40
- term-ansicolor (>= 1.0.5)
41
- diff-lcs (1.1.2)
42
- ffi (1.0.9)
43
- gherkin (2.4.1)
44
- json (>= 1.4.6)
45
- i18n (0.5.0)
46
- json (1.5.3)
47
- mongo (1.4.0)
48
- bson (= 1.4.0)
49
- rack (1.3.0)
50
- rake (0.9.2)
51
- rdiscount (1.6.8)
52
- rspec (2.6.0)
53
- rspec-core (~> 2.6.0)
54
- rspec-expectations (~> 2.6.0)
55
- rspec-mocks (~> 2.6.0)
56
- rspec-core (2.6.4)
57
- rspec-expectations (2.6.0)
58
- diff-lcs (~> 1.1.2)
59
- rspec-mocks (2.6.0)
60
- term-ansicolor (1.0.5)
61
- tzinfo (0.3.29)
62
- will_paginate (2.3.16)
63
-
64
- PLATFORMS
65
- ruby
66
-
67
- DEPENDENCIES
68
- activemodel (= 3.0.10)
69
- activesupport (= 3.0.10)
70
- appraisal (~> 0.3.6)
71
- bson_ext (~> 1.4)
72
- bundler (>= 1.0.0)
73
- mongomodel!
74
- rspec (~> 2.6.0)
75
- tzinfo