delineate 0.6.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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +90 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +292 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/delineate.gemspec +84 -0
- data/lib/class_inheritable_attributes.rb +141 -0
- data/lib/core_extensions.rb +14 -0
- data/lib/delineate.rb +11 -0
- data/lib/delineate/attribute_map/attribute_map.rb +714 -0
- data/lib/delineate/attribute_map/csv_serializer.rb +133 -0
- data/lib/delineate/attribute_map/json_serializer.rb +37 -0
- data/lib/delineate/attribute_map/map_serializer.rb +170 -0
- data/lib/delineate/attribute_map/xml_serializer.rb +45 -0
- data/lib/delineate/map_attributes.rb +201 -0
- data/spec/database.yml +17 -0
- data/spec/delineate_spec.rb +662 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/support/models.rb +184 -0
- data/spec/support/schema.rb +125 -0
- metadata +182 -0
data/spec/database.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ':memory:'
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
hostname: localhost
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: delineate_test
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
adapter: postgresql
|
14
|
+
hostname: localhost
|
15
|
+
username: postgres
|
16
|
+
password:
|
17
|
+
database: delineate_test
|
@@ -0,0 +1,662 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'active_support/json.rb'
|
3
|
+
require 'active_support/xml_mini.rb'
|
4
|
+
|
5
|
+
describe "Attribute Map declaration (attributes)" do
|
6
|
+
it "should require a map name" do
|
7
|
+
running {
|
8
|
+
Post.map_attributes do
|
9
|
+
attribute :post_id, :using => :id
|
10
|
+
end
|
11
|
+
}.should raise_error(ArgumentError)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should allow all valid attribute options" do
|
15
|
+
running {
|
16
|
+
Post.map_attributes :test do
|
17
|
+
attribute :name, :using => :attr, :access => :rw, :optional => true, :read => lambda{}, :write => lambda{}
|
18
|
+
end
|
19
|
+
}.should_not raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should raise an error for invalid attribute options" do
|
23
|
+
running {
|
24
|
+
Post.map_attributes :test do
|
25
|
+
attribute :name, :hello => true
|
26
|
+
end
|
27
|
+
}.should raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should raise an error for unknown attribute" do
|
31
|
+
Post.map_attributes :test do
|
32
|
+
attribute :xxx, :access => :ro
|
33
|
+
end
|
34
|
+
|
35
|
+
p = Post.new
|
36
|
+
expect {p.test_attributes}.to raise_error(NoMethodError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should error for writeable attribute not declared as attribute_accessible" do
|
40
|
+
running {
|
41
|
+
Post.map_attributes :test do
|
42
|
+
attribute :xxx
|
43
|
+
end
|
44
|
+
}.should raise_error(RuntimeError, /Expected 'attr_accessible :xxx'/)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should allow all valid access options" do
|
48
|
+
running {
|
49
|
+
Post.attr_accessible(:created_at)
|
50
|
+
Post.map_attributes :test do
|
51
|
+
attribute :title, :access => :ro
|
52
|
+
attribute :content, :access => :rw
|
53
|
+
attribute :created_at, :access => :w
|
54
|
+
end
|
55
|
+
}.should_not raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should not allow :write option when access is :ro" do
|
59
|
+
running {
|
60
|
+
Post.map_attributes :test do
|
61
|
+
attribute :name, :access => :ro, :write => lambda{}
|
62
|
+
end
|
63
|
+
}.should raise_error(ArgumentError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise an error for invalid access option value" do
|
67
|
+
running {
|
68
|
+
Post.map_attributes :test do
|
69
|
+
attribute :name, :access => :ddd
|
70
|
+
end
|
71
|
+
}.should raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should automatically name an attribute read/write lamda" do
|
75
|
+
Post.map_attributes :test do
|
76
|
+
attribute :summary, :read => lambda {|p| p.instance_variable_get(:@test)},
|
77
|
+
:write => lambda {|p, v| p.instance_variable_set(:@test, v)}
|
78
|
+
end
|
79
|
+
|
80
|
+
p = Post.new
|
81
|
+
p.summary_test = "This is a test"
|
82
|
+
p.summary_test.should == "This is a test"
|
83
|
+
expect(p.test_attributes).to include(:summary => 'This is a test')
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should honor the model_attr name for an attribute read/write lamda" do
|
87
|
+
Post.map_attributes :test do
|
88
|
+
attribute :name, :using => :test_name, :access => :ro, :read => lambda{|p| "This is a test"}
|
89
|
+
end
|
90
|
+
p = Post.new
|
91
|
+
p.test_name.should == "This is a test"
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "Attribute Map declaration (associations)" do
|
97
|
+
before(:all) do
|
98
|
+
reload_model_classes!
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should raise an error for an undefined association" do
|
102
|
+
running {
|
103
|
+
Post.map_attributes :test do
|
104
|
+
association :hello
|
105
|
+
end
|
106
|
+
}.should raise_error(ArgumentError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should raise an error for invalid association options" do
|
110
|
+
running {
|
111
|
+
Post.map_attributes :test do
|
112
|
+
association :comments, :hello => true
|
113
|
+
end
|
114
|
+
}.should raise_error(ArgumentError)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should allow all valid association options" do
|
118
|
+
running {
|
119
|
+
Post.attr_accessible(:name)
|
120
|
+
Post.map_attributes :test do
|
121
|
+
attribute :name
|
122
|
+
association :comments, :using => :comments, :override => :merge, :polymorphic => false, :access => :rw,
|
123
|
+
:optional => true
|
124
|
+
end
|
125
|
+
}.should_not raise_error
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should raise an error for invalid access option value" do
|
129
|
+
running {
|
130
|
+
Post.map_attributes :test do
|
131
|
+
association :comments, :access => :bad
|
132
|
+
end
|
133
|
+
}.should raise_error(ArgumentError)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should raise an error for invalid :override option value" do
|
137
|
+
running {
|
138
|
+
Reply.map_attributes :test, :override => true do
|
139
|
+
association :comments
|
140
|
+
end
|
141
|
+
}.should raise_error(ArgumentError)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should allow :override for STI subclass" do
|
145
|
+
running {
|
146
|
+
Reply.map_attributes :test, :override => :replace do
|
147
|
+
attribute :type
|
148
|
+
end
|
149
|
+
}.should_not raise_error
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should disallow :override for non-STI subclass" do
|
153
|
+
running {
|
154
|
+
Comment.map_attributes :test, :override => :replace do
|
155
|
+
attribute :type
|
156
|
+
end
|
157
|
+
}.should raise_error(ArgumentError)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should raise an error for :replace with no specs" do
|
161
|
+
running {
|
162
|
+
Post.map_attributes :test do
|
163
|
+
association :comments, :override => :replace do
|
164
|
+
end
|
165
|
+
end
|
166
|
+
}.should raise_error(ArgumentError)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should not allow circular merge references" do
|
170
|
+
running {
|
171
|
+
Comment.map_attributes :test do
|
172
|
+
attribute :content
|
173
|
+
association :post, :optional => true do
|
174
|
+
attribute :content
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
Post.map_attributes :test do
|
179
|
+
attribute :title
|
180
|
+
association :comments do
|
181
|
+
attribute :commentor, :access => :ro
|
182
|
+
end
|
183
|
+
end
|
184
|
+
}.should_not raise_error
|
185
|
+
|
186
|
+
running {Post.attribute_map(:test).send('resolve!')}.should raise_error(RuntimeError, /circular merge/)
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should allow circular non-merge references" do
|
190
|
+
running {
|
191
|
+
Comment.map_attributes :test do
|
192
|
+
attribute :content
|
193
|
+
association :post, :access => :ro, :optional => true, :override => :replace do
|
194
|
+
attribute :title
|
195
|
+
attribute :content
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
Post.map_attributes :test do
|
200
|
+
attribute :title
|
201
|
+
association :comments do
|
202
|
+
attribute :commentor, :access => :ro
|
203
|
+
end
|
204
|
+
end
|
205
|
+
}.should_not raise_error
|
206
|
+
|
207
|
+
running {Post.attribute_map(:test).send('resolve!')}.should_not raise_error
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
c = Comment.new(:content => "This is the content", :commentor => 'John Q Public')
|
212
|
+
p = Post.new(:title => 'This is the title')
|
213
|
+
c.post = p
|
214
|
+
p.comments = [c]
|
215
|
+
|
216
|
+
expected_result = {:title=>"This is the title",
|
217
|
+
:comments=>[{:content=>"This is the content", :commentor=>"John Q Public", :post=>{:title=>"This is the title", :content=>nil}}]
|
218
|
+
}
|
219
|
+
expect(p.test_attributes(:include => {:comments => {:include => :post}})).to eq(expected_result)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should process replacing association's map" do
|
223
|
+
running {
|
224
|
+
Comment.attr_accessible(:test)
|
225
|
+
Comment.map_attributes :test do
|
226
|
+
attribute :content
|
227
|
+
attribute :commentor
|
228
|
+
association :post, :optional => true
|
229
|
+
end
|
230
|
+
|
231
|
+
Post.map_attributes :test do
|
232
|
+
association :comments, :override => :replace do
|
233
|
+
attribute :test
|
234
|
+
attribute :commentor, :access => :ro
|
235
|
+
end
|
236
|
+
end
|
237
|
+
}.should_not raise_error
|
238
|
+
Post.attribute_map(:test).send('resolve!')
|
239
|
+
|
240
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].should_not be_nil
|
241
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].associations.has_key?(:post).should be_false
|
242
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes.should have_key(:test)
|
243
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes.should_not have_key(:content)
|
244
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes.should have_key(:commentor)
|
245
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes[:commentor][:access].should == :ro
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should process merging association's map" do
|
249
|
+
running {
|
250
|
+
Comment.attr_accessible(:test)
|
251
|
+
Comment.map_attributes :test do
|
252
|
+
attribute :content
|
253
|
+
attribute :commentor, :access => :ro
|
254
|
+
association :post, :optional => true
|
255
|
+
end
|
256
|
+
|
257
|
+
Post.map_attributes :test do
|
258
|
+
association :comments, :override => :merge do
|
259
|
+
attribute :test
|
260
|
+
attribute :commentor
|
261
|
+
end
|
262
|
+
end
|
263
|
+
}.should_not raise_error
|
264
|
+
Post.attribute_map(:test).send('resolve!')
|
265
|
+
|
266
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].should_not be_nil
|
267
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].associations.size.should == 1
|
268
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes.should have_key(:test)
|
269
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes.should have_key(:content)
|
270
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes.should have_key(:commentor)
|
271
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].attributes[:commentor][:access].should_not == :ro
|
272
|
+
Post.attribute_map(:test).associations[:comments][:attr_map].associations.should have_key(:post)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should use STI base class map in a subclass" do
|
276
|
+
running {
|
277
|
+
Post.map_attributes :test do
|
278
|
+
attribute :title
|
279
|
+
end
|
280
|
+
Comment.map_attributes :test do
|
281
|
+
attribute :content
|
282
|
+
attribute :commentor, :access => :ro
|
283
|
+
association :post, :optional => true
|
284
|
+
end
|
285
|
+
Reply.map_attributes :test do
|
286
|
+
attribute :type
|
287
|
+
end
|
288
|
+
}.should_not raise_error
|
289
|
+
Reply.attribute_map(:test).send('resolve!')
|
290
|
+
|
291
|
+
Reply.attribute_map(:test).associations[:post].should_not be_nil
|
292
|
+
Reply.attribute_map(:test).attributes.should have_key(:type)
|
293
|
+
Reply.attribute_map(:test).attributes.should have_key(:content)
|
294
|
+
Reply.attribute_map(:test).attributes.should have_key(:commentor)
|
295
|
+
end
|
296
|
+
|
297
|
+
it "should override STI base class map in a subclass when specified" do
|
298
|
+
running {
|
299
|
+
Comment.map_attributes :test do
|
300
|
+
attribute :content
|
301
|
+
attribute :commentor, :access => :ro
|
302
|
+
association :post, :optional => true
|
303
|
+
end
|
304
|
+
|
305
|
+
Reply.map_attributes :test, :override => :replace do
|
306
|
+
attribute :type
|
307
|
+
end
|
308
|
+
}.should_not raise_error
|
309
|
+
Reply.attribute_map(:test).send('resolve!')
|
310
|
+
|
311
|
+
Reply.attribute_map(:test).associations[:post].should be_nil
|
312
|
+
Reply.attribute_map(:test).attributes.should have_key(:type)
|
313
|
+
Reply.attribute_map(:test).attributes.should_not have_key(:content)
|
314
|
+
Reply.attribute_map(:test).attributes.should_not have_key(:commentor)
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
describe "AttributeMap Serializer" do
|
320
|
+
it "should retrieve attributes as a hash (omitting optional)" do
|
321
|
+
p = Post.first
|
322
|
+
attrs = p.api_attributes
|
323
|
+
|
324
|
+
attrs.keys.should == [:id, :title, :content, :created_at, :author, :topics]
|
325
|
+
attrs[:content].should == "This is the content for post 1"
|
326
|
+
attrs[:author].keys.should == [:id, :first_name, :last_name, :name, :email]
|
327
|
+
attrs[:author][:name].should == 'Tom Smith'
|
328
|
+
attrs[:author].should_not have_key(:posts)
|
329
|
+
attrs[:topics].should have(2).items
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should retrieve attributes as json" do
|
333
|
+
p = Post.first
|
334
|
+
attrs = p.mapped_attributes(:api, :json)
|
335
|
+
attrs.should be_a_kind_of(String)
|
336
|
+
attrs = ActiveSupport::JSON.decode(attrs).symbolize_keys
|
337
|
+
|
338
|
+
(attrs.keys - [:id, :title, :content, :created_at, :author, :topics]).should be_empty
|
339
|
+
([:id, :title, :content, :created_at, :author, :topics] - attrs.keys).should be_empty
|
340
|
+
attrs[:content].should == "This is the content for post 1"
|
341
|
+
attrs[:author].symbolize_keys!
|
342
|
+
(attrs[:author].keys - [:id, :first_name, :last_name, :name, :email]).should be_empty
|
343
|
+
([:id, :first_name, :last_name, :name, :email] - attrs[:author].keys).should be_empty
|
344
|
+
attrs[:author][:name].should == 'Tom Smith'
|
345
|
+
attrs[:author].should_not have_key(:posts)
|
346
|
+
attrs[:topics].should have(2).items
|
347
|
+
end
|
348
|
+
|
349
|
+
it "should retrieve attributes as xml" do
|
350
|
+
p = Post.first
|
351
|
+
attrs = p.mapped_attributes(:api, :xml, :root => 'response', :dasherize => false)
|
352
|
+
attrs.should be_a_kind_of(String)
|
353
|
+
attrs = Hash.from_xml(attrs)['response'].symbolize_keys
|
354
|
+
|
355
|
+
(attrs.keys - [:id, :title, :content, :created_at, :author, :topics]).should be_empty
|
356
|
+
([:id, :title, :content, :created_at, :author, :topics] - attrs.keys).should be_empty
|
357
|
+
attrs[:content].should == "This is the content for post 1"
|
358
|
+
attrs[:author].symbolize_keys!
|
359
|
+
(attrs[:author].keys - [:id, :first_name, :last_name, :name, :email]).should be_empty
|
360
|
+
([:id, :first_name, :last_name, :name, :email] - attrs[:author].keys).should be_empty
|
361
|
+
attrs[:author][:name].should == 'Tom Smith'
|
362
|
+
attrs[:author].should_not have_key(:posts)
|
363
|
+
attrs[:topics].should have(2).items
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should raise an error if a specified association does not have a corresponding map" do
|
367
|
+
reload_model_classes!
|
368
|
+
|
369
|
+
Post.map_attributes :test do
|
370
|
+
association :comments
|
371
|
+
end
|
372
|
+
running {
|
373
|
+
Post.first.test_attributes
|
374
|
+
}.should raise_error(RuntimeError, /Cannot resolve map/)
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should honor the :only option" do
|
378
|
+
p = Post.first
|
379
|
+
|
380
|
+
attrs = p.api_attributes(:only => :id)
|
381
|
+
attrs.keys.should == [:id]
|
382
|
+
|
383
|
+
attrs = p.api_attributes(:only => [:id, :author])
|
384
|
+
attrs.keys.should == [:id, :author]
|
385
|
+
attrs[:author].keys.should == [:id, :first_name, :last_name, :name, :email]
|
386
|
+
end
|
387
|
+
|
388
|
+
it "should honor the :except option" do
|
389
|
+
p = Post.first
|
390
|
+
attrs = p.api_attributes(:except => :id)
|
391
|
+
|
392
|
+
attrs.keys.should == [:title, :content, :created_at, :author, :topics]
|
393
|
+
attrs[:author].keys.should == [:id, :first_name, :last_name, :name, :email]
|
394
|
+
|
395
|
+
attrs = p.api_attributes(:except => [:id, :title])
|
396
|
+
attrs.keys.should == [:content, :created_at, :author, :topics]
|
397
|
+
attrs[:author].keys.should == [:id, :first_name, :last_name, :name, :email]
|
398
|
+
end
|
399
|
+
|
400
|
+
it "should let :only take precendence over :except" do
|
401
|
+
p = Post.first
|
402
|
+
attrs = p.api_attributes(:only => [:id, :title], :except => :title)
|
403
|
+
|
404
|
+
attrs.keys.should == [:id, :title]
|
405
|
+
end
|
406
|
+
|
407
|
+
it "should let :include specify optional attributes" do
|
408
|
+
p = Post.first
|
409
|
+
|
410
|
+
attrs = p.api_attributes(:include => :heavy_attr)
|
411
|
+
attrs.keys.should == [:id, :title, :content, :created_at, :heavy_attr, :author, :topics]
|
412
|
+
end
|
413
|
+
|
414
|
+
it "should allow specifying an unknown :include" do
|
415
|
+
p = Post.first
|
416
|
+
|
417
|
+
attrs = p.api_attributes(:include => :unknown)
|
418
|
+
attrs.keys.should == [:id, :title, :content, :created_at, :author, :topics]
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should let :include specify optional attribute groups" do
|
422
|
+
p = Post.first
|
423
|
+
|
424
|
+
attrs = p.api_attributes(:include => :extended_attrs)
|
425
|
+
attrs.keys.should == [:id, :title, :content, :created_at, :heavy_attr, :author, :topics]
|
426
|
+
end
|
427
|
+
|
428
|
+
it "should use :include option to retrieve optional associations" do
|
429
|
+
a = Author.first
|
430
|
+
|
431
|
+
attrs = a.api_attributes
|
432
|
+
attrs.should_not have_key(:posts)
|
433
|
+
attrs.should_not have_key(:author_group)
|
434
|
+
|
435
|
+
attrs = a.api_attributes(:include => :author_group)
|
436
|
+
attrs.should_not have_key(:posts)
|
437
|
+
attrs.should have_key(:author_group)
|
438
|
+
|
439
|
+
p = Post.first
|
440
|
+
attrs = p.api_attributes(:include => {:author => {:include => :posts}})
|
441
|
+
attrs[:author].should have_key(:posts)
|
442
|
+
attrs[:author][:posts].size.should == 3
|
443
|
+
end
|
444
|
+
|
445
|
+
it "should serialize a polymorphic one-one association" do
|
446
|
+
attrs = Author.find(5).api_attributes
|
447
|
+
attrs[:email][:special_email].should == 'special'
|
448
|
+
attrs[:email][:email].should == "author5@email.com"
|
449
|
+
end
|
450
|
+
|
451
|
+
it "should serialize a polymorphic collection" do
|
452
|
+
attrs = Post.first.api_attributes(:include => :comments)
|
453
|
+
|
454
|
+
attrs[:comments].should have(3).items
|
455
|
+
attrs[:comments][0].should_not have_key(:type)
|
456
|
+
attrs[:comments][1].should_not have_key(:type)
|
457
|
+
attrs[:comments].last[:type].should == 'Reply'
|
458
|
+
end
|
459
|
+
|
460
|
+
it "must allow including both optional attributes and associations with mixed arrays and hashes" do
|
461
|
+
p = Post.first
|
462
|
+
includes = {:include => [:extended_attrs, {:author => {:include => [:posts, :author_group]}}, :comments]}
|
463
|
+
attrs = p.api_attributes(includes)
|
464
|
+
|
465
|
+
attrs.keys.should == [:id, :title, :content, :created_at, :heavy_attr, :author, :topics, :comments]
|
466
|
+
attrs[:author].should have_key(:posts)
|
467
|
+
attrs[:author].should have_key(:author_group)
|
468
|
+
attrs[:comments].should have(3).items
|
469
|
+
end
|
470
|
+
|
471
|
+
it "should process includes specified in a hash" do
|
472
|
+
p = Post.first
|
473
|
+
includes = {:include => {:author => {:include => [:posts, :author_group]}, :comments => {}}}
|
474
|
+
attrs = p.api_attributes(includes)
|
475
|
+
|
476
|
+
attrs.keys.should == [:id, :title, :content, :created_at, :author, :topics, :comments]
|
477
|
+
attrs[:author].should have_key(:posts)
|
478
|
+
attrs[:author].should have_key(:author_group)
|
479
|
+
attrs[:comments].should have(3).items
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
|
484
|
+
describe "AttributeMap Deerializer" do
|
485
|
+
it "handles inputting attributes from a hash for new objects" do
|
486
|
+
h = {:title => "New title", :content => "New content"}
|
487
|
+
|
488
|
+
p = Post.new
|
489
|
+
p.api_attributes = h
|
490
|
+
p.attributes.should == {"id" => nil, "title"=>"New title", "content"=>"New content", "author_id"=>nil, "created_at"=>nil}
|
491
|
+
end
|
492
|
+
|
493
|
+
it "handles inputting attributes from a hash for existing objects" do
|
494
|
+
h = {:title => "New title", :content => "New content"}
|
495
|
+
|
496
|
+
p = Post.first
|
497
|
+
p.api_attributes = h
|
498
|
+
p.title.should == 'New title'
|
499
|
+
p.content.should == 'New content'
|
500
|
+
p.author_id.should_not be_nil
|
501
|
+
p.created_at.should_not be_nil
|
502
|
+
end
|
503
|
+
|
504
|
+
it "allows unrecognized and unspecified attributes but not set their values" do
|
505
|
+
h = {:title => "New title", :content => "New content", :author_id => 1, :unknown => 'sdfsdf'}
|
506
|
+
|
507
|
+
p = Post.new
|
508
|
+
p.api_attributes = h
|
509
|
+
|
510
|
+
p.attributes.should == {"id" => nil, "created_at"=>nil, "title"=>"New title", "content"=>"New content", "author_id"=>nil}
|
511
|
+
p[:unknown].should be_nil
|
512
|
+
end
|
513
|
+
|
514
|
+
it "does not overwrite read-only attributes" do
|
515
|
+
h = {:created_at => nil}
|
516
|
+
|
517
|
+
p = Post.first
|
518
|
+
p.api_attributes = h
|
519
|
+
p.changed?.should be_false
|
520
|
+
p.created_at.should_not be_nil
|
521
|
+
end
|
522
|
+
|
523
|
+
it "should let you specify an external name that is different from internal name" do
|
524
|
+
Post.map_attributes :test do
|
525
|
+
attribute :public_title, :model_attr => :title
|
526
|
+
end
|
527
|
+
|
528
|
+
p = Post.first
|
529
|
+
p.test_attributes = {:public_title => 'New title'}
|
530
|
+
p.title.should == 'New title'
|
531
|
+
end
|
532
|
+
|
533
|
+
it "should handle updating a one-to-one association attributes" do
|
534
|
+
h = {:author => {:id => 1, :first_name => 'New', :email => {:email => 'new_email'}}}
|
535
|
+
|
536
|
+
p = Post.first
|
537
|
+
p.api_attributes = h
|
538
|
+
|
539
|
+
p.author_id.should == 1
|
540
|
+
p.created_at.should_not be_nil
|
541
|
+
p.author.changed?.should be_true
|
542
|
+
p.author.first_name.should == 'New'
|
543
|
+
p.author.last_name.should == 'Smith'
|
544
|
+
p.author.email.email_addr.should == 'new_email'
|
545
|
+
end
|
546
|
+
|
547
|
+
it "must not overwrite read-only associations" do
|
548
|
+
Post.map_attributes :test do
|
549
|
+
association :author, :access => :ro
|
550
|
+
end
|
551
|
+
Author.map_attributes :test do
|
552
|
+
attribute :first_name
|
553
|
+
attribute :last_name
|
554
|
+
end
|
555
|
+
|
556
|
+
p = Post.first
|
557
|
+
p.test_attributes = {:author => {:first_name => 'New'}}
|
558
|
+
p.author.changed?.should be_false
|
559
|
+
p.author.first_name.should_not == 'New'
|
560
|
+
end
|
561
|
+
|
562
|
+
it "lets you specify an association external name that different that the internal name" do
|
563
|
+
Post.accepts_nested_attributes_for(:author, :update_only => true)
|
564
|
+
Post.map_attributes :test do
|
565
|
+
association :public_author, :using => :author
|
566
|
+
end
|
567
|
+
Author.map_attributes :test do
|
568
|
+
attribute :first_name
|
569
|
+
attribute :last_name
|
570
|
+
end
|
571
|
+
|
572
|
+
p = Post.first
|
573
|
+
p.test_attributes = {:public_author => {:first_name => 'New'}}
|
574
|
+
|
575
|
+
p.author_id.should == 1
|
576
|
+
p.author.changed?.should be_true
|
577
|
+
p.author.first_name.should == 'New'
|
578
|
+
end
|
579
|
+
|
580
|
+
it "sets STI base class attributes and associations when writing to subclass" do
|
581
|
+
r = Reply.new
|
582
|
+
r.api_attributes = {:content => 'Content for test', :post => {:title => 'STI Title'}}
|
583
|
+
|
584
|
+
r.content.should == 'Content for test'
|
585
|
+
r.post.id.should == nil
|
586
|
+
r.post.title.should == 'STI Title'
|
587
|
+
r.type.should == 'Reply'
|
588
|
+
end
|
589
|
+
|
590
|
+
it "should delete a one-one association item" do
|
591
|
+
h = {:email => {:id => 4, :_destroy => true}}
|
592
|
+
|
593
|
+
a = Author.find(4)
|
594
|
+
a.api_attributes = h
|
595
|
+
|
596
|
+
a.email.should be_marked_for_destruction
|
597
|
+
end
|
598
|
+
|
599
|
+
it "should add an item to a one-many association" do
|
600
|
+
h = {:comments => [{:commentor => 'A Commentor', :content => 'Some new comment'}]}
|
601
|
+
|
602
|
+
p = Post.first
|
603
|
+
p.api_attributes = h
|
604
|
+
|
605
|
+
p.comments.should have(4).items
|
606
|
+
p.comments.last.new_record?.should be_true
|
607
|
+
end
|
608
|
+
|
609
|
+
it "should update an item to a one-many association" do
|
610
|
+
h = {:comments => [{:id => 1, :commentor => 'A Commentor'}]}
|
611
|
+
|
612
|
+
p = Post.first
|
613
|
+
p.api_attributes = h
|
614
|
+
|
615
|
+
p.comments.should have(3).items
|
616
|
+
p.comments.first.new_record?.should be_false
|
617
|
+
p.comments.first.should be_changed
|
618
|
+
p.comments.first.commentor.should == 'A Commentor'
|
619
|
+
p.comments.first.content.should == 'Comment 1 for post 1'
|
620
|
+
end
|
621
|
+
|
622
|
+
it "should delete an item from a one-many association" do
|
623
|
+
h = {:comments => [{:id => 1, :_destroy => true}]}
|
624
|
+
|
625
|
+
p = Post.first
|
626
|
+
p.api_attributes = h
|
627
|
+
|
628
|
+
p.comments.should have(3).items
|
629
|
+
p.comments.first.should be_marked_for_destruction
|
630
|
+
end
|
631
|
+
|
632
|
+
it "should not allow delete if the association map spec doesn't allow it" do
|
633
|
+
Comment.map_attributes :test do
|
634
|
+
attribute :content
|
635
|
+
end
|
636
|
+
Post.has_many :comments_no_delete, :class_name => 'Comment'
|
637
|
+
Post.accepts_nested_attributes_for(:comments_no_delete, :allow_destroy => false)
|
638
|
+
Post.attr_accessible(:comments_no_delete_attributes)
|
639
|
+
Post.map_attributes :test do
|
640
|
+
attribute :title
|
641
|
+
association :comments_no_delete
|
642
|
+
end
|
643
|
+
|
644
|
+
h = {:comments => [{:id => 1, :_destroy => true}]}
|
645
|
+
|
646
|
+
p = Post.first
|
647
|
+
p.test_attributes = h
|
648
|
+
|
649
|
+
p.comments.should have(3).items
|
650
|
+
p.comments.first.should_not be_marked_for_destruction
|
651
|
+
end
|
652
|
+
|
653
|
+
it "should serialize a polymorphic collection" do
|
654
|
+
attrs = Post.first.api_attributes(:include => :comments)
|
655
|
+
|
656
|
+
attrs[:comments].should have(3).items
|
657
|
+
attrs[:comments][0].should_not have_key(:type)
|
658
|
+
attrs[:comments][1].should_not have_key(:type)
|
659
|
+
attrs[:comments].last[:type].should == 'Reply'
|
660
|
+
end
|
661
|
+
|
662
|
+
end
|