ripple 0.8.0 → 0.8.1

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