ripple 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -102,7 +102,7 @@ module Ripple
102
102
 
103
103
  def attributes_from_property_defaults
104
104
  self.class.properties.values.inject({}) do |hash, prop|
105
- hash[prop.key] = prop.default if prop.default
105
+ hash[prop.key] = prop.default unless prop.default.nil?
106
106
  hash
107
107
  end.with_indifferent_access
108
108
  end
@@ -29,6 +29,9 @@ module Ripple
29
29
  def key=(value)
30
30
  self.#{prop} = value
31
31
  end
32
+ def key_attr
33
+ :#{prop}
34
+ end
32
35
  CODE
33
36
  end
34
37
  end
@@ -43,6 +46,10 @@ module Ripple
43
46
  def key=(value)
44
47
  @key = value.to_s
45
48
  end
49
+
50
+ def key_attr
51
+ :key
52
+ end
46
53
  end
47
54
  end
48
55
  end
@@ -61,6 +61,7 @@ module Ripple
61
61
  include Ripple::Conversion
62
62
  include Ripple::Document::Finders
63
63
  include Ripple::Inspection
64
+ include Ripple::NestedAttributes
64
65
  end
65
66
 
66
67
  module ClassMethods
@@ -37,6 +37,7 @@ module Ripple
37
37
  include Ripple::Conversion
38
38
  include Finders
39
39
  include Ripple::Inspection
40
+ include Ripple::NestedAttributes
40
41
  end
41
42
 
42
43
  module ClassMethods
