eventbrite/eventbrite-sdk-ruby

View on GitHub
lib/eventbrite_sdk/resource/operations/relationships.rb

Summary

Maintainability
A
0 mins
Test Coverage
module EventbriteSDK
  class Resource
    module Operations
      # Adds +belongs_to+ and +has_many+
      # relationship functionality to Resource instances
      #
      # Requires that included class responds_to:
      # :resource_path
      #   returns a path that will be given to the list_class on instantiation
      #   must have an arity of 1
      # :resource_class_from_string
      #   Should constantize the string into the
      #   class of the operating relationship
      # :list_class
      #   Should return the class responsible for handling many of a resource
      module Relationships
        module ClassMethods
          # Builds a memoized single relationship to another Resource
          # by dynamically defining a method on the instance
          # with the given +rel_method+
          # ============================================
          #
          # class Wheel
          #   include Resource::Operations::Relationships
          #
          #   belongs_to :car, object_class: 'Car', mappings: { id: :car_id }
          #
          #   ...
          # end
          #
          # Wheel.new('id' => 4,  'car_id' => 1).
          #   car #=> EventbriteSDK::Car.retrieve('id' => 1)
          #
          # rel_method: Symbol of the method we are defining on this instance
          #             e.g. belongs_to :thing => defines self#thing
          # object_class: String representation of resource
          #               e.g. 'Event' => EventbriteSDK::Event
          #
          def belongs_to(rel_method, object_class:)
            define_method(rel_method) do
              relationships[rel_method] ||= build_relative(
                rel_method, object_class
              )
            end
          end

          # Builds a memoized ResourceList relationship, dynamically defining
          # a method on the instance with the given +rel_method+
          # ============================================
          #
          #   class Car
          #     include Resource::Operations::Relationships
          #
          #     has_many :wheels, object: 'Wheel', key: 'wheels'
          #
          #     def resource_path(postfix)
          #       "my_path/1/#{postfix}"
          #     end
          #
          #     # ...
          #   end
          #
          #   Car.new('id' => '1').wheels
          #
          # Would instantiate a new ResourceList
          #
          #   ResourceList.new(
          #     url_base: 'my_path/1/wheels',
          #     object_class: Wheel,
          #     key: :wheels
          #   )
          #
          # rel_method: Symbol of the method we are defining on this instance
          # object_class: String representation of the Class we will give
          # to ResourceList
          #
          # key: key to use when ResourceList is extracting objects from
          #      a list payload, if nil then rel_method is used as a default
          #
          def has_many(rel_method, object_class: nil, key: nil)
            define_method(rel_method) do
              key ||= rel_method

              # Don't memoize if it's a new instance
              if new?
                BlankResourceList.new(key: key)
              else
                relationships[rel_method] ||= list_class(rel_method).new(
                  url_base: path(rel_method),
                  object_class: resource_class_from_string(object_class),
                  key: key
                )
              end
            end
          end
        end

        module InstanceMethods
          def list_class(resource_list_rel)
            class_name = resource_list_rel.to_s.split('_').map(&:capitalize).join
            class_name = "#{class_name}List"

            if Lists.const_defined?(class_name)
              Lists.const_get(class_name)
            else
              ResourceList
            end
          end

          private

          def build_relative(name, klass)
            relation_class = resource_class_from_string(klass)
            relative_attrs = attrs.respond_to?(name) && attrs.public_send(name)

            if relative_attrs
              relation_class.new(relative_attrs)
            else
              relation_class.retrieve(id: public_send(:"#{name}_id"))
            end
          end

          def relationships
            @_relationships ||= {}
          end

          def reset_memoized_relationships
            @_relationships = {}
          end
        end

        def self.included(receiver)
          receiver.extend ClassMethods
          receiver.send(:include, InstanceMethods)
        end
      end
    end
  end
end