protip 0.20.7 → 0.30.0

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/protip.rb +10 -0
  3. data/lib/protip/{wrapper.rb → decorator.rb} +98 -82
  4. data/lib/protip/extensions.rb +0 -1
  5. data/lib/protip/resource.rb +56 -32
  6. data/lib/protip/resource/extra_methods.rb +5 -3
  7. data/lib/protip/tasks/compile.rake +2 -2
  8. data/lib/protip/transformer.rb +14 -0
  9. data/lib/protip/transformers/abstract_transformer.rb +11 -0
  10. data/lib/protip/transformers/active_support/time_with_zone_transformer.rb +35 -0
  11. data/lib/protip/transformers/decorating_transformer.rb +33 -0
  12. data/lib/protip/transformers/default_transformer.rb +22 -0
  13. data/lib/protip/transformers/delegating_transformer.rb +44 -0
  14. data/lib/protip/transformers/deprecated_transformer.rb +90 -0
  15. data/lib/protip/transformers/enum_transformer.rb +93 -0
  16. data/lib/protip/transformers/primitives_transformer.rb +78 -0
  17. data/lib/protip/transformers/timestamp_transformer.rb +31 -0
  18. data/test/functional/protip/decorator_test.rb +204 -0
  19. data/test/unit/protip/decorator_test.rb +665 -0
  20. data/test/unit/protip/resource_test.rb +76 -92
  21. data/test/unit/protip/transformers/decorating_transformer_test.rb +92 -0
  22. data/test/unit/protip/transformers/delegating_transformer_test.rb +83 -0
  23. data/test/unit/protip/transformers/deprecated_transformer_test.rb +139 -0
  24. data/test/unit/protip/transformers/enum_transformer_test.rb +157 -0
  25. data/test/unit/protip/transformers/primitives_transformer_test.rb +139 -0
  26. data/test/unit/protip/transformers/timestamp_transformer_test.rb +46 -0
  27. metadata +23 -12
  28. data/definitions/google/protobuf/wrappers.proto +0 -99
  29. data/lib/google/protobuf/descriptor.rb +0 -1
  30. data/lib/google/protobuf/wrappers.rb +0 -48
  31. data/lib/protip/converter.rb +0 -22
  32. data/lib/protip/standard_converter.rb +0 -185
  33. data/test/unit/protip/standard_converter_test.rb +0 -502
  34. data/test/unit/protip/wrapper_test.rb +0 -646
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c75d9159e3ac75fe200cd600f364dd4a7945602
4
- data.tar.gz: d1f8c8d9e0f252d95bb50c77ea81777d9fbb7d6a
3
+ metadata.gz: 751ca27cb6c6edc2bb6e39614b7ad5dedf4509d4
4
+ data.tar.gz: 8f1fe4aa42f18348ed8f3edb7e886c045cf31dad
5
5
  SHA512:
6
- metadata.gz: 53a4b29f3e11698dde0e177e3e3ef90d2b8d5152b7a82d31d40a5a0561ebc8066af8ced78d9d05fe8ccd18ad9354dd6ffecde4440d5a1fedbf1782790477c039
7
- data.tar.gz: d6d0a6a04d041aa444d8d33f6675a47f4ac98c1bb9befa4754f1f6491a34e7d90c31e28e871686ad183ac2c0ed168bc7b61ddbb5dc3c34812d7035b0e75ceb2e
6
+ metadata.gz: 1bce93588ea39831f7ac6e548ee489863e2e590be4f138b9b0be0337a6c9290890edfb215f9f51e91b95d19fae0663869f076159c1541281e752ca3805770c66
7
+ data.tar.gz: 52960419aae018faf89f4fb572875d2b81f5f9286da4434a110d2ca826082eb95bb3ec1e6e34cca816bb36ceb721e54a5e8cc4d763f676d109dcff7f5c5510be
@@ -5,3 +5,13 @@ require 'protip/resource'
5
5
  if defined?(Mime::Type)
6
6
  Mime::Type.register 'application/x-protobuf', :protobuf
7
7
  end
8
+
9
+ module Protip
10
+ def self.default_transformer
11
+ @default_transformer ||= Protip::Transformers::DefaultTransformer.new
12
+ end
13
+
14
+ def self.decorate(message, transformer = Protip.default_transfomer)
15
+ Protip::Decorator.new(message, transformer)
16
+ end
17
+ end
@@ -1,39 +1,36 @@
1
1
  require 'active_support/concern'
