protip 0.10.0 → 0.10.4

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: c1e044c1437371db39928f0e1ea08104e1a15554
4
- data.tar.gz: 5672c792162be0304a20520d89d4f0a4a8cf1f4a
3
+ metadata.gz: f78ca1c3460fdd0b52a6c398e2837a0bb6d750fa
4
+ data.tar.gz: 5752105ecb4363cd8a681ea87008ed166c106018
5
5
  SHA512:
6
- metadata.gz: 85bcbbde46db8f52a0b6e75f05b38192cbf1ca8be0c2c9ccd4eed63c3782cae17f09d290fd33a95590ce843c22c9ecd664b7965e07eee213b82a2015a251ca30
7
- data.tar.gz: 0f7b09c74b2c57c16502083be6c0f5e5eeaffcb99fae9e474c6a785359ca8cdfbcc42ce80c052f1a46c566adf6ec97fb21a47d9f2de5fd93b023fed94c5f02e4
6
+ metadata.gz: 39c47fd2e35da1270a206c42785d8c7395f7402dc7c7f14064650970c3b9a1d2996b486c2fe92dc563911045b21484237764cb555bc0c8ed556d4e94db77af4e
7
+ data.tar.gz: f5bd09ed0b9dadff3f20a1723a7142c02588888316239373cd593d90623e558bca94245bc0de27780c517fc2757a97c1dc39ff492115e2382d623730949a7fd0
@@ -0,0 +1,2 @@
1
+ import public "protip/messages/types.proto";
2
+ import public "protip/messages/wrappers.proto";
@@ -0,0 +1,6 @@
1
+ // TODO: Move to plain protip namespace, use Any?
2
+ package Protip.Messages;
3
+
4
+ message Array {
5
+ repeated bytes messages = 1;
6
+ }
@@ -0,0 +1,12 @@
1
+ // TODO: move to plain protip namespace, use map type, change messages -> message
2
+ package Protip.Messages;
3
+
4
+ message Errors {
5
+ repeated string messages = 1;
6
+ repeated FieldError field_errors = 2;
7
+ }
8
+
9
+ message FieldError {
10
+ optional string field = 1;
11
+ optional string message = 2;
12
+ }
@@ -0,0 +1,7 @@
1
+ package protip;
2
+
3
+ message Date {
4
+ required int64 year = 1;
5
+ required uint32 month = 2;
6
+ required uint32 day = 3;
7
+ }
@@ -0,0 +1,60 @@
1
+ package protip;
2
+
3
+ /////
4
+ // Wrappers, copied from https://github.com/google/protobuf/blob/master/src/google/protobuf/wrappers.proto
5
+ // These should exactly match the structure of those built-in types, so that users can safely swap them in
6
+ // when upgrading to proto3.
7
+
8
+ // Wrapper message for double.
9
+ message DoubleValue {
10
+ // The double value.
11
+ required double value = 1;
12
+ }
13
+
14
+ // Wrapper message for float.
15
+ message FloatValue {
16
+ // The float value.
17
+ required float value = 1;
18
+ }
19
+
20
+ // Wrapper message for int64.
21
+ message Int64Value {
22
+ // The int64 value.
23
+ required int64 value = 1;
24
+ }
25
+
26
+ // Wrapper message for uint64.
27
+ message UInt64Value {
28
+ // The uint64 value.
29
+ required uint64 value = 1;
30
+ }
31
+
32
+ // Wrapper message for int32.
33
+ message Int32Value {
34
+ // The int32 value.
35
+ required int32 value = 1;
36
+ }
37
+
38
+ // Wrapper message for uint32.
39
+ message UInt32Value {
40
+ // The uint32 value.
41
+ required uint32 value = 1;
42
+ }
43
+
44
+ // Wrapper message for bool.
45
+ message BoolValue {
46
+ // The bool value.
47
+ required bool value = 1;
48
+ }
49
+
50
+ // Wrapper message for string.
51
+ message StringValue {
52
+ // The string value.
53
+ required string value = 1;
54
+ }
55
+
56
+ // Wrapper message for bytes.
57
+ message BytesValue {
58
+ // The bytes value.
59
+ required bytes value = 1;
60
+ }
@@ -228,17 +228,15 @@ module Protip
228
228
  end
229
229
  end
230
230
 
231
- def initialize(message_or_params = {})
231
+ def initialize(message_or_attributes = {})
232
232
  if self.class.message == nil
233
233
  raise RuntimeError.new('Must define a message class using `resource`')
234
234
  end
235
- if message_or_params.is_a?(self.class.message)
236
- self.message = message_or_params
235
+ if message_or_attributes.is_a?(self.class.message)
236
+ self.message = message_or_attributes
237
237
  else
238
238
  self.message = self.class.message.new
239
- message_or_params.each do |field, value|
240
- public_send :"#{field}=", value
241
- end
239
+ @wrapper.assign_attributes message_or_attributes
242
240
  end
243
241
 
244
242
  super()
@@ -2,6 +2,11 @@ require 'active_support/concern'
2
2
  require 'protobuf'
3
3
 
4
4
  module Protip
5
+
6
+ # Wraps a protobuf message to allow:
7
+ # - getting/setting of certain message fields as if they were more complex Ruby objects
8
+ # - mass assignment of attributes
9
+ # - standardized creation of nested messages that can't be converted to/from Ruby objects
5
10
  class Wrapper
6
11
  attr_reader :message, :converter
7
12
  def initialize(message, converter)
@@ -33,6 +38,68 @@ module Protip
33
38
  end
34
39
  end
35
40
 
41
+ # Create a nested field on our message. For example, given the following definitions:
42
+ #
43
+ # message Inner {
44
+ # optional string str = 1;
45
+ # }
46
+ # message Outer {
47
+ # optional Inner inner = 1;
48
+ # }
49
+ #
50
+ # We could create an inner message using:
51
+ #
52
+ # wrapper = Protip::Wrapper.new(Outer.new, converter)
53
+ # wrapper.inner # => nil
54
+ # wrapper.build(:inner, str: 'example')
55
+ # wrapper.inner.str # => 'example'
56
+ #
57
+ # Rebuilds the field if it's already present. Raises an error if the name of a primitive field
58
+ # or a convertible message field is given.
59
+ #
60
+ # @param field_name [String|Symbol] The field name to build
61
+ # @param attributes [Hash] The initial attributes to set on the field (as parsed by +assign_attributes+)
62
+ # @return [Protip::Wrapper] The created field
63
+ def build(field_name, attributes = {})
64
+
65
+ field = message.class.fields.detect{|field| field.name == field_name.to_sym}
66
+ if !field.is_a?(Protobuf::Field::MessageField)
67
+ raise "Not a message field: #{field_name}"
68
+ elsif converter.convertible?(field.type_class)
69
+ raise "Cannot build a convertible field: #{field.name}"
70
+ end
71
+
72
+ message[field_name] = field.type_class.new
73
+ wrapper = get(field)
74
+ wrapper.assign_attributes attributes
75
+ wrapper
76
+ end
77
+
78
+ # Mass assignment of message attributes. Nested messages will be built if necessary, but not overwritten
79
+ # if they already exist.
80
+ #
81
+ # @param attributes [Hash] The attributes to set. Keys are field names. For primitive fields and message fields
82
+ # which are convertible to/from Ruby objects, values are the same as you'd pass to the field's setter
83
+ # method. For nested message fields which can't be converted to/from Ruby objects, values are attribute
84
+ # hashes which will be passed down to +assign_attributes+ on the nested field.
85
+ # @return [NilClass]
86
+ def assign_attributes(attributes)
87
+ attributes.each do |field_name, value|
88
+ field = message.class.fields.detect{|field| field.name == field_name.to_sym}
89
+
90
+ # For inconvertible nested messages, the value should be a hash - just pass it through to the nested message
91
+ if field.is_a?(Protobuf::Field::MessageField) && !converter.convertible?(field.type_class)
92
+ wrapper = get(field) || build(field.name) # Create the field if it doesn't already exist
93
+ wrapper.assign_attributes value
94
+ # Otherwise, if the field is a convertible message or a simple type, we set the value directly
95
+ else
96
+ set(field, value)
97
+ end
98
+ end
99
+
100
+ nil # Return nil to match ActiveRecord behavior
101
+ end
102
+
36
103
  def as_json
37
104
  json = {}
38
105
  message.class.fields.each do |name|
@@ -281,6 +281,42 @@ module Protip::ResourceTest # Namespace for internal constants
281
281
  end
282
282
  end
283
283
 
284
+ describe '#initialize' do
285
+ it 'fails if a resource definition has not yet been given' do
286
+ error = assert_raises RuntimeError do
287
+ resource_class.new
288
+ end
289
+ assert_equal 'Must define a message class using `resource`', error.message
290
+ end
291
+ describe 'when a message is given' do
292
+ before do
293
+ resource_class.class_eval do
294
+ resource actions: [], message: ResourceMessage
295
+ end
296
+ end
297
+
298
+ it 'creates a resource with an empty message if no attributes are provided' do
299
+ assert_equal ResourceMessage.new, resource_class.new.message
300
+ end
301
+
302
+ it 'allows a message to be provided directly' do
303
+ message = ResourceMessage.new(id: 1)
304
+ assert_equal message, resource_class.new(message).message
305
+ end
306
+
307
+ it 'sets attributes when a hash is given' do
308
+ attrs = {id: 2}
309
+ assert_equal ResourceMessage.new(attrs), resource_class.new(attrs).message
310
+ end
311
+
312
+ it 'delegates to `assign_attributes` on its wrapper object when a hash is given' do
313
+ attrs = {id: 3}
314
+ Protip::Wrapper.any_instance.expects(:assign_attributes).once.with({id: 3})
315
+ resource_class.new(attrs)
316
+ end
317
+ end
318
+ end
319
+
284
320
  describe '#save' do
