openlogic-couchrest_model 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,6 @@
1
+ class Client < CouchRest::Model::Base
2
+ use_database DB
3
+
4
+ property :name
5
+ property :tax_code
6
+ end
@@ -0,0 +1,27 @@
1
+ require 'question'
2
+ require 'person'
3
+
4
+ class Course < CouchRest::Model::Base
5
+ use_database TEST_SERVER.default_database
6
+
7
+ property :title, String
8
+ property :questions, [Question]
9
+ property :professor, Person
10
+ property :participants, [Object]
11
+ property :ends_at, Time
12
+ property :estimate, Float
13
+ property :hours, Integer
14
+ property :profit, BigDecimal
15
+ property :started_on, :type => Date
16
+ property :updated_at, DateTime
17
+ property :active, :type => TrueClass
18
+ property :very_active, :type => TrueClass
19
+ property :klass, :type => Class
20
+
21
+ view_by :title
22
+ view_by :title, :active
23
+ view_by :dept, :ducktype => true
24
+
25
+ view_by :active, :map => "function(d) { if (d['#{model_type_key}'] == 'Course' && d['active']) { emit(d['updated_at'], 1); }}", :reduce => "function(k,v,r) { return sum(v); }"
26
+
27
+ end
@@ -0,0 +1,8 @@
1
+ class Event < CouchRest::Model::Base
2
+ use_database DB
3
+
4
+ property :subject
5
+ property :occurs_at, Time, :init_method => 'parse'
6
+ property :end_date, Date, :init_method => 'parse'
7
+
8
+ end
@@ -0,0 +1,14 @@
1
+ class Invoice < CouchRest::Model::Base
2
+ # Set the default database to use
3
+ use_database DB
4
+
5
+ # Official Schema
6
+ property :client_name
7
+ property :employee_name
8
+ property :location
9
+
10
+ # Validation
11
+ validates_presence_of :client_name, :employee_name
12
+ validates_presence_of :location, :message => "Hey stupid!, you forgot the location"
13
+
14
+ end
@@ -0,0 +1,5 @@
1
+ class KeyChain < CouchRest::Model::Base
2
+ use_database(DB)
3
+
4
+ property(:keys, Hash)
5
+ end
@@ -0,0 +1,4 @@
1
+ class Membership
2
+ include CouchRest::Model::Embeddable
3
+
4
+ end
@@ -0,0 +1,11 @@
1
+ require 'cat'
2
+
3
+ class Person
4
+ include ::CouchRest::Model::Embeddable
5
+ property :pet, Cat
6
+ property :name, [String]
7
+
8
+ def last_name
9
+ name.last
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ class Project < CouchRest::Model::Base
2
+ use_database DB
3
+ property :name, String
4
+ timestamps!
5
+ view_by :name
6
+ end
@@ -0,0 +1,7 @@
1
+ class Question
2
+ include ::CouchRest::Model::Embeddable
3
+
4
+ property :q
5
+ property :a
6
+
7
+ end
@@ -0,0 +1,9 @@
1
+ class SaleEntry < CouchRest::Model::Base
2
+ use_database DB
3
+
4
+ property :description
5
+ property :price
6
+
7
+ view_by :description
8
+
9
+ end
@@ -0,0 +1,14 @@
1
+ require 'client'
2
+ require 'sale_entry'
3
+
4
+ class SaleInvoice < CouchRest::Model::Base
5
+ use_database DB
6
+
7
+ belongs_to :client
8
+ belongs_to :alternate_client, :class_name => 'Client', :foreign_key => 'alt_client_id'
9
+
10
+ collection_of :entries, :class_name => 'SaleEntry'
11
+
12
+ property :date, Date
13
+ property :price, Integer
14
+ end
@@ -0,0 +1,10 @@
1
+ class Service < CouchRest::Model::Base
2
+ # Set the default database to use
3
+ use_database DB
4
+
5
+ # Official Schema
6
+ property :name
7
+ property :price, Integer
8
+
9
+ validates_length_of :name, :minimum => 4, :maximum => 20
10
+ end
@@ -0,0 +1,22 @@
1
+ class User < CouchRest::Model::Base
2
+ # Set the default database to use
3
+ use_database DB
4
+ property :name, :accessible => true
5
+ property :admin # this will be automatically protected
6
+ end
7
+
8
+ class SpecialUser < CouchRest::Model::Base
9
+ # Set the default database to use
10
+ use_database DB
11
+ property :name # this will not be protected
12
+ property :admin, :protected => true
13
+ end
14
+
15
+ # There are two modes of protection
16
+ # 1) Declare accessible poperties, assume all the rest are protected
17
+ # property :name, :accessible => true
18
+ # property :admin # this will be automatically protected
19
+ #
20
+ # 2) Declare protected properties, assume all the rest are accessible
21
+ # property :name # this will not be protected
22
+ # property :admin, :protected => true
@@ -0,0 +1,3 @@
1
+ function globalLib() {
2
+ return "fixture";
3
+ };
@@ -0,0 +1,3 @@
1
+ function justThisView() {
2
+ return "fixture";
3
+ };
@@ -0,0 +1,4 @@
1
+ function(doc) {
2
+ //include-lib
3
+ emit(null, null);
4
+ };
@@ -0,0 +1,3 @@
1
+ function(doc) {
2
+ emit(null, null);
3
+ };
@@ -0,0 +1,3 @@
1
+ function(ks,vs,co) {
2
+ return vs.length;
3
+ };
@@ -0,0 +1,8 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe CouchRest::Model::Validations do
4
+
5
+ let(:invoice) do
6
+ Invoice.new()
7
+ end
8
+ end
@@ -0,0 +1,60 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require "bundler/setup"
5
+ require "rubygems"
6
+ require "rspec"
7
+
8
+ require 'couchrest_model'
9
+
10
+ unless defined?(FIXTURE_PATH)
11
+ MODEL_PATH = File.join(File.dirname(__FILE__), "fixtures", "models")
12
+ $LOAD_PATH.unshift(MODEL_PATH)
13
+
14
+ FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures')
15
+ SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp')
16
+
17
+ COUCHHOST = "http://127.0.0.1:5984"
18
+ TESTDB = 'couchrest-model-test'
19
+ TEST_SERVER = CouchRest.new COUCHHOST
20
+ TEST_SERVER.default_database = TESTDB
21
+ DB = TEST_SERVER.database(TESTDB)
22
+ end
23
+
24
+ RSpec.configure do |config|
25
+ config.before(:all) { reset_test_db! }
26
+
27
+ config.after(:all) do
28
+ cr = TEST_SERVER
29
+ test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
30
+ test_dbs.each do |db|
31
+ cr.database(db).delete! rescue nil
32
+ end
33
+ end
34
+ end
35
+
36
+ # Require each of the fixture models
37
+ Dir[ File.join(MODEL_PATH, "*.rb") ].sort.each { |file| require File.basename(file) }
38
+
39
+ class Basic < CouchRest::Model::Base
40
+ use_database TEST_SERVER.default_database
41
+ end
42
+
43
+ def reset_test_db!
44
+ DB.recreate! rescue nil
45
+ # Reset the Design Cache
46
+ Thread.current[:couchrest_design_cache] = {}
47
+ DB
48
+ end
49
+
50
+
51
+ def couchdb_lucene_available?
52
+ lucene_path = "http://localhost:5985/"
53
+ url = URI.parse(lucene_path)
54
+ req = Net::HTTP::Get.new(url.path)
55
+ res = Net::HTTP.new(url.host, url.port).start { |http| http.request(req) }
56
+ true
57
+ rescue Exception => e
58
+ false
59
+ end
60
+
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'test/unit/assertions'
4
+ require 'active_model/lint'
5
+
6
+ class CompliantModel < CouchRest::Model::Base
7
+ end
8
+
9
+
10
+ describe CouchRest::Model::Base do
11
+ include Test::Unit::Assertions
12
+ include ActiveModel::Lint::Tests
13
+
14
+ before :each do
15
+ @model = CompliantModel.new
16
+ end
17
+
18
+ describe "active model lint tests" do
19
+ ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
20
+ example m.gsub('_',' ') do
21
+ send m
22
+ end
23
+ end
24
+ end
25
+
26
+ def model
27
+ @model
28
+ end
29
+
30
+ end
@@ -0,0 +1,242 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe "Assocations" do
5
+
6
+ describe ".merge_belongs_to_association_options" do
7
+ before :all do
8
+ def SaleInvoice.merge_assoc_opts(*args)
9
+ merge_belongs_to_association_options(*args)
10
+ end
11
+ end
12
+
13
+ it "should return a default set of options" do
14
+ o = SaleInvoice.merge_assoc_opts(:cat)
15
+ o[:foreign_key].should eql('cat_id')
16
+ o[:class_name].should eql('Cat')
17
+ o[:proxy_name].should eql('cats')
18
+ o[:proxy].should eql('Cat') # same as class name
19
+ end
20
+
21
+ it "should merge with provided options" do
22
+ o = SaleInvoice.merge_assoc_opts(:cat, :foreign_key => 'somecat_id', :proxy => 'some_cats')
23
+ o[:foreign_key].should eql('somecat_id')
24
+ o[:proxy].should eql('some_cats')
25
+ end
26
+
27
+ it "should generate a proxy string if proxied" do
28
+ SaleInvoice.stub!(:proxy_owner_method).twice.and_return('company')
29
+ o = SaleInvoice.merge_assoc_opts(:cat)
30
+ o[:proxy].should eql('self.company.cats')
31
+ end
32
+
33
+ end
34
+
35
+ describe "of type belongs to" do
36
+
37
+ before :each do
38
+ @invoice = SaleInvoice.create(:price => 2000)
39
+ @client = Client.create(:name => "Sam Lown")
40
+ end
41
+
42
+ it "should create a foreign key property with setter and getter" do
43
+ @invoice.properties.find{|p| p.name == 'client_id'}.should_not be_nil
44
+ @invoice.respond_to?(:client_id).should be_true
45
+ @invoice.respond_to?("client_id=").should be_true
46
+ end
47
+
48
+ it "should set the property and provide object when set" do
49
+ @invoice.client = @client
50
+ @invoice.client_id.should eql(@client.id)
51
+ @invoice.client.should eql(@client)
52
+ end
53
+
54
+ it "should set the attribute, save and return" do
55
+ @invoice.client = @client
56
+ @invoice.save
57
+ @invoice = SaleInvoice.get(@invoice.id)
58
+ @invoice.client.id.should eql(@client.id)
59
+ end
60
+
61
+ it "should remove the association if nil is provided" do
62
+ @invoice.client = @client
63
+ @invoice.client = nil
64
+ @invoice.client_id.should be_nil
65
+ end
66
+
67
+ it "should not try to search for association if foreign_key is nil" do
68
+ @invoice.client_id = nil
69
+ Client.should_not_receive(:get)
70
+ @invoice.client
71
+ end
72
+
73
+ it "should allow override of foreign key" do
74
+ @invoice.respond_to?(:alternate_client).should be_true
75
+ @invoice.respond_to?("alternate_client=").should be_true
76
+ @invoice.properties.find{|p| p.name == 'alt_client_id'}.should_not be_nil
77
+ end
78
+
79
+ it "should allow override of foreign key and save" do
80
+ @invoice.alternate_client = @client
81
+ @invoice.save
82
+ @invoice = SaleInvoice.get(@invoice.id)
83
+ @invoice.alternate_client.id.should eql(@client.id)
84
+ end
85
+
86
+ end
87
+
88
+ describe "of type collection_of" do
89
+
90
+ before(:each) do
91
+ @invoice = SaleInvoice.create(:price => 2000)
92
+ @entries = [
93
+ SaleEntry.create(:description => 'test line 1', :price => 500),
94
+ SaleEntry.create(:description => 'test line 2', :price => 500),
95
+ SaleEntry.create(:description => 'test line 3', :price => 1000)
96
+ ]
97
+ end
98
+
99
+ it "should create an associated property and collection proxy" do
100
+ @invoice.respond_to?('entry_ids').should be_true
101
+ @invoice.respond_to?('entry_ids=').should be_true
102
+ @invoice.entries.class.should eql(::CouchRest::Model::CollectionOfProxy)
103
+ end
104
+
105
+ it "should allow replacement of objects" do
106
+ @invoice.entries = @entries
107
+ @invoice.entries.length.should eql(3)
108
+ @invoice.entry_ids.length.should eql(3)
109
+ @invoice.entries.first.should eql(@entries.first)
110
+ @invoice.entry_ids.first.should eql(@entries.first.id)
111
+ end
112
+
113
+ it "should allow ids to be set directly and load entries" do
114
+ @invoice.entry_ids = @entries.collect{|i| i.id}
115
+ @invoice.entries.length.should eql(3)
116
+ @invoice.entries.first.should eql(@entries.first)
117
+ end
118
+
119
+ it "should replace collection if ids replaced" do
120
+ @invoice.entry_ids = @entries.collect{|i| i.id}
121
+ @invoice.entries.length.should eql(3) # load once
122
+ @invoice.entry_ids = @entries[0..1].collect{|i| i.id}
123
+ @invoice.entries.length.should eql(2)
124
+ end
125
+
126
+ it "should allow forced collection update if ids changed" do
127
+ @invoice.entry_ids = @entries[0..1].collect{|i| i.id}
128
+ @invoice.entries.length.should eql(2) # load once
129
+ @invoice.entry_ids << @entries[2].id
130
+ @invoice.entry_ids.length.should eql(3)
131
+ @invoice.entries.length.should eql(2) # cached!
132
+ @invoice.entries(true).length.should eql(3)
133
+ end
134
+
135
+ it "should empty arrays when nil collection provided" do
136
+ @invoice.entries = @entries
137
+ @invoice.entries = nil
138
+ @invoice.entry_ids.should be_empty
139
+ @invoice.entries.should be_empty
140
+ end
141
+
142
+ it "should empty arrays when nil ids array provided" do
143
+ @invoice.entries = @entries
144
+ @invoice.entry_ids = nil
145
+ @invoice.entry_ids.should be_empty
146
+ @invoice.entries.should be_empty
147
+ end
148
+
149
+ it "should ignore nil entries" do
150
+ @invoice.entries = [ nil ]
151
+ @invoice.entry_ids.should be_empty
152
+ @invoice.entries.should be_empty
153
+ end
154
+
155
+ # Account for dirty tracking
156
+ describe "dirty tracking" do
157
+ it "should register changes on push" do
158
+ @invoice.changed?.should be_false
159
+ @invoice.entries << @entries[0]
160
+ @invoice.changed?.should be_true
161
+ end
162
+ it "should register changes on pop" do
163
+ @invoice.entries << @entries[0]
164
+ @invoice.save
165
+ @invoice.changed?.should be_false
166
+ @invoice.entries.pop
167
+ @invoice.changed?.should be_true
168
+ end
169
+ it "should register id changes on push" do
170
+ @invoice.entry_ids << @entries[0].id
171
+ @invoice.changed?.should be_true
172
+ end
173
+ it "should register id changes on pop" do
174
+ @invoice.entry_ids << @entries[0].id
175
+ @invoice.save
176
+ @invoice.changed?.should be_false
177
+ @invoice.entry_ids.pop
178
+ @invoice.changed?.should be_true
179
+ end
180
+ end
181
+
182
+ describe "proxy" do
183
+
184
+ it "should ensure new entries to proxy are matched" do
185
+ @invoice.entries << @entries.first
186
+ @invoice.entry_ids.first.should eql(@entries.first.id)
187
+ @invoice.entries.first.should eql(@entries.first)
188
+ @invoice.entries << @entries[1]
189
+ @invoice.entries.count.should eql(2)
190
+ @invoice.entry_ids.count.should eql(2)
191
+ @invoice.entry_ids.last.should eql(@entries[1].id)
192
+ @invoice.entries.last.should eql(@entries[1])
193
+ end
194
+
195
+ it "should support push method" do
196
+ @invoice.entries.push(@entries.first)
197
+ @invoice.entry_ids.first.should eql(@entries.first.id)
198
+ end
199
+
200
+ it "should support []= method" do
201
+ @invoice.entries[0] = @entries.first
202
+ @invoice.entry_ids.first.should eql(@entries.first.id)
203
+ end
204
+
205
+ it "should support unshift method" do
206
+ @invoice.entries.unshift(@entries.first)
207
+ @invoice.entry_ids.first.should eql(@entries.first.id)
208
+ @invoice.entries.unshift(@entries[1])
209
+ @invoice.entry_ids.first.should eql(@entries[1].id)
210
+ end
211
+
212
+ it "should support pop method" do
213
+ @invoice.entries.push(@entries.first)
214
+ @invoice.entries.pop.should eql(@entries.first)
215
+ @invoice.entries.empty?.should be_true
216
+ @invoice.entry_ids.empty?.should be_true
217
+ end
218
+
219
+ it "should support shift method" do
220
+ @invoice.entries.push(@entries[0])
221
+ @invoice.entries.push(@entries[1])
222
+ @invoice.entries.shift.should eql(@entries[0])
223
+ @invoice.entries.first.should eql(@entries[1])
224
+ @invoice.entry_ids.first.should eql(@entries[1].id)
225
+ end
226
+
227
+ it "should raise error when adding un-persisted entries" do
228
+ SaleEntry.find_by_description('test entry').should be_nil
229
+ entry = SaleEntry.new(:description => 'test entry', :price => 500)
230
+ lambda {
231
+ @invoice.entries << entry
232
+ }.should raise_error
233
+ # In the future maybe?
234
+ # @invoice.save.should be_true
235
+ # SaleEntry.find_by_description('test entry').should_not be_nil
236
+ end
237
+
238
+ end
239
+
240
+ end
241
+
242
+ end