@@ -0,0 +1,263 @@
1
+ require 'ripple'
2
+ module Ripple
3
+ module NestedAttributes #:nodoc:
4
+ extend ActiveSupport::Concern
5
+
6
+ UNASSIGNABLE_KEYS = %w{ _destroy }
7
+ TRUE_VALUES = [ true, "true", 1, "1", "yes", "ok", "y" ]
8
+
9
+ included do
10
+ class_inheritable_accessor :nested_attributes_options, :instance_writer => false
11
+ self.nested_attributes_options = {}
12
+ end
13
+
14
+ # = Nested Attributes
15
+ #
16
+ # This is similar to the `accepts_nested_attributes` functionality
17
+ # as found in AR. This allows the use update attributes and create
18
+ # new child records through the parent. It also allows the use of
19
+ # the `fields_for` form view helper, using a presenter pattern.
20
+ #
21
+ # To enable in the model, call the class method, using the same
22
+ # relationship as defined in the `one` or `many`.
23
+ #
24
+ # class Shipment
25
+ # include Ripple::Document
26
+ # one :box
27
+ # many :addresses
28
+ # accepts_nested_attributes_for :box, :addresses
29
+ # end
30
+ #
31
+ # == One
32
+ #
33
+ # Given this model:
34
+ #
35
+ # class Shipment
36
+ # include Ripple::Document
37
+ # one :box
38
+ # accepts_nested_attributes_for :box
39
+ # end
40
+ #
41
+ # This allows creating a box child during creation:
42
+ #
43
+ # shipment = Shipment.create(:box_attributes => { :shape => 'square' })
44
+ # shipment.box.shape # => 'square'
45
+ #
46
+ # This also allows updating box attributes:
47
+ #
48
+ # shipment.update_attributes(:box_attributes => { :key => 'xxx', :shape => 'triangle' })
49
+ # shipment.box.shape # => 'triangle'
50
+ #
51
+ # == Many
52
+ #
53
+ # Given this model
54
+ #
55
+ # class Manifest
56
+ # include Ripple::Document
57
+ # many :shipments
58
+ # accepts_nested_attributes_for :shipments
59
+ # end
60
+ #
61
+ # This allows creating several shipments during manifest creation:
62
+ #
63
+ # manifest = Manifest.create(:shipments_attributes => [ { :reference => "foo1" }, { :reference => "foo2" } ])
64
+ # manifest.shipments.size # => 2
65
+ # manifest.shipments.first.reference # => foo1
66
+ # manifest.shipments.second.reference # => foo2
67
+ #
68
+ # And updating shipment attributes:
69
+ #
70
+ # manifest.update_attributes(:shipment_attributes => [ { :key => 'xxx', :reference => 'updated foo1' },
71
+ # { :key => 'yyy', :reference => 'updated foo2' } ])
72
+ # manifest.shipments.first.reference # => updated foo1
73
+ # manifest.shipments.second.reference # => updated foo2
74
+ #
75
+ # NOTE: On many embedded, then entire collection of embedded documents is replaced, as there
76
+ # is no key to specifically update.
77
+ #
78
+ # Given
79
+ #
80
+ # class Manifest
81
+ # include Ripple::Documnet
82
+ # many :signatures
83
+ # accepts_nested_attributes_for :signatures
84
+ # end
85
+ #
86
+ # class Signature
87
+ # include Ripple::EmbeddedDocument
88
+ # property :esignature, String
89
+ # end
90
+ #
91
+ # The assigning of attributes replaces existing:
92
+ #
93
+ # manifest = Manifest.create(:signature_attributes => [ { :esig => 'a00001' }, { :esig => 'b00001' } ]
94
+ # manifest.signatures # => [<Signature esig="a00001">, <Signature esig="b00001">]
95
+ #
96
+ # manifest.signature_attributes = [ { :esig => 'c00001' } ]
97
+ # manifest.signatures # => [<Signature esig="c00001">]
98
+ #
99
+ module ClassMethods
100
+
101
+ def accepts_nested_attributes_for(*attr_names)
102
+ options = { :allow_destroy => false }
103
+ options.update(attr_names.extract_options!)
104
+
105
+ attr_names.each do |association_name|
106
+ if association = self.associations[association_name]
107
+ nested_attributes_options[association_name.to_sym] = options
108
+
109
+ class_eval %{
110
+ def #{association_name}_attributes=(attributes)
111
+ assign_nested_attributes_for_#{association.type}_association(:#{association_name}, attributes)
112
+ end
113
+
114
+ before_save :autosave_nested_attributes_for_#{association_name}
115
+ before_save :destroy_marked_for_destruction
116
+
117
+ private
118
+
119
+ def autosave_nested_attributes_for_#{association_name}
120
+ save_nested_attributes_for_#{association.type}_association(:#{association_name}) if self.autosave[:#{association_name}]
121
+ end
122
+ }, __FILE__, __LINE__
123
+ else
124
+ raise ArgumentError, "Association #{association_name} not found!"
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ module InstanceMethods
131
+
132
+ protected
133
+
134
+ def autosave
135
+ @autosave_nested_attributes_for ||= {}
136
+ end
137
+
138
+ def marked_for_destruction
139
+ @marked_for_destruction ||= {}
140
+ end
141
+
142
+ private
143
+
144
+ def save_nested_attributes_for_one_association(association_name)
145
+ send(association_name).save
146
+ end
147
+
148
+ def save_nested_attributes_for_many_association(association_name)
149
+ send(association_name).map(&:save)
150
+ end
151
+
152
+ def destroy_marked_for_destruction
153
+ self.marked_for_destruction.each_pair do |association_name, resources|
154
+ resources.map(&:destroy)
155
+ send(association_name).reload
156
+ end
157
+ end
158
+
159
+ def destroy_nested_many_association(association_name)
160
+ send(association_name).map(&:destroy)
161
+ end
162
+
163
+ def assign_nested_attributes_for_one_association(association_name, attributes)
164
+ association = self.class.associations[association_name]
165
+ if association.embeddable?
166
+ assign_nested_attributes_for_one_embedded_association(association_name, attributes)
167
+ else
168
+ self.autosave[association_name] = true
169
+ assign_nested_attributes_for_one_linked_association(association_name, attributes)
170
+ end
171
+ end
172
+
173
+ def assign_nested_attributes_for_one_embedded_association(association_name, attributes)
174
+ send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
175
+ end
176
+
177
+ def assign_nested_attributes_for_one_linked_association(association_name, attributes)
178
+ attributes = attributes.stringify_keys
179
+ options = nested_attributes_options[association_name]
180
+
181
+ if attributes[key_attr.to_s].blank? && !reject_new_record?(association_name, attributes)
182
+ send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
183
+ else
184
+ if ((existing_record = send(association_name)).key.to_s == attributes[key_attr.to_s].to_s)
185
+ assign_to_or_mark_for_destruction(existing_record, attributes, association_name, options[:allow_destroy])
186
+ else
187
+ raise ArgumentError, "Attempting to update a child that isn't already associated to the parent."
188
+ end
189
+ end
190
+ end
191
+
192
+ def assign_nested_attributes_for_many_association(association_name, attributes_collection)
193
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
194
+ raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
195
+ end
196
+
197
+ if attributes_collection.is_a? Hash
198
+ attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
199
+ end
200
+
201
+ association = self.class.associations[association_name]
202
+ if association.embeddable?
203
+ assign_nested_attributes_for_many_embedded_association(association_name, attributes_collection)
204
+ else
205
+ self.autosave[association_name] = true
206
+ assign_nested_attributes_for_many_linked_association(association_name, attributes_collection)
207
+ end
208
+ end
209
+
210
+ def assign_nested_attributes_for_many_embedded_association(association_name, attributes_collection)
211
+ options = nested_attributes_options[association_name]
212
+ send(:"#{association_name}=", []) # Clobber existing
213
+ attributes_collection.each do |attributes|
214
+ attributes = attributes.stringify_keys
215
+ if !reject_new_record?(association_name, attributes)
216
+ send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
217
+ end
218
+ end
219
+ end
220
+
221
+ def assign_nested_attributes_for_many_linked_association(association_name, attributes_collection)
222
+ options = nested_attributes_options[association_name]
223
+ attributes_collection.each do |attributes|
224
+ attributes = attributes.stringify_keys
225
+
226
+ if attributes[key_attr.to_s].blank? && !reject_new_record?(association_name, attributes)
227
+ send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
228
+ elsif existing_record = send(association_name).detect { |record| record.key.to_s == attributes[key_attr.to_s].to_s }
229
+ assign_to_or_mark_for_destruction(existing_record, attributes, association_name, options[:allow_destroy])
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ def assign_to_or_mark_for_destruction(record, attributes, association_name, allow_destroy)
236
+ if has_destroy_flag?(attributes) && allow_destroy
237
+ (self.marked_for_destruction[association_name] ||= []) << record
238
+ else
239
+ record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
240
+ end
241
+ end
242
+
243
+ def has_destroy_flag?(hash)
244
+ TRUE_VALUES.include?(hash.stringify_keys['_destroy'])
245
+ end
246
+
247
+ def reject_new_record?(association_name, attributes)
248
+ has_destroy_flag?(attributes) || call_reject_if(association_name, attributes)
249
+ end
250
+
251
+ def call_reject_if(association_name, attributes)
252
+ attributes = attributes.stringify_keys
253
+ case callback = nested_attributes_options[association_name][:reject_if]
254
+ when Symbol
255
+ method(callback).arity == 0 ? send(callback) : send(callback, attributes)
256
+ when Proc
257
+ callback.call(attributes)
258
+ end
259
+ end
260
+
261
+ end
262
+
263
+ end
@@ -58,9 +58,10 @@ module Ripple
58
58
 