2
2
 
3
+ require 'protip/transformers/enum_transformer'
4
+
3
5
  module Protip
4
6
 
5
7
  # Wraps a protobuf message to allow:
6
- # - getting/setting of certain message fields as if they were more complex Ruby objects
8
+ # - getting/setting of message fields as if they were more complex Ruby objects
7
9
  # - mass assignment of attributes
8
10
  # - standardized creation of nested messages that can't be converted to/from Ruby objects
9
- class Wrapper
11
+ class Decorator
10
12
 
11
- attr_reader :message, :converter, :nested_resources
13
+ attr_reader :message, :transformer, :nested_resources
12
14
 
13
- def initialize(message, converter, nested_resources={})
15
+ def initialize(message, transformer, nested_resources={})
14
16
  @message = message
15
- @converter = converter
17
+ @transformer = transformer
16
18
  @nested_resources = nested_resources
17
19
  end
18
20
 
19
21
  def respond_to?(name)
20
22
  if super
21
- return true
23
+ true
22
24
  else
23
25
  # Responds to calls to oneof groups by name
24
26
  return true if message.class.descriptor.lookup_oneof(name.to_s)
25
27
 
26
- # Responds to field getters, setters, and in the scalar enum case, query methods
28
+ # Responds to field getters, setters, and query methods for all fieldsfa
27
29
  field = message.class.descriptor.lookup(name.to_s.gsub(/[=?]$/, ''))
28
30
  return false if !field
29
- if name[-1, 1] == '?'
30
- # For query methods, only respond if the field is matchable
31
- return self.class.matchable?(field)
32
- else
33
- return true
34
- end
31
+
32
+ true
35
33
  end
36
- false
37
34
  end
38
35
 
39
36
  def method_missing(name, *args)
@@ -74,52 +71,69 @@ module Protip
74
71
  #
75
72
  # We could create an inner message using:
76
73
  #
77
- # wrapper = Protip::Wrapper.new(Outer.new, converter)
74
+ # wrapper = Protip::Wrapper.new(Outer.new, transformer)
78
75
  # wrapper.inner # => nil
79
76
  # wrapper.build(:inner, str: 'example')
80
77
  # wrapper.inner.str # => 'example'
81
78
  #
82
- # Rebuilds the field if it's already present. Raises an error if the name of a primitive field
83
- # or a convertible message field is given.
79
+ # Assigns values by decorating an instance of the inner message,
80
+ # passing in our transformer, and calling +assign_attributes+ on
81
+ # the created decorator object.
82
+ #
83
+ # Rebuilds the field if it's already present. Raises an error if
84
+ # the name of a primitive field is given.
85
+ #
86
+ # TODO: do we still need this or is it enough to just use
87
+ # +decorator.field_name = hash+?
84
88
  #
85
89
  # @param field_name [String|Symbol] The field name to build
86
- # @param attributes [Hash] The initial attributes to set on the field (as parsed by +assign_attributes+)
87
- # @return [Protip::Wrapper] The created field
90
+ # @param attributes [Hash] The initial attributes to set on the
91
+ # field (as parsed by +assign_attributes+) @return
92
+ # [Protip::Wrapper] The created field
88
93
  def build(field_name, attributes = {})
89
94
 
90
- field = message.class.descriptor.detect{|field| field.name.to_sym == field_name.to_sym}
95
+ field = message.class.descriptor.detect do |f|
96
+ f.name.to_sym == field_name.to_sym
97
+ end
98
+
91
99
  if !field
92
100
  raise "No field named #{field_name}"
93
101
  elsif field.type != :message
94
102
  raise "Can only build message fields: #{field_name}"
95
- elsif converter.convertible?(field.subtype.msgclass)
96
- raise "Cannot build a convertible field: #{field.name}"
97
103
  end
98
104
 
99
- message[field_name.to_s] = field.subtype.msgclass.new
100
- wrapper = get(field)
101
- wrapper.assign_attributes attributes
102
- wrapper
105
+ built = field.subtype.msgclass.new
106
+ built_wrapper = self.class.new(built, transformer)
107
+ built_wrapper.assign_attributes attributes
108
+ message[field_name.to_s] = built_wrapper.message
109
+
110
+ get(field)
103
111
  end
