Class: Protip::Decorator
- Inherits:
-
Object
- Object
- Protip::Decorator
- Defined in:
- lib/protip/decorator.rb
Overview
Wraps a protobuf message to allow:
-
getting/setting of message fields as if they were more complex Ruby objects
-
mass assignment of attributes
-
standardized creation of nested messages that can’t be converted to/from Ruby objects
Instance Attribute Summary collapse
-
#message ⇒ Object
readonly
Returns the value of attribute message.
-
#nested_resources ⇒ Object
readonly
Returns the value of attribute nested_resources.
-
#transformer ⇒ Object
readonly
Returns the value of attribute transformer.
Class Method Summary collapse
Instance Method Summary collapse
- #==(decorator) ⇒ Object
- #as_json ⇒ Object
-
#assign_attributes(attributes) ⇒ Object
Mass assignment of message attributes.
-
#build(field_name, attributes = {}) ⇒ Object
Create a nested field on our message.
-
#initialize(message, transformer, nested_resources = {}) ⇒ Decorator
constructor
A new instance of Decorator.
- #inspect ⇒ Object
- #method_missing(name, *args) ⇒ Object
- #respond_to_missing?(name) ⇒ Boolean
-
#to_h ⇒ Hash
A hash whose keys are the fields of our message, and whose values are the Ruby representations (either nested hashes or transformed messages) of the field values.
Constructor Details
#initialize(message, transformer, nested_resources = {}) ⇒ Decorator
Returns a new instance of Decorator.
15 16 17 18 19 |
# File 'lib/protip/decorator.rb', line 15 def initialize(, transformer, nested_resources={}) @message = @transformer = transformer @nested_resources = nested_resources end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/protip/decorator.rb', line 38 def method_missing(name, *args) descriptor = .class.descriptor name = name.to_s last_char = name[-1, 1] if last_char == '=' return method_missing_set(name, *args) end if last_char == '?' return method_missing_query(name, *args) end field = descriptor.lookup(name) if field return method_missing_field(field, *args) end oneof = descriptor.lookup_oneof(name) # For calls to a oneof group, return the active oneof field, or nil if there isn't one if oneof return method_missing_oneof(oneof, *args) end super end |
Instance Attribute Details
#message ⇒ Object (readonly)
Returns the value of attribute message.
13 14 15 |
# File 'lib/protip/decorator.rb', line 13 def @message end |
#nested_resources ⇒ Object (readonly)
Returns the value of attribute nested_resources.
13 14 15 |
# File 'lib/protip/decorator.rb', line 13 def nested_resources @nested_resources end |
#transformer ⇒ Object (readonly)
Returns the value of attribute transformer.
13 14 15 |
# File 'lib/protip/decorator.rb', line 13 def transformer @transformer end |
Class Method Details
.enum_for_field(field) ⇒ Object
180 181 182 183 184 185 186 187 188 189 |
# File 'lib/protip/decorator.rb', line 180 def enum_for_field(field) return nil if field.label == :repeated if field.type == :enum field.subtype elsif field.type == :message && field.subtype.name == 'protip.messages.EnumValue' Protip::Transformers::EnumTransformer.enum_for_field(field) else nil end end |
Instance Method Details
#==(decorator) ⇒ Object
172 173 174 175 176 177 |
# File 'lib/protip/decorator.rb', line 172 def ==(decorator) decorator.class == self.class && decorator..class == .class && .class.encode() == decorator..class.encode(decorator.) && transformer == decorator.transformer end |
#as_json ⇒ Object
147 148 149 |
# File 'lib/protip/decorator.rb', line 147 def as_json to_h.as_json end |
#assign_attributes(attributes) ⇒ Object
Mass assignment of message attributes. Nested messages will be built if necessary, but not overwritten if they already exist.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/protip/decorator.rb', line 125 def assign_attributes(attributes) attributes.each do |field_name, value| field = .class.descriptor.lookup(field_name.to_s) || (raise ArgumentError.new("Unrecognized field: #{field_name}")) # Message fields can be set directly by Hash, which either # builds or updates them as appropriate. # # TODO: This kind of oddly assumes that the built message # responds to +assign_attributes+ (as it does when a # +DecoratingTransformer+ is used for the transformation). Can # be removed if we decide the update behavior is unnecessary, # since +DecoratingTransformer+ supports assignment by hash. if field.type == :message && value.is_a?(Hash) (get(field) || build(field.name)).assign_attributes value else set(field, value) end end nil # Return nil to match ActiveRecord behavior end |
#build(field_name, attributes = {}) ⇒ Object
Create a nested field on our message. For example, given the following definitions:
Inner {
optional string str = 1;
}
Outer {
optional Inner inner = 1;
}
We could create an inner message using:
wrapper = Protip::Wrapper.new(Outer.new, transformer)
wrapper.inner # => nil
wrapper.build(:inner, str: 'example')
wrapper.inner.str # => 'example'
Assigns values by decorating an instance of the inner message, passing in our transformer, and calling assign_attributes
on the created decorator object.
Rebuilds the field if it’s already present. Raises an error if the name of a primitive field is given.
TODO: do we still need this or is it enough to just use decorator.field_name = hash?
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/protip/decorator.rb', line 95 def build(field_name, attributes = {}) field = .class.descriptor.detect do |f| f.name.to_sym == field_name.to_sym end if !field raise "No field named #{field_name}" elsif field.type != :message raise "Can only build message fields: #{field_name}" end built = field.subtype.msgclass.new built_wrapper = self.class.new(built, transformer) built_wrapper.assign_attributes attributes [field_name.to_s] = built_wrapper. get(field) end |
#inspect ⇒ Object
21 22 23 |
# File 'lib/protip/decorator.rb', line 21 def inspect "<#{self.class.name}(#{transformer.class.name}) #{.inspect}>" end |
#respond_to_missing?(name) ⇒ Boolean
25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/protip/decorator.rb', line 25 def respond_to_missing?(name, *) return true if super # Responds to calls to oneof groups by name return true if .class.descriptor.lookup_oneof(name.to_s) # Responds to field getters, setters, and query methods for all fieldsfa field = .class.descriptor.lookup(name.to_s.gsub(/[=?]$/, '')) return true if field false end |
#to_h ⇒ Hash
Returns A hash whose keys are the fields of our message, and whose values are the Ruby representations (either nested hashes or transformed messages) of the field values.
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/protip/decorator.rb', line 154 def to_h hash = {} # Use nested +to_h+ on fields which are also decorated messages transform = ->(v) { v.is_a?(self.class) ? v.to_h : v } .class.descriptor.each do |field| value = get(field) if field.label == :repeated value.map!{|v| transform[v]} else value = transform[value] end hash[field.name.to_sym] = value end hash end |