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