104
112
 
105
- # Mass assignment of message attributes. Nested messages will be built if necessary, but not overwritten
106
- # if they already exist.
113
+ # Mass assignment of message attributes. Nested messages will be
114
+ # built if necessary, but not overwritten if they already exist.
107
115
  #
108
- # @param attributes [Hash] The attributes to set. Keys are field names. For primitive fields and message fields
109
- # which are convertible to/from Ruby objects, values are the same as you'd pass to the field's setter
110
- # method. For nested message fields which can't be converted to/from Ruby objects, values are attribute
111
- # hashes which will be passed down to +assign_attributes+ on the nested field.
112
- # @return [NilClass]
116
+ # @param attributes [Hash] The attributes to set. Keys are field
117
+ # names. For primitive fields and message fields which are
118
+ # convertible to/from Ruby objects, values are the same as you'd
119
+ # pass to the field's setter method. For nested message fields
120
+ # which can't be converted to/from Ruby objects, values are
121
+ # attribute hashes which will be passed down to
122
+ # +assign_attributes+ on the nested field. @return [NilClass]
113
123
  def assign_attributes(attributes)
114
124
  attributes.each do |field_name, value|
115
125
  field = message.class.descriptor.lookup(field_name.to_s) ||
116
126
  (raise ArgumentError.new("Unrecognized field: #{field_name}"))
117
-
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
127
+ # Message fields can be set directly by Hash, which either
128
+ # builds or updates them as appropriate.
129
+ #
130
+ # TODO: This kind of oddly assumes that the built message
131
+ # responds to +assign_attributes+ (as it does when a
132
+ # +DecoratingTransformer+ is used for the transformation). Can
133
+ # be removed if we decide the update behavior is unnecessary,
134
+ # since +DecoratingTransformer+ supports assignment by hash.
135
+ if field.type == :message && value.is_a?(Hash)
136
+ (get(field) || build(field.name)).assign_attributes value
123
137
  else
124
138
  set(field, value)
125
139
  end
@@ -132,56 +146,52 @@ module Protip
132
146
  to_h.as_json
133
147
  end
134
148
 
135
- # @return [Hash] A hash whose keys are the fields of our message, and whose values are the Ruby representations
136
- # (either nested hashes or converted messages) of the field values.
149
+ # @return [Hash] A hash whose keys are the fields of our message,
150
+ # and whose values are the Ruby representations (either nested
151
+ # hashes or transformed messages) of the field values.
137
152
  def to_h
138
153
  hash = {}
154
+
155
+ # Use nested +to_h+ on fields which are also decorated messages
156
+ transform = ->(v) { v.is_a?(self.class) ? v.to_h : v }
157
+
139
158
  message.class.descriptor.each do |field|
140
- value = public_send(field.name)
159
+ value = get(field)
141
160
  if field.label == :repeated
142
- value.map!{|v| v.is_a?(self.class) ? v.to_h : v}
161
+ value.map!{|v| transform[v]}
143
162
  else
144
- value = (value.is_a?(self.class) ? value.to_h : value)
163
+ value = transform[value]
145
164
  end
146
165
  hash[field.name.to_sym] = value
147
166
  end
148
167
  hash
149
168
  end
150
169
 
151
- def ==(wrapper)
152
- wrapper.class == self.class &&
153
- wrapper.message.class == message.class &&
154
- message.class.encode(message) == wrapper.message.class.encode(wrapper.message) &&
155
- converter == wrapper.converter
170
+ def ==(decorator)
171
+ decorator.class == self.class &&
172
+ decorator.message.class == message.class &&
173
+ message.class.encode(message) == decorator.message.class.encode(decorator.message) &&
174
+ transformer == decorator.transformer
156
175
  end
157
176
 
158
177
  class << self
159
- # Whether the given field returns boolean values. When true, wrappers will respond to +field_name?+
160
- # query methods.
161
- def boolean?(field)
162
- field.type == :bool || (field.type == :message && field.subtype.name == 'google.protobuf.BoolValue')
163
- end
164
-
165
178
  def enum_for_field(field)