285
321
  let :response do
286
322
  ResourceMessage.new(string: 'pit', string2: 'bull', id: 200)
@@ -12,6 +12,7 @@ module Protip::WrapperTest # namespace for internal constants
12
12
 
13
13
  class InnerMessage < ::Protobuf::Message
14
14
  required :int64, :value, 1
15
+ optional :string, :note, 2
15
16
  end
16
17
  class Message < ::Protobuf::Message
17
18
  optional InnerMessage, :inner, 1
@@ -44,6 +45,94 @@ module Protip::WrapperTest # namespace for internal constants
44
45
  end
45
46
  end
46
47
 
48
+ describe '#build' do
49
+ it 'raises an error when building a primitive field' do
50
+ assert_raises RuntimeError do
51
+ wrapper.build(:string)
52
+ end
53
+ end
54
+
55
+ it 'raises an error when building a convertible message' do
56
+ converter.stubs(:convertible?).with(InnerMessage).returns(true)
57
+ assert_raises RuntimeError do
58
+ wrapper.build(:inner)
59
+ end
60
+ end
61
+
62
+ describe 'with an inconvertible message field' do
63
+ let(:wrapped_message) { Message.new }
64
+
65
+ before do
66
+ converter.stubs(:convertible?).with(InnerMessage).returns(false)
67
+ end
68
+
69
+ it 'builds the message when no attributes are provided' do
70
+ assert_nil wrapped_message.inner # Sanity check
71
+ wrapper.build(:inner)
72
+ assert_equal InnerMessage.new, wrapped_message.inner
73
+ end
74
+
75
+ it 'overwrites the message if it exists' do
76
+ wrapped_message.inner = InnerMessage.new(value: 4)
77
+ wrapper.build(:inner)
78
+ assert_equal InnerMessage.new, wrapped_message.inner
79
+ end
80
+
81
+ it 'delegates to #assign_attributes if attributes are provided' do
82
+ Protip::Wrapper.any_instance.expects(:assign_attributes).once.with({value: 40})
83
+ wrapper.build(:inner, value: 40)
84
+ end
85
+
86
+ it 'returns the built message' do
87
+ built = wrapper.build(:inner)
88
+ assert_equal wrapper.inner, built
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#assign_attributes' do
94
+ it 'assigns primitive fields directly' do
95
+ wrapper.assign_attributes string: 'another thing'
96
+ assert_equal 'another thing', wrapped_message.string
97
+ end
98
+
99
+ it 'assigns convertible message fields directly' do
100
+ converter.stubs(:convertible?).with(InnerMessage).returns(true)
101
+ converter.expects(:to_message).once.with(45, InnerMessage).returns(InnerMessage.new(value: 43))
102
+ wrapper.assign_attributes inner: 45
103
+ assert_equal InnerMessage.new(value: 43), wrapped_message.inner
104
+ end
105
+
106
+ it 'returns nil' do
107
+ assert_nil wrapper.assign_attributes({})
108
+ end
109
+
110
+ describe 'when assigning inconvertible message fields' do
111
+ before do
112
+ converter.stubs(:convertible?).with(InnerMessage).returns(false)
113
+ end
114
+
115
+ it 'sets multiple attributes' do
116
+ wrapper.assign_attributes string: 'test2', inner: {value: 50}
117
+ assert_equal 'test2', wrapped_message.string
118
+ assert_equal InnerMessage.new(value: 50), wrapped_message.inner
119
+ end
120
+
121
+ it 'updates inconvertible message fields which have already been built' do
122
+ wrapped_message.inner = InnerMessage.new(value: 60)
123
+ wrapper.assign_attributes inner: {note: 'updated'}
124
+ assert_equal InnerMessage.new(value: 60, note: 'updated'), wrapped_message.inner
125
+ end
126
+
127
+ it 'delegates to itself when setting nested attributes on inconvertible message fields' do
128
+ inner = mock
129
+ wrapper.stubs(:get).with(wrapped_message.class.fields.detect{|f| f.name == :inner}).returns(inner)
130
+ inner.expects(:assign_attributes).once.with(value: 50, note: 'noted')
131
+ wrapper.assign_attributes inner: {value: 50, note: 'noted'}
132
+ end
133
+ end
134
+ end
135
+
47
136
  describe '#get' do
48
137
  it 'does not convert simple fields' do
49
138
  converter.expects(:convertible?).never
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.10.0
4
+ version: 0.10.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - AngelList
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-22 00:00:00.000000000 Z
11
+ date: 2015-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -142,6 +142,11 @@ executables: []
142
142
  extensions: []
143
143
  extra_rdoc_files: []
144
144
  files:
145
+ - definitions/protip.proto
146
+ - definitions/protip/messages/array.proto
147
+ - definitions/protip/messages/errors.proto
148
+ - definitions/protip/messages/types.proto
149
+ - definitions/protip/messages/wrappers.proto
145
150
  - lib/protip.rb
146
151
  - lib/protip/client.rb
147
152
  - lib/protip/converter.rb