protip 0.18.5 → 0.18.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8aa9bb432c7fae05ac9e7f790545b1f50868bba1
4
- data.tar.gz: d1abe3cf1d8a52ca3a13002ec89e520831cc8ed6
3
+ metadata.gz: a9c25de6d9f409c5bb8c1659951ecc4fa10a5aa4
4
+ data.tar.gz: aab7d4182c10f0a540dd5762ea3921dac899c2b1
5
5
  SHA512:
6
- metadata.gz: 207674ca4d193805cab007b1ec73303d3ec3701186f3471245cd86f366fd6d967b25ad44982d02fcc5e0b44e1257e7bac93501db953de5acc1ae2047b5ca079a
7
- data.tar.gz: 136aca0f48b4d564370bfe6674758ea8e67d2c32b29c2cc9d922e240d70cf9ccd4400ca590a2aed8eaa234a7d9a8bcd318a49cbcc1686b514d38bfb485391750
6
+ metadata.gz: 530e2c08bf013f25afea7d4be6eb254b85b192fb8ca374ceb09ef48ce3e9e703926ca07335bac5396edcacc129f29710cb2cb9e1b619508210ba39c8170335f9
7
+ data.tar.gz: d65a08e07c0365afadfdf614f731bcf5939deed2e463f4e3d4159f70b0691e1d9735567f33dd1edcf0d7c893f919a128f01e0e634aceec33c4761422aa502e81
@@ -132,12 +132,13 @@ module Protip
132
132
  end
133
133
 
134
134
  define_method "#{field.name}=" do |new_value|
135
- old_wrapped_value = @wrapper.send(field.name)
135
+ old_value = self.message[field.name] # Only compare the raw values
136
136
  @wrapper.send("#{field.name}=", new_value)
137
- new_wrapped_value = @wrapper.send(field.name)
137
+ new_value = self.message[field.name]
138
138
 
139
- # needed for ActiveModel::Dirty
140
- send("#{field.name}_will_change!") if new_wrapped_value != old_wrapped_value
139
+ # Need to check that types are the same first, otherwise protobuf gets mad comparing
140
+ # messages with non-messages
141
+ send("#{field.name}_will_change!") unless new_value.class == old_value.class && new_value == old_value
141
142
  end
142
143
 
143
144
  # needed for ActiveModel::Dirty
@@ -263,8 +264,23 @@ module Protip
263
264
  end
264
265
 
265
266
  def assign_attributes(attributes)
266
- # the resource needs to call its own setters so that fields get marked as dirty
267
- attributes.each { |field_name, value| send("#{field_name}=", value) }
267
+ old_attributes = {}
268
+ descriptor = message.class.descriptor
269
+ attributes.keys.each do |key|
270
+ field = descriptor.lookup(key.to_s)
271
+ value = message[key.to_s]
272
+ # If the current value is a message, we need to clone it to get a reasonable comparison later,
273
+ # since we might just assign attributes to the current instance of the message directly
274
+ old_attributes[key] = field && field.type == :message && value ? value.clone : value
275
+ end
276
+ @wrapper.assign_attributes attributes
277
+ attributes.keys.each do |key|
278
+ old_value = old_attributes[key]
279
+ new_value = message[key.to_s]
280
+ unless old_value.class == new_value.class && old_value == new_value
281
+ send "#{key}_will_change!"
282
+ end
283
+ end
268
284
  nil # return nil to match ActiveRecord behavior
269
285
  end
270
286
 
@@ -115,17 +115,11 @@ module Protip
115
115
  field = message.class.descriptor.lookup(field_name.to_s) ||
116
116
  (raise ArgumentError.new("Unrecognized field: #{field_name}"))
117
117
 
118
- # For inconvertible nested messages, the value should be either a hash or a message
119
- if field.type == :message && !converter.convertible?(field.subtype.msgclass)
120
- if value.is_a?(field.subtype.msgclass) # If a message, set it directly
121
- set(field, value)
122
- elsif value.is_a?(Hash) # If a hash, pass it through to the nested message
123
- wrapper = get(field) || build(field.name) # Create the field if it doesn't already exist
124
- wrapper.assign_attributes value
125
- else # If value is a simple type (e.g. nil), set the value directly
126
- set(field, value)
127
- end
128
- # Otherwise, if the field is a convertible message or a simple type, we set the value directly
118
+ # For inconvertible nested messages, we allow a hash to be passed in with nested attributes
119
+ if field.type == :message && !converter.convertible?(field.subtype.msgclass) && value.is_a?(Hash)
120
+ wrapper = get(field) || build(field.name) # Create the field if it doesn't already exist
121
+ wrapper.assign_attributes value
122
+ # Otherwise, if the field is a message (convertible or not) or a simple type, we set the value directly
129
123
  else
