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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ddcb2b4b3bd6f69e1166447dffb4dc3aa02760c
4
- data.tar.gz: 8dedbff1aff8667b6efb681076434fd8dc0405f3
3
+ metadata.gz: 1170fab1e59f11eaf4e5d9fa6180d7086e5fe5fc
4
+ data.tar.gz: dece3a11f6abd2f216e78ee7df65245b03dd1c55
5
5
  SHA512:
6
- metadata.gz: f225ba2453ee71bb97c0546f5fb4e702b96f046c612bd85c6b3277658c417138b17bb812c6895f5c6ebace61d8bddf13d792357c11c9ac785bededf41c1426c7
7
- data.tar.gz: 600c78b9cb83e256ff1fc9cef872bb68de7040c9a2b6b6a37e4f74c243ac5d83b8e5bf10474254767406458712238f5fd854a5838340d95a5a33c1e781c719b5
6
+ metadata.gz: 07f0f92583e771802269412f33d383bcf878d35939b97906db016fa8c3af12500bdcffe9a5eb6545233f4cbfc878c2a842a01db66d54a8463ec2b18a45080684
7
+ data.tar.gz: dfaf66f2608d722e89b64bbbf144c12d36e788205e69431c528e7678507f487419a50c90e061a6fadc55d1eee5163e02e5a0acb5eb5a6675cc77c7f458e6cdf6
@@ -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
@@ -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
- set field, args[0]
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
- else # If a hash, pass it through to the nested message
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
@@ -1,6 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  require 'protip'
4
+ require 'google/protobuf/wrappers'
4
5
 
5
6
  describe 'Protip::Resource (functional)' do
6
7
 
data/test/test_helper.rb CHANGED
@@ -2,4 +2,4 @@ require 'minitest/autorun'
2
2
  require 'mocha/mini_test'
3
3
  require 'webmock/minitest'
4
4
 
5
- require 'minitest/pride'
5
+ require 'minitest/pride'
@@ -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?).once.with(nested_message_class).returns(false)
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?).times(2).with(nested_message_class).returns(true)
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).once.with(nested_message_class.new number: 100).returns 'intern'
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
- it 'delegates to #assign_attributes on its wrapper object when a hash is given' do
439
- attrs = {id: 3}
440
- Protip::Wrapper.any_instance.expects(:assign_attributes).once.with({id: 3})
441
- resource_class.new(attrs)
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
- it 'delegates to #assign_attributes on its wrapper object' do
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
- Protip::Wrapper.any_instance.expects(:assign_attributes).once.with(string: 'whodunnit').returns('boo')
456
- assert_equal 'boo', resource.assign_attributes(string: 'whodunnit')
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 '#set' do
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).once.returns(true)
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?).never
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).once.returns(false)
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.12.4
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-09-12 00:00:00.000000000 Z
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.4.5
185
+ rubygems_version: 2.2.2
186
186
  signing_key:
187
187
  specification_version: 4
188
188
  summary: Resources backed by protobuf messages