179
+ return nil if field.label == :repeated
166
180
  if field.type == :enum
167
181
  field.subtype
168
182
  elsif field.type == :message && field.subtype.name == 'protip.messages.EnumValue'
169
- Protip::StandardConverter.enum_for_field(field)
183
+ Protip::Transformers::EnumTransformer.enum_for_field(field)
170
184
  else
171
185
  nil
172
186
  end
173
187
  end
174
-
175
- # Semi-private check for whether a field should have an associated query method (e.g. +field_name?+).
176
- # @return [Boolean] Whether the field should have an associated query method on wrappers.
177
- def matchable?(field)
178
- return false if field.label == :repeated
179
- (nil != enum_for_field(field)) || boolean?(field)
180
- end
181
188
  end
182
189
 
183
190
  private
184
191
 
192
+ # Get the transformed value of the given field on our message.
193
+ #
194
+ # @param field [::Google::Protobuf::FieldDescriptor]
185
195
  def get(field)
186
196
  if field.label == :repeated
187
197
  message[field.name].map{|value| to_ruby_value field, value}
@@ -190,19 +200,23 @@ module Protip
190
200
  end
191
201
  end
192
202
 
193
- # Helper for getting values - converts the value for the given field to one that we can return to the user
203
+ # Helper for getting values - converts the value for the given
204
+ # field to one that we can return to the user
205
+ #
206
+ # @param field [::Google::Protobuf::FieldDescriptor] The
207
+ # descriptor for the field we're fetching.
208
+ # @param value [Object] The message or primitive value of the
209
+ # field.
194
210
  def to_ruby_value(field, value)
195
211
  if field.type == :message
196
212
  field_name_sym = field.name.to_sym
197
213
  if nil == value
198
214
  nil
199
- elsif converter.convertible?(field.subtype.msgclass)
200
- converter.to_object value, field
201
215
  elsif nested_resources.has_key?(field_name_sym)
202
216
  resource_klass = nested_resources[field_name_sym]
203
217
  resource_klass.new value
204
218
  else
205
- self.class.new value, converter
219
+ transformer.to_object value, field
206
220
  end
207
221
  else
208
222
  value
@@ -217,21 +231,20 @@ module Protip
217
231
  end
218
232
  end
219
233
 
220
- # Helper for setting values - converts the value for the given field to one that we can set directly
234
+ # Helper for setting values - converts the value for the given
235
+ # field to one that we can set directly
221
236
  def to_protobuf_value(field, value)
222
237
  if field.type == :message
223
238
  if nil == value
224
239
  nil
225
- # This check must happen before the nested_resources check to ensure nested messages
226
- # are set properly
240
+ # This check must happen before the nested_resources check to
241
+ # ensure nested messages are set properly
227
242
  elsif value.is_a?(field.subtype.msgclass)
228
243
  value
229
- elsif converter.convertible?(field.subtype.msgclass)
230
- converter.to_message value, field.subtype.msgclass, field
231
244
  elsif nested_resources.has_key?(field.name.to_sym)
232
245
  value.message
233
246
  else
234
- raise ArgumentError.new "Cannot convert from Ruby object: \"#{field.name}\""
247
+ transformer.to_message(value, field)
235
248
  end
236
249
  elsif field.type == :enum
237
250
  value.is_a?(Fixnum) ? value : value.to_sym
@@ -241,7 +254,7 @@ module Protip
241
254
  end
242
255
 
243
256
  def matches?(field, value)
244
- enum = Protip::Wrapper.enum_for_field(field)
257
+ enum = Protip::Decorator.enum_for_field(field)
245
258
  if value.is_a?(Fixnum)
246
259
  sym = enum.lookup_value(value)
247
260
  else
@@ -250,7 +263,6 @@ module Protip
250
263
  end
251
264
  raise RangeError.new("#{field} has no value #{value}") if nil == sym
252
265
  get(field) == sym
253
-
254
266
  end
255
267
 
256
268
  def method_missing_oneof(oneof, *args)
@@ -267,14 +279,18 @@ module Protip
267
279
 
268
280
  def method_missing_query(name, *args)
269
281
  field = message.class.descriptor.lookup(name[0, name.length - 1])
270
- raise NoMethodError if !field || !self.class.matchable?(field)
271
- if nil != Protip::Wrapper.enum_for_field(field)
272
- raise ArgumentError unless args.length == 1
273
- return matches?(field, args[0])
274
- elsif self.class.boolean?(field)
275
- !!get(field)
282
+ raise NoMethodError unless field
283
+
284
+ if nil != Protip::Decorator.enum_for_field(field) && args.length == 1
285
+ matches?(field, args[0])
286
+ elsif args.length == 0
287
+ value = get(field)
288
+
289
+ # Copied in from ActiveSupport +.blank?+
290
+ blank = (value.respond_to?(:empty?) ? !!value.empty? : !value)
291
+ !blank
276
292
  else
277
- raise NoMethodError
293
+ raise ArgumentError
278
294
  end
279
295
  end
280
296
 
@@ -3,7 +3,6 @@
3
3
 
4
4
  require 'google/protobuf'
5
5
 
6
- require 'google/protobuf/descriptor'
7
6
  Google::Protobuf::DescriptorPool.generated_pool.build do
8
7
  end
9
8
 
@@ -18,9 +18,11 @@ require 'active_model/dirty'
18
18
 
19
19
  require 'forwardable'
20
20
 
21
+ require 'protip'
22
+ require 'protip/client'
21
23
  require 'protip/error'
22
- require 'protip/standard_converter'
23
- require 'protip/wrapper'
24
+ require 'protip/decorator'
25
+ require 'protip/transformers/default_transformer'
24
26
 
25
27
  require 'protip/messages/array'
26
28
 
@@ -48,8 +50,8 @@ module Protip
48
50
  extend ActiveModel::Translation
49
51
  extend Forwardable
50
52
 
51
- def_delegator :@wrapper, :message
52
- def_delegator :@wrapper, :as_json
53
+ def_delegator :@decorator, :message
54
+ def_delegator :@decorator, :as_json
53
55
 
54
56
  # Initialize housekeeping variables
55
57
  @belongs_to_associations = Set.new
@@ -64,7 +66,7 @@ module Protip
64
66
 
65
67
  attr_reader :message, :nested_resources, :belongs_to_associations, :belongs_to_polymorphic_associations
66
68
 
67
- attr_writer :base_path, :converter
69
+ attr_writer :base_path, :transformer
68
70
 
69
71
  def base_path
70
72
  if @base_path == nil
@@ -74,8 +76,8 @@ module Protip
74
76
  end
75
77
  end
76
78
 
77
- def converter
78
- @converter || (@_standard_converter ||= Protip::StandardConverter.new)
79
+ def transformer
80
+ @transformer || ::Protip.default_transformer
79
81
  end
80
82
 
81
83
  private
@@ -119,21 +121,19 @@ module Protip
119
121
  # Allow calls to oneof groups to get the set oneof field
120
122
  def define_oneof_group_methods(message)
121
123
  message.descriptor.each_oneof do |oneof_field|
122
- def_delegator :@wrapper, :"#{oneof_field.name}"
124
+ def_delegator :@decorator, :"#{oneof_field.name}"
123
125
  end
124
126
  end
125
127
 
126
128
  # Define attribute readers/writers
127
129
  def define_attribute_accessors(message)
128
130
  message.descriptor.each do |field|
129
- def_delegator :@wrapper, :"#{field.name}"
130
- if ::Protip::Wrapper.matchable?(field)
131
- def_delegator :@wrapper, :"#{field.name}?"
132
- end
131
+ def_delegator :@decorator, :"#{field.name}"
132
+ def_delegator :@decorator, :"#{field.name}?"
133
133
 
134
134
  define_method "#{field.name}=" do |new_value|
135
135
  old_value = self.message[field.name] # Only compare the raw values
136
- @wrapper.send("#{field.name}=", new_value)
136
+ @decorator.send("#{field.name}=", new_value)
137
137
  new_value = self.message[field.name]
138
138
 
139
139
  # Need to check that types are the same first, otherwise protobuf gets mad comparing
@@ -152,17 +152,29 @@ module Protip
152
152
  if query
153
153
  if actions.include?(:show)
154
154
  define_singleton_method :find do |id, query_params = {}|
155
- wrapper = ::Protip::Wrapper.new(query.new, converter)
156
- wrapper.assign_attributes query_params
157
- ::Protip::Resource::SearchMethods.show(self, id, wrapper.message)
155
+ message = nil
156
+ if query_params.is_a?(query)
157
+ message = query_params
158
+ else
159
+ decorator = ::Protip::Decorator.new(query.new, transformer)
160
+ decorator.assign_attributes query_params
161
+ message = decorator.message
162
+ end
163
+ ::Protip::Resource::SearchMethods.show(self, id, message)
158
164
  end
159
165
  end
160
166
 
161
167
  if actions.include?(:index)
162
168
  define_singleton_method :all do |query_params = {}|
163
- wrapper = ::Protip::Wrapper.new(query.new, converter)
164
- wrapper.assign_attributes query_params
165
- ::Protip::Resource::SearchMethods.index(self, wrapper.message)
169
+ message = nil
170
+ if query_params.is_a?(query)
171
+ message = query_params
172
+ else
173
+ decorator = ::Protip::Decorator.new(query.new, transformer)
174
+ decorator.assign_attributes query_params
175
+ message = decorator.message
176
+ end
177
+ ::Protip::Resource::SearchMethods.index(self, message)
166
178
  end
167
179
  end
168
180
  else
@@ -183,9 +195,16 @@ module Protip
183
195
  def member(action:, method:, request: nil, response: nil)
184
196
  if request
185
197
  define_method action do |request_params = {}|
186
- wrapper = ::Protip::Wrapper.new(request.new, self.class.converter)
187
- wrapper.assign_attributes request_params
188
- ::Protip::Resource::ExtraMethods.member self, action, method, wrapper.message, response
198
+ message = nil
199
+ if request_params.is_a?(request) # Message provided directly
200
+ message = request_params
201
+ else # Parameters provided by hash
202
+ decorator = ::Protip::Decorator.new(request.new, self.class.transformer)
203
+ decorator.assign_attributes request_params
204
+ message = decorator.message
205
+ end
206
+ ::Protip::Resource::ExtraMethods.member self,
207
+ action, method, message, response
189
208
  end
190
209
  else
191
210
  define_method action do
@@ -197,13 +216,16 @@ module Protip
197
216
  def collection(action:, method:, request: nil, response: nil)
198
217
  if request
199
218
  define_singleton_method action do |request_params = {}|
200
- wrapper = ::Protip::Wrapper.new(request.new, converter)
201
- wrapper.assign_attributes request_params
219
+ message = nil
220
+ if request_params.is_a?(request) # Message provided directly
221
+ message = request_params
222
+ else # Parameters provided by hash
223
+ decorator = ::Protip::Decorator.new(request.new, transformer)
224
+ decorator.assign_attributes request_params
225
+ message = decorator.message
226
+ end
202
227
  ::Protip::Resource::ExtraMethods.collection self,
203
- action,
204
- method,
205
- wrapper.message,
206
- response
228
+ action, method, message, response
207
229
  end
208
230
  else
209
231
  define_singleton_method action do
@@ -220,8 +242,9 @@ module Protip
220
242
  end
221
243
 
222
244
  def belongs_to_polymorphic(association_name, options = {}, &block)
223
- # We evaluate the block in the context of a wrapper that stores simple belongs-to associations
224
- # as they're being created.
245
+ # We evaluate the block in the context of a wrapper that
246
+ # stores simple belongs-to associations as they're being
247
+ # created.
225
248
  nested_association_creator = Class.new do
226
249
  attr_reader :associations
227
250
  def initialize(resource_class)
@@ -274,7 +297,7 @@ module Protip
274
297
  # since we might just assign attributes to the current instance of the message directly
275
298
  old_attributes[key] = field && field.type == :message && value ? value.clone : value
276
299
  end
277
- @wrapper.assign_attributes attributes
300
+ @decorator.assign_attributes attributes
278
301
  keys.each do |key|
279
302
  old_value = old_attributes[key]
280
303
  new_value = message[key]
@@ -286,7 +309,8 @@ module Protip
286
309
  end
287
310
 
288
311
  def message=(message)
289
- @wrapper = Protip::Wrapper.new(message, self.class.converter, self.class.nested_resources)
312
+ @decorator = Protip::Decorator.new(message,
313
+ self.class.transformer, self.class.nested_resources)
290
314
  end
291
315
 
292
316
  def save