130
124
  set(field, value)
131
125
  end
@@ -224,7 +218,7 @@ module Protip
224
218
  elsif nested_resources.has_key?(field.name.to_sym)
225
219
  value.message
226
220
  else
227
- raise ArgumentError.new "Cannot convert from Ruby object: \"#{field}\""
221
+ raise ArgumentError.new "Cannot convert from Ruby object: \"#{field.name}\""
228
222
  end
229
223
  elsif field.type == :enum
230
224
  value.is_a?(Fixnum) ? value : value.to_sym
@@ -515,9 +515,97 @@ module Protip::ResourceTest # Namespace for internal constants
515
515
  attrs = {id: 2}
516
516
  assert_equal resource_message_class.new(attrs), resource_class.new(attrs).message
517
517
  end
518
+
519
+ it 'allows nested attributes to be given' do
520
+ attrs = {
521
+ nested_message: {
522
+ number: 3
523
+ }
524
+ }
525
+ assert_equal nested_message_class.new(number: 3), resource_class.new(attrs).message.nested_message
526
+ end
518
527
  end
519
528
  end
520
529
 
530
+ # Shared behavior for direct setters and #assign_attributes. The setter object must be able to be
531
+ # initialized as +setter_class.new(resource)+, and must respond to +setter.set({field => value, field2 => value2})+
532
+ # by performing the appropriate operation (e.g. +.field=+ or +.assign_attributes+).
533
+ def self.describe_dirty_attributes_setter(setter_class)
534
+
535
+ describe 'dirty attributes' do
536
+ let :converter do
537
+ Class.new do
538
+ include Protip::Converter
539
+ end.new
540
+ end
541
+
542
+ let :resource do
543
+ resource_class.new resource_message_class.new({
544
+ string: 'foo',
545
+ nested_message: nested_message_class.new(number: 32)
546
+ })
547
+ end
548
+
549
+ let :setter do
550
+ setter_class.new(resource)
551
+ end
552
+
553
+ before do
554
+ resource_class.converter = converter
555
+ raise 'sanity check failed' if resource.changed? || resource.string_changed? || resource.nested_message_changed?
556
+ end
557
+
558
+ it 'recognizes changes in scalar values' do
559
+ setter.set string: 'bar'
560
+ assert resource.changed?, 'resource was not marked as changed'
561
+ assert resource.string_changed?, 'field was not marked as changed'
562
+ end
563
+
564
+ it 'recognizes when scalar values do not change' do
565
+ setter.set string: 'foo'
566
+ refute resource.changed?, 'resource was marked as changed'
567
+ refute resource.string_changed?, 'field was marked as changed'
568
+ end
569
+
570
+ describe '(message attributes)' do
571
+ before do
572
+ converter.stubs(:convertible?).with(nested_message_class).returns(true)
573
+ converter.stubs(:to_message).with(42, nested_message_class).returns(nested_message_class.new(number: 52))
574
+ converter.stubs(:to_object).with(nested_message_class.new(number: 52)).returns(42)
575
+ converter.stubs(:to_object).with(nested_message_class.new(number: 62)).returns(72)
576
+ end
577
+ it 'marks convertible messages as changed if they are changed as Ruby values' do
578
+ setter.set nested_message: 42
579
+ assert resource.changed?, 'resource was not marked as changed'
580
+ assert resource.nested_message_changed?, 'field was not marked as changed'
581
+ end
582
+ it 'marks sub-messages as changed if they are changed as messages' do
583
+ setter.set nested_message: nested_message_class.new(number: 62)
584
+ assert resource.changed?, 'resource was not marked as changed'
585
+ assert resource.nested_message_changed?, 'field was not marked as changed'
586
+ end
587
+ it 'marks sub-messages as changed when they are nullified' do
588
+ setter.set nested_message: nil
589
+ assert resource.changed?, 'resource was not marked as changed'
590
+ assert resource.nested_message_changed?, 'field was not marked as changed'
591
+ end
592
+ it 'recognizes when convertible messages are not changed when set as Ruby values' do
593
+ resource.message.nested_message.number = 52
594
+ raise 'sanity check failed' if resource.changed? || resource.nested_message_changed?
595
+ setter.set nested_message: 42
596
+ refute resource.changed?, 'resource was marked as changed'
597
+ refute resource.string_changed?, 'field was marked as changed'
598
+ end
599
+ it 'recognizes when sub-messages are not changed when set as messages' do
600
+ setter.set nested_message: nested_message_class.new(number: 32)
601
+ refute resource.changed?, 'resource was marked as changed'
602
+ refute resource.string_changed?, 'field was marked as changed'
603
+ end
604
+ end
605
+ end
606
+ end
607
+
608
+
521
609
  describe 'attribute writer' do
522
610
  before do
