class BelongsToOneOf::BelongsToOneOfModel

Attributes

include_type_column[R]
model_class[R]
possible_resource_types[R]
resource_key[R]
resource_type_field[R]

Public Class Methods

new( resource_key, raw_possible_resource_types, include_type_column, resource_type_field, model_class ) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 7
def initialize(
  resource_key,
  raw_possible_resource_types,
  include_type_column,
  resource_type_field,
  model_class
)
  @resource_key = resource_key
  @possible_resource_types = normalise_resource_types(raw_possible_resource_types)
  @include_type_column = include_type_column
  @resource_type_field = resource_type_field
  @model_class = model_class
  validate_config
  initialize_associations
end

Public Instance Methods

resource_getter(model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 81
def resource_getter(model)
  possible_resource_types.keys.detect do |resource_type|
    return model.send(resource_type) if model.send(resource_type)
  end
end
resource_id_getter(model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 87
def resource_id_getter(model)
  possible_resource_types.values.detect do |id_column|
    return model.send(id_column) if model.send(id_column)
  end
end
resource_setter(resource, model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 54
    def resource_setter(resource, model)
      return if resource.nil?

      possible_resource_types.each_key do |resource_type|
        model.public_send("#{resource_type}=", nil)
      end

      model.instance_variable_set("@#{resource_key}", resource)
      resource_type_accessor = find_resource_accessor(resource, model)

      unless resource_type_accessor
        message = "one of #{possible_resource_types.keys.join(', ')} expected, "\
              "got #{resource.inspect} which is an instance of "\
"#{resource.class}(##{resource.class.object_id})"
        raise ActiveRecord::AssociationTypeMismatch, message
      end

      resource_setter_method = "#{resource_type_accessor}="
      model.public_send(resource_setter_method, resource)

      if include_type_column
        resource_type_setter_method = "#{resource_type_field}="
        resource_type_to_store = find_resource_type(resource, model)
        model.public_send(resource_type_setter_method, resource_type_to_store)
      end
    end
resource_type_getter(model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 93
def resource_type_getter(model)
  resource = model.send(resource_key)
  return unless resource

  possible_resource_types.keys.detect do |resource_type_accessor|
    reflection = model.class.reflect_on_association(resource_type_accessor)
    return reflection.class_name if resource.is_a?(reflection.klass)
  end
end
validate_at_most_one_resource(model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 33
def validate_at_most_one_resource(model)
  return unless number_of_resources(model) > 1

  model.errors.add(:base, "model must belong to at most one #{resource_key}")
end
validate_correct_resource_type(model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 39
def validate_correct_resource_type(model)
  resource = model.send(resource_key)
  return unless resource

  actual_resource_type = resource_type_getter(model)
  specified_resource_type = model.send(resource_type_field)

  unless actual_resource_type == specified_resource_type
    model.errors.add(
      resource_type_field,
      "#{resource_type_field} must match the type of #{resource_key}",
    )
  end
end
validate_exactly_one_resource(model) click to toggle source

TODO: I18n the error messages (provide translations??)

# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 27
def validate_exactly_one_resource(model)
  return if number_of_resources(model) == 1

  model.errors.add(:base, "model must belong to exactly one #{resource_key}")
end

Private Instance Methods

find_resource_accessor(resource, model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 152
def find_resource_accessor(resource, model)
  possible_resource_types.keys.detect do |resource_type_accessor|
    reflection = model.class.reflect_on_association(resource_type_accessor)
    return resource_type_accessor if resource.is_a?(reflection.klass)
  end
end
find_resource_type(resource, model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 159
def find_resource_type(resource, model)
  possible_resource_types.keys.detect do |resource_type_accessor|
    reflection = model.class.reflect_on_association(resource_type_accessor)
    return reflection.class_name if resource.is_a?(reflection.klass)
  end
end
initialize_associations() click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 112
def initialize_associations
  possible_resource_types.each do |resource_type_accessor, resource_id_column|
    opts = { optional: true }

    # Only set :foreign_key for a complex relation. Simple relations can be safely
    # "inflected" (i.e. Rails knows these objects are the same...)
    #   foo = Foo.find(1)
    #   foo.bar.foo == foo
    if resource_id_column.to_s.gsub!(/_id$/, "") != resource_type_accessor.to_s
      opts[:foreign_key] = resource_id_column
    end

    model_class.belongs_to(resource_type_accessor, **opts)
  end
end
normalise_resource_types(raw_possible_resource_types) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 128
def normalise_resource_types(raw_possible_resource_types)
  if raw_possible_resource_types.is_a?(Array)
    raw_possible_resource_types.each_with_object({}) do |classname, hash|
      unless classname.is_a?(Symbol)
        raise InvalidParamsException, "expected a symbol, received #{classname}"
      end

      hash[classname] = "#{classname.to_s.underscore}_id".to_sym
    end
  elsif raw_possible_resource_types.is_a?(Hash)
    raw_possible_resource_types
  else
    raise InvalidParamsException,
          "possible_resource_types must be an Array or a Hash, " \
      "received #{raw_possible_resource_types.class}"
  end
end
number_of_resources(model) click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 146
def number_of_resources(model)
  possible_resource_types.keys.count do |resource_type_accessor|
    model.send(resource_type_accessor)
  end
end
validate_config() click to toggle source
# File lib/belongs_to_one_of/belongs_to_one_of_model.rb, line 105
def validate_config
  unless resource_key.is_a?(Symbol)
    raise InvalidParamsException,
          "expected resource_key to be a symbol, received #{resource_key.class}"
  end
end