Class: Protip::Resource::Associations::BelongsToPolymorphicAssociation

Inherits:
Object
  • Object
show all
Includes:
Association
Defined in:
lib/protip/resource/associations/belongs_to_polymorphic_association.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Association

#define_accessors!

Constructor Details

#initialize(resource_class, association_name, belongs_to_associations, id_field: nil) ⇒ BelongsToPolymorphicAssociation

Define a polymorphic association based on a one-of field. The options for the oneof must all be IDs with an associated Protip::Resource::Associations::BelongsToAssociation that's already been created.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 21

def initialize(resource_class, association_name, belongs_to_associations, id_field: nil)
  # The class where accessors will be defined
  @resource_class = resource_class

  # The name of the accessor methods
  @association_name = association_name.to_sym

  # The oneof field that holds the ID of the foreign resource
  @id_field  = (id_field ||
    Protip::Resource::Associations::BelongsToAssociation.default_id_field(association_name)).to_sym
  @_oneof = @resource_class.message.descriptor.lookup_oneof(@id_field.to_s)
  raise "Invalid field name for polymorphic association: #{@id_field}" unless @_oneof


  # Internally, keep the nested associations indexed by ID field
  @_indexed_belongs_to_associations = {}
  belongs_to_associations.each do |association|
    if @_indexed_belongs_to_associations.has_key? association.id_field.to_sym
      raise ArgumentError.new("Duplicate association for #{id_field}")
    end
    @_indexed_belongs_to_associations[association.id_field.to_sym] = association
  end
  field_names = @_oneof.map{|desc| desc.name.to_sym}
  unless (field_names.length == @_indexed_belongs_to_associations.length &&
    @_indexed_belongs_to_associations.keys.all?{|id_field| field_names.include? id_field})
    raise ArgumentError.new(
      'Polymorphic association requires an association to be defined for all nested fields'
    )
  end
  @belongs_to_associations = Set.new(belongs_to_associations)
end

Instance Attribute Details

#association_name ⇒ Object (readonly)

Returns the value of attribute association_name.



10
11
12
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 10

def association_name
  @association_name
end

#belongs_to_associations ⇒ Object (readonly)

Returns the value of attribute belongs_to_associations.



10
11
12
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 10

def belongs_to_associations
  @belongs_to_associations
end

#id_field ⇒ Object (readonly)

Returns the value of attribute id_field.



10
11
12
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 10

def id_field
  @id_field
end

#resource_class ⇒ Object (readonly)

Returns the value of attribute resource_class.



10
11
12
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 10

def resource_class
  @resource_class
end

Instance Method Details

#read(resource) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 53

def read(resource)
  field = resource.message.public_send(id_field)
  if field
    @_indexed_belongs_to_associations[field].read(resource)
  else
    nil
  end
end

#write(resource, value) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/protip/resource/associations/belongs_to_polymorphic_association.rb', line 62

def write(resource, value)
  if value == nil
    @_oneof.each do |field_descriptor|
      resource.public_send(:"#{field_descriptor.name}=", nil)
    end
    nil
  else
    # Find the nested reference matching this association type
    matching_associations = belongs_to_associations.select do |association|
      value.is_a? association.associated_resource_class
    end

    # Make sure we found exactly one
    if matching_associations.empty?
      raise ArgumentError.new("Could not find matching reference for value of type #{value.class}")
    end
    if matching_associations.length > 1
      raise ArgumentError.new(
              "Value of type #{value.class} matched with #{matching_references.keys.map(&:to_s).join(', ')}"
            )
    end

    # And forward the write operation
    matching_associations.first.write(resource, value)
  end
end