protip 0.10.0 → 0.10.4

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: 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