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 +4 -4
- data/definitions/protip.proto +2 -0
- data/definitions/protip/messages/array.proto +6 -0
- data/definitions/protip/messages/errors.proto +12 -0
- data/definitions/protip/messages/types.proto +7 -0
- data/definitions/protip/messages/wrappers.proto +60 -0
- data/lib/protip/resource.rb +4 -6
- data/lib/protip/wrapper.rb +67 -0
- data/test/unit/protip/resource_test.rb +36 -0
- data/test/unit/protip/wrapper_test.rb +89 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f78ca1c3460fdd0b52a6c398e2837a0bb6d750fa
|
4
|
+
data.tar.gz: 5752105ecb4363cd8a681ea87008ed166c106018
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39c47fd2e35da1270a206c42785d8c7395f7402dc7c7f14064650970c3b9a1d2996b486c2fe92dc563911045b21484237764cb555bc0c8ed556d4e94db77af4e
|
7
|
+
data.tar.gz: f5bd09ed0b9dadff3f20a1723a7142c02588888316239373cd593d90623e558bca94245bc0de27780c517fc2757a97c1dc39ff492115e2382d623730949a7fd0
|
@@ -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,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
|
+
}
|
data/lib/protip/resource.rb
CHANGED
@@ -228,17 +228,15 @@ module Protip
|
|
228
228
|
end
|
229
229
|
end
|
230
230
|
|
231
|
-
def initialize(
|
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
|
236
|
-
self.message =
|
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
|
-
|
240
|
-
public_send :"#{field}=", value
|
241
|
-
end
|
239
|
+
@wrapper.assign_attributes message_or_attributes
|
242
240
|
end
|
243
241
|
|
244
242
|
super()
|
data/lib/protip/wrapper.rb
CHANGED
@@ -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.
|
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-
|
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
|