59
59
  # @return [Object] The default value for this property if defined, or nil.
60
60
  def default
61
- if default = options[:default]
62
- type_cast(default.respond_to?(:call) ? default.call : default)
63
- end
61
+ default = options[:default]
62
+
63
+ return nil if default.nil?
64
+ type_cast(default.respond_to?(:call) ? default.call : default)
64
65
  end
65
66
 
66
67
  # @return [Hash] options appropriate for the validates class method
data/lib/ripple.rb CHANGED
@@ -37,6 +37,7 @@ module Ripple
37
37
  autoload :Property, "ripple/properties"
38
38
  autoload :Timestamps
39
39
  autoload :Validations
40
+ autoload :NestedAttributes
40
41
 
41
42
  # Exceptions
42
43
  autoload :PropertyTypeMismatch
@@ -14,8 +14,10 @@
14
14
  require File.expand_path("../../../spec_helper", __FILE__)
15
15
 
16
16
  describe "Ripple Associations" do
17
+ require 'support/test_server'
18
+
17
19
  before :all do
18
- Object.module_eval do
20
+ Object.module_eval do
19
21
  class User
20
22
  include Ripple::Document
21
23
  one :profile
@@ -37,7 +39,7 @@ describe "Ripple Associations" do
37
39
  end
38
40
  end
39
41
  end
40
-
42
+
41
43
  before :each do
42
44
  @user = User.new(:email => 'riak@ripple.com')
43
45
  @profile = Profile.new(:name => 'Ripple')
@@ -46,7 +48,7 @@ describe "Ripple Associations" do
46
48
  @friend1 = User.create(:email => "friend@ripple.com")
47
49
  @friend2 = User.create(:email => "friend2@ripple.com")
48
50
  end
49
-
51
+
50
52
  it "should save one embedded associations" do
51
53
  @user.profile = @profile
52
54
  @user.save
@@ -55,7 +57,7 @@ describe "Ripple Associations" do
55
57
  @found.profile.should be_a(Profile)
