Class: Protip::Wrapper
- Inherits:
-
Object
- Object
- Protip::Wrapper
- Defined in:
- lib/protip/wrapper.rb
Overview
Wraps a protobuf message to allow:
-
getting/setting of certain 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
-
#converter ⇒ Object
readonly
Returns the value of attribute converter.
-
#message ⇒ Object
readonly
Returns the value of attribute message.
-
#nested_resources ⇒ Object
readonly
Returns the value of attribute nested_resources.
Class Method Summary collapse
-
.matchable?(field) ⇒ Boolean
Semi-private check for whether a field should have an associated query method (e.g.
field_name?).
Instance Method Summary collapse
- #==(wrapper) ⇒ Object
- #as_json ⇒ Object
-
#assign_attributes(attributes) ⇒ NilClass
Mass assignment of message attributes.
-
#build(field_name, attributes = {}) ⇒ Protip::Wrapper
Create a nested field on our message.
-
#initialize(message, converter, nested_resources = {}) ⇒ Wrapper
constructor
A new instance of Wrapper.
- #method_missing(name, *args) ⇒ Object
- #respond_to?(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 converted messages) of the field values.
Constructor Details
#initialize(message, converter, nested_resources = {}) ⇒ Wrapper
Returns a new instance of Wrapper.
13 14 15 16 17 |
# File 'lib/protip/wrapper.rb', line 13 def initialize(, converter, nested_resources={}) @message = @converter = converter @nested_resources = nested_resources end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args) ⇒ Object
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/protip/wrapper.rb', line 34 def method_missing(name, *args) descriptor = .class.descriptor is_setter_method = name =~ /=$/ return method_missing_setter(name, *args) if is_setter_method is_query_method = name =~ /\?$/ return method_missing_query(name, *args) if is_query_method field = descriptor.detect{|field| field.name.to_sym == name} return method_missing_field(field, *args) if field oneof_descriptor = descriptor.lookup_oneof(name.to_s) # For calls to a oneof group, return the active oneof field, or nil if there isn't one return method_missing_oneof(oneof_descriptor) if oneof_descriptor super end |
Instance Attribute Details
#converter ⇒ Object (readonly)
Returns the value of attribute converter.
11 12 13 |
# File 'lib/protip/wrapper.rb', line 11 def converter @converter end |
#message ⇒ Object (readonly)
Returns the value of attribute message.
11 12 13 |
# File 'lib/protip/wrapper.rb', line 11 def @message end |
#nested_resources ⇒ Object (readonly)
Returns the value of attribute nested_resources.
11 12 13 |
# File 'lib/protip/wrapper.rb', line 11 def nested_resources @nested_resources end |
Class Method Details
.matchable?(field) ⇒ Boolean
Semi-private check for whether a field should have an associated query method (e.g. field_name?).
156 157 158 159 160 161 162 |
# File 'lib/protip/wrapper.rb', line 156 def matchable?(field) return false if field.label == :repeated field.type == :enum || field.type == :bool || field.type == :message && (field.subtype.name == "google.protobuf.BoolValue") end |
Instance Method Details
#==(wrapper) ⇒ Object
146 147 148 149 150 151 |
# File 'lib/protip/wrapper.rb', line 146 def ==(wrapper) wrapper.class == self.class && wrapper..class == .class && .class.encode() == wrapper..class.encode(wrapper.) && converter == wrapper.converter end |
#as_json ⇒ Object
126 127 128 |
# File 'lib/protip/wrapper.rb', line 126 def as_json to_h.as_json end |
#assign_attributes(attributes) ⇒ NilClass
Mass assignment of message attributes. Nested messages will be built if necessary, but not overwritten if they already exist.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/protip/wrapper.rb', line 100 def assign_attributes(attributes) attributes.each do |field_name, value| field = .class.descriptor.detect{|field| field.name == field_name.to_s} if !field raise ArgumentError.new("Unrecognized field: #{field_name}") end # For inconvertible nested messages, the value should be either a hash or a message if field.type == :message && !converter.convertible?(field.subtype.msgclass) if value.is_a?(field.subtype.msgclass) # If a message, set it directly set(field, value) elsif value.is_a?(Hash) # If a hash, pass it through to the nested message wrapper = get(field) || build(field.name) # Create the field if it doesn't already exist wrapper.assign_attributes value else # If value is a simple type (e.g. nil), set the value directly set(field, value) end # Otherwise, if the field is a convertible message or a simple type, we set the value directly else set(field, value) end end nil # Return nil to match ActiveRecord behavior end |
#build(field_name, attributes = {}) ⇒ Protip::Wrapper
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, converter)
wrapper.inner # => nil
wrapper.build(:inner, str: 'example')
wrapper.inner.str # => 'example'
Rebuilds the field if it’s already present. Raises an error if the name of a primitive field or a convertible message field is given.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/protip/wrapper.rb', line 75 def build(field_name, attributes = {}) field = .class.descriptor.detect{|field| field.name.to_sym == field_name.to_sym} if !field raise "No field named #{field_name}" elsif field.type != :message raise "Can only build message fields: #{field_name}" elsif converter.convertible?(field.subtype.msgclass) raise "Cannot build a convertible field: #{field.name}" end [field_name.to_s] = field.subtype.msgclass.new wrapper = get(field) wrapper.assign_attributes attributes wrapper end |
#respond_to?(name) ⇒ Boolean
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/protip/wrapper.rb', line 19 def respond_to?(name) if super true else # Responds to calls to oneof groups by name return true if .class.descriptor.lookup_oneof(name.to_s) # Responds to field getters, setters, and in the scalar enum case, query methods .class.descriptor.any? do |field| regex = /^#{field.name}[=#{self.class.matchable?(field) ? '\\?' : ''}]?$/ name.to_s =~ regex end end 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 converted messages) of the field values.
132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/protip/wrapper.rb', line 132 def to_h hash = {} .class.descriptor.each do |field| value = public_send(field.name) if field.label == :repeated value.map!{|v| v.is_a?(self.class) ? v.to_h : v} else value = (value.is_a?(self.class) ? value.to_h : value) end hash[field.name.to_sym] = value end hash end |