protip 0.20.7 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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