56
58
  @found.profile.user.should == @found
57
59
  end
58
-
60
+
59
61
  it "should save many embedded associations" do
60
62
  @user.addresses << @billing << @shipping
61
63
  @user.save
@@ -71,7 +73,7 @@ describe "Ripple Associations" do
71
73
  @ship.should be_a(Address)
72
74
  end
73
75
 
74
- it "should save a many linked association" do
76
+ it "should save a many linked association" do
75
77
  @user.friends << @friend1 << @friend2
76
78
  @user.save
77
79
  @user.should_not be_new_record
@@ -87,7 +89,7 @@ describe "Ripple Associations" do
87
89
  @found = User.find(@user.key)
88
90
  @found.emergency_contact.key.should == @friend1.key
89
91
  end
90
-
92
+
91
93
  after :each do
92
94
  User.destroy_all
93
95
  end
@@ -97,5 +99,5 @@ describe "Ripple Associations" do
97
99
  Object.send(:remove_const, :Profile)
98
100
  Object.send(:remove_const, :Address)
99
101
  end
100
-
102
+
101
103
  end
@@ -0,0 +1,260 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Ripple::NestedAttributes do
4
+ require 'support/models/car'
5
+ require 'support/models/driver'
6
+ require 'support/models/passenger'
7
+ require 'support/models/engine'
8
+ require 'support/models/seat'
9
+ require 'support/models/wheel'
10
+ require 'support/test_server'
11
+
12
+ context "one :driver (link)" do
13
+ subject { Car.new }
14
+
15
+ it { should respond_to(:driver_attributes=) }
16
+
17
+ it "should not have a driver" do
18
+ subject.driver.should be_nil
19
+ end
20
+
21
+ describe "creation" do
22
+ subject { Car.new(:make => 'VW', :model => 'Rabbit', :driver_attributes => { :name => 'Speed Racer' }) }
23
+
24
+ it "should have a driver of class Driver" do
25
+ subject.driver.should be_a(Driver)
26
+ end
27
+
28
+ it "should have a driver with name 'Speed Racer'" do
29
+ subject.driver.name.should == 'Speed Racer'
30
+ end
31
+
32
+ it "should save the child when saving the parent" do
33
+ subject.driver.should_receive(:save)
34
+ subject.save
35
+ end
36
+ end
37
+
38
+ describe "update" do
39
+ let(:driver) { Driver.create(:name => 'Slow Racer') }
40
+
41
+ before do
42
+ subject.driver = driver
43
+ subject.save
44
+ end
45
+
46
+ it "should have a driver" do
47
+ subject.driver.should == driver
48
+ end
49
+
50
+ it "should update attributes" do
51
+ subject.driver_attributes = { :name => 'Speed Racer' }
52
+ subject.driver.name.should == 'Speed Racer'
53
+ end
54
+
55
+ it "should not save the child if attributes haven't been updated" do
56
+ subject.driver.should_not_receive(:save)
57
+ subject.save
58
+ end
59
+
60
+ it "should save the child when saving the parent" do
61
+ subject.driver_attributes = { :name => 'Speed Racer' }
62
+ subject.driver.should_receive(:save)
63
+ subject.save
64
+ end
65
+ end
66
+ end
67
+
68
+ context "many :passengers (link)" do
69
+ subject { Car.new }
70
+
71
+ it { should respond_to(:passengers_attributes=) }
72
+
73
+ it "should not have passengers" do
74
+ subject.passengers.should == []
75
+ end
76
+
77
+ describe "creation" do
78
+ subject { Car.new(:make => 'VW',
79
+ :model => 'Rabbit',
80
+ :passengers_attributes => [ { :name => 'Joe' },
81
+ { :name => 'Sue' },
82
+ { :name => 'Pat' } ] ) }
83
+
84
+ it "should have 3 passengers" do
85
+ subject.passengers.size.should == 3
86
+ end
87
+
88
+ it "should have 3 passengers with specified names" do
89
+ subject.passengers.first.name.should == 'Joe'
90
+ subject.passengers.second.name.should == 'Sue'
91
+ subject.passengers.third.name.should == 'Pat'
92
+ end
93
+
94
+ it "should save the children when saving the parent" do
95
+ subject.passengers.each do |passenger|
96
+ passenger.should_receive(:save)
97
+ end
98
+ subject.save
99
+ end
100
+ end
101
+
102
+ describe "update" do
103
+ let(:passenger1) { Passenger.create(:name => 'One') }
104
+ let(:passenger2) { Passenger.create(:name => 'Two') }
105
+ let(:passenger3) { Passenger.create(:name => 'Three') }
106
+
107
+ before do
108
+ subject.passengers << passenger1
109
+ subject.passengers << passenger2
110
+ subject.passengers << passenger3
111
+ subject.save
112
+ end
113
+
114
+ it "should have 3 passengers" do
115
+ subject.passengers.size.should == 3
116
+ end
117
+
118
+ it "should update attributes" do
119
+ subject.passengers_attributes = [ { :key => passenger1.key, :name => 'UPDATED One' },
120
+ { :key => passenger2.key, :name => 'UPDATED Two' },
121
+ { :key => passenger3.key, :name => 'UPDATED Three' } ]
122
+ subject.passengers.first.name.should == 'UPDATED One'
123
+ subject.passengers.second.name.should == 'UPDATED Two'
124
+ subject.passengers.third.name.should == 'UPDATED Three'
125
+ end
126
+
127
+ it "should not save the child if attributes haven't been updated" do
128
+ subject.passengers.each do |passenger|
129
+ passenger.should_not_receive(:save)
130
+ end
131
+ subject.save
132
+ end
133
+
134
+ it "should save the child when saving the parent" do
135
+ subject.passengers_attributes = [ { :key => passenger1.key, :name => 'UPDATED One' },
136
+ { :key => passenger1.key, :name => 'UPDATED Two' },
137
+ { :key => passenger1.key, :name => 'UPDATED Three' } ]
138
+ subject.passengers.each do |passenger|
139
+ passenger.should_receive(:save)
140
+ end
141
+ subject.save
142
+ end
143
+ end
144
+ end
145
+
146
+ context "one :engine (embedded)" do
147
+ subject { Car.new }
148
+
149
+ it { should respond_to(:engine_attributes=) }
150
+
151
+ it "should not have an engine" do
152
+ subject.engine.should be_nil
153
+ end
154
+
155
+ describe "creation" do
156
+ subject { Car.new(:make => 'VW', :model => 'Rabbit', :engine_attributes => { :displacement => '2.5L' }) }
157
+
158
+ it "should have an engine of class Engine" do
159
+ subject.engine.should be_a(Engine)
160
+ end
161
+
162
+ it "should have a engine with displacement '2.5L'" do
163
+ subject.engine.displacement.should == '2.5L'
164
+ end
165
+
166
+ it "should save the child when saving the parent" do
167
+ subject.engine.should_not_receive(:save)
168
+ subject.save
169
+ end
170
+ end
171
+
172
+ describe "update" do
173
+ before do
174
+ subject.engine.build(:displacement => '3.6L')
175
+ subject.save
176
+ end
177
+
178
+ it "should have a specified engine" do
179
+ subject.engine.displacement.should == '3.6L'
180
+ end
181
+
182
+ it "should update attributes" do
183
+ subject.engine_attributes = { :displacement => 'UPDATED 3.6L' }
184
+ subject.engine.displacement.should == 'UPDATED 3.6L'
185
+ end
186
+
187
+ it "should not save the child if attributes haven't been updated" do
188
+ subject.engine.should_not_receive(:save)
189
+ subject.save
190
+ end
191
+
192
+ it "should not save the child when saving the parent" do
193
+ subject.engine_attributes = { :displacement => 'UPDATED 3.6L' }
194
+ subject.engine.should_not_receive(:save)
195
+ subject.save
196
+ end
197
+ end
198
+ end
199
+
200
+ context "many :seats (embedded)" do
201
+ subject { Car.new }
202
+
203
+ it { should respond_to(:seats_attributes=) }
204
+
205
+ it "should not have passengers" do
206
+ subject.seats.should == []
207
+ end
208
+
209
+ describe "creation" do
210
+ subject { Car.new(:make => 'VW',
211
+ :model => 'Rabbit',
212
+ :seats_attributes => [ { :color => 'red' },
213
+ { :color => 'blue' },
214
+ { :color => 'brown' } ] ) }
215
+
216
+ it "should have 3 seats" do
217
+ subject.seats.size.should == 3
218
+ end
219
+
220
+ it "should have 3 passengers with specified names" do
221
+ subject.seats.first.color.should == 'red'
222
+ subject.seats.second.color.should == 'blue'
223
+ subject.seats.third.color.should == 'brown'
224
+ end
225
+
226
+ specify "replace/clobber" do
227
+ subject.seats_attributes = [ { :color => 'orange' } ]
228
+ subject.seats.size.should == 1
229
+ subject.seats.first.color.should == 'orange'
230
+ end
231
+
232
+ end
233
+ end
234
+
235
+ context ":reject_if" do
236
+ it "should not create a wheel" do
237
+ car = Car.new(:wheels_attributes => [ { :diameter => 10 } ])
238
+ car.wheels.should == []
239
+ end
240
+
241
+ it "should create a wheel" do
242
+ car = Car.new(:wheels_attributes => [ { :diameter => 16 } ])
243
+ car.wheels.size.should == 1
244
+ car.wheels.first.diameter.should == 16
245
+ end
246
+ end
247
+
248
+ context ":allow_delete" do
249
+ let(:wheel) { Wheel.create(:diameter => 17) }
250
+ subject { Car.create(:wheels => [ wheel ] ) }
251
+
252
+ it "should allow us to delete the wheel" do
253
+ subject.wheels_attributes = [ { :key => wheel.key, :_destroy => "1" } ]
254
+ subject.save
255
+ subject.wheels.should == []
256
+ end
257
+
258
+ end
259
+
260
+ end
@@ -14,6 +14,8 @@
14
14
  require File.expand_path("../../../spec_helper", __FILE__)
