mongomodel 0.4.1 → 0.4.2

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.
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