protip 0.12.4 → 0.13.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 +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
|