15
15
 
16
16
  describe "Ripple Persistence" do
17
+ require 'support/test_server'
18
+
17
19
  before :all do
18
20
  Object.module_eval do
19
21
  class Widget
@@ -15,7 +15,7 @@ require File.expand_path("../../spec_helper", __FILE__)
15
15
 
16
16
  describe Ripple::AttributeMethods do
17
17
  require 'support/models/widget'
18
-
18
+
19
19
  before :each do
20
20
  @widget = Widget.new
21
21
  end
@@ -37,7 +37,7 @@ describe Ripple::AttributeMethods do
37
37
  @widget.attributes = {'key' => 'new-key'}
38
38
  @widget.key.should == 'widget-key'
39
39
  end
40
-
40
+
41
41
  it "should typecast the key to a string" do
42
42
  @widget.key = 10
43
43
  @widget.key.should == "10"
@@ -56,8 +56,9 @@ describe Ripple::AttributeMethods do
56
56
 
57
57
  it "should return the property default if defined and not set" do
58
58
  @widget.name.should == "widget"
59
+ @widget.manufactured.should == false
59
60
  end
60
-
61
+
61
62
  it "should allow raw attribute access when accessing the document with []" do
62
63
  @widget['name'].should == 'widget'
63
64
  end
@@ -78,7 +79,7 @@ describe Ripple::AttributeMethods do
78
79
  @widget.size = 10