523
611
  resource_class.class_exec(resource_message_class) do |resource_message_class|
@@ -532,24 +620,11 @@ module Protip::ResourceTest # Namespace for internal constants
532
620
  resource.string = test_string
533
621
  end
534
622
 
535
- it 'marks the resource and attribute as changed if the value is changed' do
536
- resource = resource_class.new string: 'original'
537
- resource.string = 'new'
538
- assert resource.changed?, 'resource should be marked as changed'
539
- assert resource.string_changed?, 'string field should be marked as changed'
540
- end
541
-
542
- it 'does not mark the resource and attribute as changed if the value is not changed' do
543
- resource = resource_class.new string: 'original'
544
- resource.send :changes_applied # clear the changes
545
- # establish that the changes were cleared
546
- assert !resource.changed?, 'resource should be not marked as changed'
547
- assert !resource.string_changed?, 'string field should not be marked as changed'
548
-
549
- resource.string = 'original'
550
- assert !resource.changed?, 'resource should be not marked as changed'
551
- assert !resource.string_changed?, 'string field should not be marked as changed'
623
+ setter_class = Class.new do
624
+ def initialize(resource) ; @resource = resource ; end
625
+ def set(attributes) ; attributes.each{|attribute, value| @resource.public_send(:"#{attribute}=", value)} ; end
552
626
  end
627
+ describe_dirty_attributes_setter(setter_class)
553
628
  end
554
629
 
555
630
  describe '#assign_attributes' do
@@ -559,15 +634,55 @@ module Protip::ResourceTest # Namespace for internal constants
559
634
  end
560
635
  end
561
636
 
562
- it 'calls the attribute writer for each attribute' do
563
- resource = resource_class.new
564
- test_string = 'whodunnit'
565
- resource.expects(:string=).with(test_string)
566
- resource.assign_attributes(string: test_string)
637
+ let :resource do
638
+ resource_class.new
639
+ end
640
+
641
+ it 'delegates to #assign_attributes on the wrapper' do
642
+ # Instantiate the resource before setting the expectation, since assign_attributes is allowed to be
643
+ # called during #initialize as well
644
+ resource
645
+
646
+ Protip::Wrapper.any_instance.expects(:assign_attributes).with(string: 'foo')
647
+ resource.assign_attributes string: 'foo'
648
+ end
649
+
650
+ setter_class = Class.new do
651
+ def initialize(resource) ; @resource = resource ; end
652
+ def set(attributes) ; @resource.assign_attributes attributes ; end
653
+ end
654
+ describe_dirty_attributes_setter setter_class
655
+ describe 'dirty attributes (nested hashes)' do
656
+
657
+ it 'marks nested hashes as changed if they set a new field' do
658
+ resource.assign_attributes nested_message: {number: 52}
659
+ assert resource.changed?, 'resource was not marked as changed'
660
+ assert resource.nested_message_changed?, 'field was not marked as changed'
661
+ end
662
+
663
+ describe '(when a nested message has an initial value)' do
664
+ before do
665
+ resource.nested_message = nested_message_class.new(number: 32)
666
+ resource.send(:changes_applied) # Clear the list of changes
667
+ # Sanity check
668
+ raise 'unexpected' if resource.changed? || resource.string_changed? || resource.nested_message_changed?
669
+ end
670
+
671
+ it 'marks nested hashes as changed if they change a field' do
672
+ resource.assign_attributes nested_message: {number: 42}
673
+ assert resource.changed?, 'resource was not marked as changed'
674
+ assert resource.nested_message_changed?, 'field was not marked as changed'
675
+ end
676
+
677
+ it 'does not mark nested hashes as changed if they do not change the underlying message' do
678
+ resource.assign_attributes nested_message: {number: 32}
679
+ refute resource.changed?, 'resource was marked as changed'
680
+ refute resource.nested_message_changed?, 'field was marked as changed'
681
+ end
682
+ end
567
683
  end
568
684
 
569
685
  it 'returns nil' do
570
- resource = resource_class.new
571
686
  assert_nil resource.assign_attributes(string: 'asdf')
572
687
  end
573
688
  end
@@ -255,7 +255,7 @@ module Protip::WrapperTest # namespace for internal constants
255
255
  wrapper.assign_attributes inner: {value: 50, note: 'noted'}
256
256
  end
257
257
 
258
- it "sets message fields to nil when they're assigned nil" do
258
+ it 'sets message fields to nil when they\'re assigned nil' do
259
259
  wrapped_message.inner = inner_message_class.new(value: 60)
260
260
  assert wrapped_message.inner
261
261
  wrapper.assign_attributes inner: nil
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.18.5
4
+ version: 0.18.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - AngelList
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-24 00:00:00.000000000 Z
11
+ date: 2016-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel