protip 0.12.4 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/protip/resource.rb +32 -2
- data/lib/protip/wrapper.rb +6 -2
- data/test/functional/protip/resource_test.rb +1 -0
- data/test/test_helper.rb +1 -1
- data/test/unit/protip/resource_test.rb +67 -10
- data/test/unit/protip/wrapper_test.rb +20 -6
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1170fab1e59f11eaf4e5d9fa6180d7086e5fe5fc
|
4
|
+
data.tar.gz: dece3a11f6abd2f216e78ee7df65245b03dd1c55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07f0f92583e771802269412f33d383bcf878d35939b97906db016fa8c3af12500bdcffe9a5eb6545233f4cbfc878c2a842a01db66d54a8463ec2b18a45080684
|
7
|
+
data.tar.gz: dfaf66f2608d722e89b64bbbf144c12d36e788205e69431c528e7678507f487419a50c90e061a6fadc55d1eee5163e02e5a0acb5eb5a6675cc77c7f458e6cdf6
|
data/lib/protip/resource.rb
CHANGED
@@ -13,6 +13,9 @@ require 'active_model/naming'
|
|
13
13
|
require 'active_model/translation'
|
14
14
|
require 'active_model/errors'
|
15
15
|
|
16
|
+
require 'active_model/attribute_methods' # ActiveModel::Dirty depends on this
|
17
|
+
require 'active_model/dirty'
|
18
|
+
|
16
19
|
require 'forwardable'
|
17
20
|
|
18
21
|
require 'protip/error'
|
@@ -129,6 +132,8 @@ module Protip
|
|
129
132
|
include ActiveModel::Validations
|
130
133
|
include ActiveModel::Conversion
|
131
134
|
|
135
|
+
include ActiveModel::Dirty
|
136
|
+
|
132
137
|
included do
|
133
138
|
extend ActiveModel::Naming
|
134
139
|
extend ActiveModel::Translation
|
@@ -136,7 +141,6 @@ module Protip
|
|
136
141
|
|
137
142
|
def_delegator :@wrapper, :message
|
138
143
|
def_delegator :@wrapper, :as_json
|
139
|
-
def_delegator :@wrapper, :assign_attributes
|
140
144
|
end
|
141
145
|
module ClassMethods
|
142
146
|
|
@@ -166,12 +170,23 @@ module Protip
|
|
166
170
|
@message = message
|
167
171
|
@message.descriptor.each do |field|
|
168
172
|
def_delegator :@wrapper, :"#{field.name}"
|
169
|
-
def_delegator :@wrapper, :"#{field.name}="
|
170
173
|
if ::Protip::Wrapper.matchable?(field)
|
171
174
|
def_delegator :@wrapper, :"#{field.name}?"
|
172
175
|
end
|
176
|
+
|
177
|
+
define_method "#{field.name}=" do |new_value|
|
178
|
+
old_wrapped_value = @wrapper.send(field.name)
|
179
|
+
@wrapper.send("#{field.name}=", new_value)
|
180
|
+
new_wrapped_value = @wrapper.send(field.name)
|
181
|
+
|
182
|
+
# needed for ActiveModel::Dirty
|
183
|
+
send("#{field.name}_will_change!") if new_wrapped_value != old_wrapped_value
|
184
|
+
end
|
173
185
|
end
|
174
186
|
|
187
|
+
# needed for ActiveModel::Dirty
|
188
|
+
define_attribute_methods @message.descriptor.map(&:name)
|
189
|
+
|
175
190
|
# Validate arguments
|
176
191
|
actions.map!{|action| action.to_sym}
|
177
192
|
(actions - %i(show index create update destroy)).each do |action|
|
@@ -258,6 +273,12 @@ module Protip
|
|
258
273
|
super()
|
259
274
|
end
|
260
275
|
|
276
|
+
def assign_attributes(attributes)
|
277
|
+
# the resource needs to call its own setters so that fields get marked as dirty
|
278
|
+
attributes.each { |field_name, value| send("#{field_name}=", value) }
|
279
|
+
nil # return nil to match ActiveRecord behavior
|
280
|
+
end
|
281
|
+
|
261
282
|
def message=(message)
|
262
283
|
@wrapper = Protip::Wrapper.new(message, self.class.converter)
|
263
284
|
end
|
@@ -271,6 +292,7 @@ module Protip
|
|
271
292
|
else
|
272
293
|
create!
|
273
294
|
end
|
295
|
+
changes_applied
|
274
296
|
rescue Protip::UnprocessableEntityError => error
|
275
297
|
success = false
|
276
298
|
error.errors.messages.each do |message|
|
@@ -298,5 +320,13 @@ module Protip
|
|
298
320
|
def errors
|
299
321
|
@errors ||= ActiveModel::Errors.new(self)
|
300
322
|
end
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
# needed for ActiveModel::Dirty
|
327
|
+
def changes_applied
|
328
|
+
@previously_changed = changes
|
329
|
+
@changed_attributes.clear
|
330
|
+
end
|
301
331
|
end
|
302
332
|
end
|
data/lib/protip/wrapper.rb
CHANGED
@@ -28,7 +28,9 @@ module Protip
|
|
28
28
|
def method_missing(name, *args)
|
29
29
|
if (name =~ /=$/ && field = message.class.descriptor.detect{|field| :"#{field.name}=" == name})
|
30
30
|
raise ArgumentError unless args.length == 1
|
31
|
-
|
31
|
+
attributes = {}.tap { |hash| hash[field.name] = args[0] }
|
32
|
+
assign_attributes attributes
|
33
|
+
args[0] # return the input value (to match ActiveRecord behavior)
|
32
34
|
elsif (name =~ /\?$/ && field = message.class.descriptor.detect{|field| self.class.matchable?(field) && :"#{field.name}?" == name})
|
33
35
|
raise ArgumentError unless args.length == 1
|
34
36
|
matches? field, args[0]
|
@@ -98,9 +100,11 @@ module Protip
|
|
98
100
|
if field.type == :message && !converter.convertible?(field.subtype.msgclass)
|
99
101
|
if value.is_a?(field.subtype.msgclass) # If a message, set it directly
|
100
102
|
set(field, value)
|
101
|
-
|
103
|
+
elsif value.is_a?(Hash) # If a hash, pass it through to the nested message
|
102
104
|
wrapper = get(field) || build(field.name) # Create the field if it doesn't already exist
|
103
105
|
wrapper.assign_attributes value
|
106
|
+
else # If value is a simple type (e.g. nil), set the value directly
|
107
|
+
set(field, value)
|
104
108
|
end
|
105
109
|
# Otherwise, if the field is a convertible message or a simple type, we set the value directly
|
106
110
|
else
|
data/test/test_helper.rb
CHANGED
@@ -112,7 +112,7 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'checks with the converter when setting message types' do
|
115
|
-
converter.expects(:convertible?).
|
115
|
+
converter.expects(:convertible?).at_least_once.with(nested_message_class).returns(false)
|
116
116
|
resource = resource_class.new
|
117
117
|
assert_raises(ArgumentError) do
|
118
118
|
resource.nested_message = 5
|
@@ -120,9 +120,9 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
120
120
|
end
|
121
121
|
|
122
122
|
it 'converts message types to and from their Ruby values when the converter allows' do
|
123
|
-
converter.expects(:convertible?).
|
123
|
+
converter.expects(:convertible?).at_least_once.with(nested_message_class).returns(true)
|
124
124
|
converter.expects(:to_message).once.with(6, nested_message_class).returns(nested_message_class.new number: 100)
|
125
|
-
converter.expects(:to_object).
|
125
|
+
converter.expects(:to_object).at_least_once.with(nested_message_class.new number: 100).returns 'intern'
|
126
126
|
|
127
127
|
resource = resource_class.new
|
128
128
|
resource.nested_message = 6
|
@@ -434,13 +434,41 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
434
434
|
attrs = {id: 2}
|
435
435
|
assert_equal resource_message_class.new(attrs), resource_class.new(attrs).message
|
436
436
|
end
|
437
|
+
end
|
438
|
+
end
|
437
439
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
440
|
+
describe 'attribute writer' do
|
441
|
+
before do
|
442
|
+
resource_class.class_exec(resource_message_class) do |resource_message_class|
|
443
|
+
resource actions: [], message: resource_message_class
|
442
444
|
end
|
443
445
|
end
|
446
|
+
|
447
|
+
it 'delegates writes to the wrapper object' do
|
448
|
+
resource = resource_class.new
|
449
|
+
test_string = 'new'
|
450
|
+
Protip::Wrapper.any_instance.expects(:string=).with(test_string)
|
451
|
+
resource.string = test_string
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'marks the resource and attribute as changed if the value is changed' do
|
455
|
+
resource = resource_class.new string: 'original'
|
456
|
+
resource.string = 'new'
|
457
|
+
assert resource.changed?, 'resource should be marked as changed'
|
458
|
+
assert resource.string_changed?, 'string field should be marked as changed'
|
459
|
+
end
|
460
|
+
|
461
|
+
it 'does not mark the resource and attribute as changed if the value is not changed' do
|
462
|
+
resource = resource_class.new string: 'original'
|
463
|
+
resource.send :changes_applied # clear the changes
|
464
|
+
# establish that the changes were cleared
|
465
|
+
assert !resource.changed?, 'resource should be not marked as changed'
|
466
|
+
assert !resource.string_changed?, 'string field should not be marked as changed'
|
467
|
+
|
468
|
+
resource.string = 'original'
|
469
|
+
assert !resource.changed?, 'resource should be not marked as changed'
|
470
|
+
assert !resource.string_changed?, 'string field should not be marked as changed'
|
471
|
+
end
|
444
472
|
end
|
445
473
|
|
446
474
|
describe '#assign_attributes' do
|
@@ -449,11 +477,17 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
449
477
|
resource actions: [], message: resource_message_class
|
450
478
|
end
|
451
479
|
end
|
452
|
-
|
480
|
+
|
481
|
+
it 'calls the attribute writer for each attribute' do
|
453
482
|
resource = resource_class.new
|
483
|
+
test_string = 'whodunnit'
|
484
|
+
resource.expects(:string=).with(test_string)
|
485
|
+
resource.assign_attributes(string: test_string)
|
486
|
+
end
|
454
487
|
|
455
|
-
|
456
|
-
|
488
|
+
it 'returns nil' do
|
489
|
+
resource = resource_class.new
|
490
|
+
assert_nil resource.assign_attributes(string: 'asdf')
|
457
491
|
end
|
458
492
|
end
|
459
493
|
|
@@ -495,6 +529,14 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
495
529
|
resource.save
|
496
530
|
assert_equal response, resource.message
|
497
531
|
end
|
532
|
+
|
533
|
+
it 'marks changes as applied' do
|
534
|
+
client.stubs(:request).returns(response)
|
535
|
+
resource = resource_class.new(string: 'time')
|
536
|
+
assert resource.string_changed?, 'string should initially be changed'
|
537
|
+
assert resource.save
|
538
|
+
assert !resource.string_changed?, 'string should no longer be changed after save'
|
539
|
+
end
|
498
540
|
end
|
499
541
|
|
500
542
|
describe 'for an existing record' do
|
@@ -528,6 +570,14 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
528
570
|
resource.save
|
529
571
|
assert_equal response, resource.message
|
530
572
|
end
|
573
|
+
|
574
|
+
it 'marks changes as applied' do
|
575
|
+
client.stubs(:request).returns(response)
|
576
|
+
resource = resource_class.new id: 5, string: 'new_string'
|
577
|
+
assert resource.string_changed?, 'string should initially be changed'
|
578
|
+
assert resource.save
|
579
|
+
assert !resource.string_changed?, 'string should no longer be changed after save'
|
580
|
+
end
|
531
581
|
end
|
532
582
|
|
533
583
|
describe 'when validation errors are thrown' do
|
@@ -567,6 +617,13 @@ module Protip::ResourceTest # Namespace for internal constants
|
|
567
617
|
it 'returns false' do
|
568
618
|
refute @resource.save, 'save returned true'
|
569
619
|
end
|
620
|
+
|
621
|
+
it 'does not mark changes as applied' do
|
622
|
+
@resource.string = 'new_string'
|
623
|
+
assert @resource.string_changed?, 'string should initially be changed'
|
624
|
+
refute @resource.save
|
625
|
+
assert @resource.string_changed?, 'string should still be changed after unsuccessful save'
|
626
|
+
end
|
570
627
|
end
|
571
628
|
end
|
572
629
|
|
@@ -216,6 +216,13 @@ module Protip::WrapperTest # namespace for internal constants
|
|
216
216
|
wrapper.assign_attributes inner: {value: 50, note: 'noted'}
|
217
217
|
end
|
218
218
|
|
219
|
+
it "sets message fields to nil when they're assigned nil" do
|
220
|
+
wrapped_message.inner = inner_message_class.new(value: 60)
|
221
|
+
assert wrapped_message.inner
|
222
|
+
wrapper.assign_attributes inner: nil
|
223
|
+
assert_nil wrapped_message.inner
|
224
|
+
end
|
225
|
+
|
219
226
|
it 'allows messages to be assigned directly' do
|
220
227
|
message = inner_message_class.new
|
221
228
|
wrapper.assign_attributes inner: message
|
@@ -341,7 +348,7 @@ module Protip::WrapperTest # namespace for internal constants
|
|
341
348
|
end
|
342
349
|
end
|
343
350
|
|
344
|
-
describe '
|
351
|
+
describe 'attribute writer' do # generated via method_missing?
|
345
352
|
it 'does not convert simple fields' do
|
346
353
|
converter.expects(:convertible?).never
|
347
354
|
converter.expects(:to_message).never
|
@@ -351,7 +358,7 @@ module Protip::WrapperTest # namespace for internal constants
|
|
351
358
|
end
|
352
359
|
|
353
360
|
it 'converts convertible messages' do
|
354
|
-
converter.expects(:convertible?).with(inner_message_class).
|
361
|
+
converter.expects(:convertible?).at_least_once.with(inner_message_class).returns(true)
|
355
362
|
converter.expects(:to_message).with(40, inner_message_class).returns(inner_message_class.new(value: 30))
|
356
363
|
|
357
364
|
wrapper.inner = 40
|
@@ -359,7 +366,7 @@ module Protip::WrapperTest # namespace for internal constants
|
|
359
366
|
end
|
360
367
|
|
361
368
|
it 'removes message fields when assigning nil' do
|
362
|
-
converter.expects(:convertible?).
|
369
|
+
converter.expects(:convertible?).at_least_once.with(inner_message_class).returns(false)
|
363
370
|
converter.expects(:to_message).never
|
364
371
|
|
365
372
|
wrapper.inner = nil
|
@@ -367,7 +374,7 @@ module Protip::WrapperTest # namespace for internal constants
|
|
367
374
|
end
|
368
375
|
|
369
376
|
it 'raises an error when setting inconvertible messages' do
|
370
|
-
converter.expects(:convertible?).with(inner_message_class).
|
377
|
+
converter.expects(:convertible?).at_least_once.with(inner_message_class).returns(false)
|
371
378
|
converter.expects(:to_message).never
|
372
379
|
assert_raises ArgumentError do
|
373
380
|
wrapper.inner = 'cannot convert me'
|
@@ -375,10 +382,12 @@ module Protip::WrapperTest # namespace for internal constants
|
|
375
382
|
end
|
376
383
|
|
377
384
|
it 'passes through messages without checking whether they are convertible' do
|
385
|
+
converter.expects(:convertible?).once.returns(true)
|
386
|
+
message = inner_message_class.new(value: 50)
|
387
|
+
|
378
388
|
converter.expects(:convertible?).never
|
379
389
|
converter.expects(:to_message).never
|
380
|
-
|
381
|
-
wrapper.inner = inner_message_class.new(value: 50)
|
390
|
+
wrapper.inner = message
|
382
391
|
assert_equal inner_message_class.new(value: 50), wrapper.message.inner
|
383
392
|
end
|
384
393
|
|
@@ -410,6 +419,11 @@ module Protip::WrapperTest # namespace for internal constants
|
|
410
419
|
wrapper.number = m
|
411
420
|
assert_equal :ONE, wrapper.number
|
412
421
|
end
|
422
|
+
|
423
|
+
it 'returns the input value' do
|
424
|
+
input_value = 'str'
|
425
|
+
assert_equal input_value, wrapper.send(:string=, input_value)
|
426
|
+
end
|
413
427
|
end
|
414
428
|
|
415
429
|
describe '#matches?' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- AngelList
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -182,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
182
|
version: '0'
|
183
183
|
requirements: []
|
184
184
|
rubyforge_project:
|
185
|
-
rubygems_version: 2.
|
185
|
+
rubygems_version: 2.2.2
|
186
186
|
signing_key:
|
187
187
|
specification_version: 4
|
188
188
|
summary: Resources backed by protobuf messages
|