79
80
  @widget.size.should == 10
80
81
  end
81
-
82
+
82
83
  it "should allow assignment of undefined attributes when assigning to the document with []=" do
83
84
  @widget['name'] = 'sprocket'
84
85
  @widget.name.should == 'sprocket'
@@ -136,7 +137,7 @@ describe Ripple::AttributeMethods do
136
137
  end
137
138
 
138
139
  it "should provide a hash representation of all of the attributes" do
139
- @widget.attributes.should == {"name" => "widget", "size" => nil}
140
+ @widget.attributes.should == {"name" => "widget", "size" => nil, "manufactured" => false}
140
141
  end
141
142
 
142
143
  it "should load attributes from mass assignment" do
@@ -154,24 +155,24 @@ describe Ripple::AttributeMethods do
154
155
  @widget = Widget.new(:name => "Riak")
155
156
  @widget.changes.should be_blank
156
157
  end
157
-
158
+
158
159
  it "should allow adding to the @attributes hash for attributes that do not exist" do
159
160
  @widget = Widget.new
160
161
  @widget['foo'] = 'bar'
161
162
  @widget.instance_eval { @attributes['foo'] }.should == 'bar'
162
163
  end
163
-
164
+
164
165
  it "should allow reading from the @attributes hash for attributes that do not exist" do
165
166
  @widget = Widget.new
166
167
  @widget['foo'] = 'bar'
167
168
  @widget['foo'].should == 'bar'
168
169
  end
169
-
170
+
170
171
  it "should allow a block upon initialization to set attributes protected from mass assignment" do
171
172
  @widget = Widget.new { |w| w.key = 'some-key' }
172
173
  @widget.key.should == 'some-key'
173
174
  end
174
-
175
+
175
176
  it "should raise an argument error when assigning a non hash to attributes" do
176
177
  @widget = Widget.new
177
178
  lambda { @widget.attributes = nil }.should raise_error(ArgumentError)
@@ -77,6 +77,11 @@ describe Ripple::Property do
77
77
  prop.default.should == "bar"
78
78
  end
79
79
 
80
+ it "should allow false for a Boolean" do
81
+ prop = Ripple::Property.new('foo', Boolean, :default => false)
82
+ prop.default.should == false
83
+ end
84
+
80
85
  it "should allow lambdas for deferred evaluation" do
81
86
  prop = Ripple::Property.new('foo', String, :default => lambda { "bar" })
82
87
  prop.default.should == "bar"
@@ -122,11 +127,11 @@ describe Ripple::Property do
122
127
  @prop.type_cast([]).should == "[]"
123
128
  end
124
129
  end
125
-
130
+
126
131
  it "should not cast nil" do
127
132
  @prop.type_cast(nil).should be_nil
128
133
  end
129
-
134
+
130
135
  end
131
136
 
132
137
  describe "when type is an Integer type" do
data/spec/spec_helper.rb CHANGED
@@ -17,10 +17,13 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'riak-client','
17
17
 
18
18
  require 'rubygems' # Use the gems path only for the spec suite
19
19
  require 'ripple'
20
- require 'rspec/autorun'
20
+ require 'rspec'
21
21
 
22
22
  Dir[File.join(File.dirname(__FILE__), "support", "*.rb")].each {|f| require f }
23
23
 
24
24
  Rspec.configure do |config|
25
25
  config.mock_with :rspec
26
+ config.after(:each) do
27
+ $test_server.recycle if $test_server
28
+ end
26
29
  end
@@ -0,0 +1,15 @@
1
+ class Car
2
+ include Ripple::Document
3
+
4
+ property :make, String
5
+ property :model, String
6
+
7
+ one :driver # linked, key_on :name
8
+ many :passengers # linked, standard :key
9
+ one :engine # embedded
10
+ many :seats # embedded
11
+ many :wheels
12
+
13
+ accepts_nested_attributes_for :driver, :passengers, :engine, :seats
14
+ accepts_nested_attributes_for :wheels, :reject_if => proc{|attrs| attrs['diameter'] < 12 }, :allow_destroy => true
15
+ end
@@ -0,0 +1,5 @@
1
+ class Driver
2
+ include Ripple::Document
3
+ property :name, String
4
+ key_on :name
5
+ end
@@ -0,0 +1,4 @@
1
+ class Engine
2
+ include Ripple::EmbeddedDocument
3
+ property :displacement, String
4
+ end
@@ -0,0 +1,5 @@
1
+ class Passenger
2
+ include Ripple::Document
3
+ property :name, String
4
+
5
+ end
@@ -0,0 +1,4 @@
1
+ class Seat
2
+ include Ripple::EmbeddedDocument
3
+ property :color, String
4
+ end
@@ -0,0 +1,5 @@
1
+ class Wheel
2
+ include Ripple::Document
3
+ property :diameter, Integer
4
+
5
+ end
@@ -16,6 +16,7 @@ class Widget
16
16
  include Ripple::Document
17
17
  property :size, Integer
18
18
  property :name, String, :default => "widget"
19
+ property :manufactured, Boolean, :default => false
19
20
  end
20
21
 
21
22
  class Cog < Widget
@@ -0,0 +1,17 @@
1
+ require 'riak/test_server'
2
+
3
+ unless $test_server
4
+ begin
5
+ require 'yaml'
6
+ config = YAML.load_file("spec/support/test_server.yml")
7
+ $test_server = Riak::TestServer.new(config.symbolize_keys)
8
+ $test_server.prepare!
9
+ $test_server.start
10
+ Ripple.config = {:port => 9000 }
11
+ at_exit { $test_server.cleanup }
12
+ rescue => e
13
+ warn "Can't run Riak::TestServer specs. Specify the location of your Riak installation in spec/support/test_server.yml. See Riak::TestServer docs for more info."
14
+ warn e.inspect
15
+ $test_server = nil
16
+ end
17
+ end
@@ -0,0 +1,2 @@
1
+ bin_dir: /Users/sean/Development/riak/rel/riak/bin
2
+ temp_dir: /Users/sean/Development/ripple/.riaktest
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 8
8
- - 0
9
- version: 0.8.0
8
+ - 1
9
+ version: 0.8.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Sean Cribbs
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-31 00:00:00 -04:00
17
+ date: 2010-10-11 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -45,8 +45,8 @@ dependencies:
45
45
  segments:
46
46
  - 0
47
47
  - 8
48
- - 0
49
- version: 0.8.0
48
+ - 1
49
+ version: 0.8.1
50
50
  type: :runtime
51
51
  version_requirements: *id002
52
52
  - !ruby/object:Gem::Dependency
@@ -122,6 +122,7 @@ files:
122
122
  - lib/ripple/i18n.rb
123
123
  - lib/ripple/inspection.rb
124
124
  - lib/ripple/locale/en.yml
125
+ - lib/ripple/nested_attributes.rb
125
126
  - lib/ripple/properties.rb
126
127
  - lib/ripple/property_type_mismatch.rb
127
128
  - lib/ripple/railtie.rb
@@ -133,6 +134,7 @@ files:
133
134
  - Rakefile
134
135
  - spec/fixtures/config.yml
135
136
  - spec/integration/ripple/associations_spec.rb
137
+ - spec/integration/ripple/nested_attributes_spec.rb
136
138
  - spec/integration/ripple/persistence_spec.rb
137
139
  - spec/ripple/associations/many_embedded_proxy_spec.rb
138
140
  - spec/ripple/associations/many_linked_proxy_spec.rb
@@ -162,10 +164,13 @@ files:
162
164
  - spec/support/mocks.rb
163
165
  - spec/support/models/address.rb
164
166
  - spec/support/models/box.rb
167
+ - spec/support/models/car.rb
165
168
  - spec/support/models/cardboard_box.rb
166
169
  - spec/support/models/clock.rb
167
170
  - spec/support/models/customer.rb
171
+ - spec/support/models/driver.rb
168
172
  - spec/support/models/email.rb
173
+ - spec/support/models/engine.rb
169
174
  - spec/support/models/family.rb
170
175
  - spec/support/models/favorite.rb
171
176
  - spec/support/models/invoice.rb
@@ -173,10 +178,15 @@ files:
173
178
  - spec/support/models/note.rb
174
179
  - spec/support/models/page.rb
175
180
  - spec/support/models/paid_invoice.rb
181
+ - spec/support/models/passenger.rb
182
+ - spec/support/models/seat.rb
176
183
  - spec/support/models/tasks.rb
177
184
  - spec/support/models/tree.rb
178
185
  - spec/support/models/user.rb
186
+ - spec/support/models/wheel.rb
179
187
  - spec/support/models/widget.rb
188
+ - spec/support/test_server.rb
189
+ - spec/support/test_server.yml
180
190
  has_rdoc: true
181
191
  homepage: http://seancribbs.github.com/ripple
182
192
  licenses: []
@@ -211,6 +221,7 @@ specification_version: 3
211
221
  summary: ripple is an object-mapper library for Riak, the distributed database by Basho.
212
222
  test_files:
213
223
  - spec/integration/ripple/associations_spec.rb
224
+ - spec/integration/ripple/nested_attributes_spec.rb
214
225
  - spec/integration/ripple/persistence_spec.rb
215
226
  - spec/ripple/associations/many_embedded_proxy_spec.rb
216
227
  - spec/ripple/associations/many_linked_proxy_spec.rb
@@ -240,10 +251,13 @@ test_files:
240
251
  - spec/support/mocks.rb
241
252
  - spec/support/models/address.rb
242
253
  - spec/support/models/box.rb
254
+ - spec/support/models/car.rb
243
255
  - spec/support/models/cardboard_box.rb
244
256
  - spec/support/models/clock.rb
245
257
  - spec/support/models/customer.rb
258
+ - spec/support/models/driver.rb
246
259
  - spec/support/models/email.rb
260
+ - spec/support/models/engine.rb
247
261
  - spec/support/models/family.rb
248
262
  - spec/support/models/favorite.rb
249
263
  - spec/support/models/invoice.rb
@@ -251,7 +265,11 @@ test_files:
251
265
  - spec/support/models/note.rb
252
266
  - spec/support/models/page.rb
253
267
  - spec/support/models/paid_invoice.rb
268
+ - spec/support/models/passenger.rb
269
+ - spec/support/models/seat.rb
254
270
  - spec/support/models/tasks.rb
255
271
  - spec/support/models/tree.rb
256
272
  - spec/support/models/user.rb
273
+ - spec/support/models/wheel.rb
257
274
  - spec/support/models/widget.rb
275
+ - spec/support/test_server.rb