lib/ree_lib/packages/ree_dao/package/ree_dao/associations.rb



module ReeDao
  class Associations
    include Ree::LinkDSL

    attr_reader :agg_caller, :list, :local_vars, :only, :except, :autoload_children, :global_opts

    def initialize(agg_caller, list, local_vars, autoload_children = false, **opts)
      @agg_caller = agg_caller
      @list = list
      @local_vars = local_vars
      @threads = [] if !self.class.sync_mode?
      @global_opts = opts
      @only = opts[:only] if opts[:only]
      @except = opts[:except] if opts[:except]
      @autoload_children = autoload_children

      raise ArgumentError.new("you can't use both :only and :except arguments at the same time") if @only && @except

      local_vars.each do |k, v|
        instance_variable_set(k, v)

        self.class.define_method k.to_s.gsub('@', '') do
          v
        end
      end
    end

    def self.sync_mode?
      ReeDao.load_sync_associations_enabled?
    end

    contract(
      Symbol,
      Ksplat[
        scope?: Sequel::Dataset,
        setter?: Or[Symbol, Proc],
        foreign_key?: Symbol,
        autoload_children?: Bool
      ],
      Optblock => Any
    )
    def belongs_to(assoc_name, **opts, &block)
      association(__method__, assoc_name, **opts, &block)
    end
  
    contract(
      Symbol,
      Ksplat[
        scope?: Sequel::Dataset,
        setter?: Or[Symbol, Proc],
        foreign_key?: Symbol,
        autoload_children?: Bool
      ],
      Optblock => Any
    )
    def has_one(assoc_name, **opts, &block)
      association(__method__, assoc_name, **opts, &block)
    end
  
    contract(
      Symbol,
      Ksplat[
        scope?: Sequel::Dataset,
        setter?: Or[Symbol, Proc],
        foreign_key?: Symbol,
        autoload_children?: Bool
      ],
      Optblock => Any
    )
    def has_many(assoc_name, **opts, &block)
      association(__method__, assoc_name, **opts, &block)
    end
  
    contract(
      Symbol,
      Ksplat[
        scope?: Sequel::Dataset,
        setter?: Or[Symbol, Proc],
        foreign_key?: Symbol,
        autoload_children?: Bool
      ],
      Optblock => Any
    )
    def field(assoc_name, **opts, &block)
      association(__method__, assoc_name, **opts, &block)
    end

    private

    contract(
      Or[
        :belongs_to,
        :has_one,
        :has_many,
        :field
      ],
      Symbol,
      Ksplat[
        scope?: Sequel::Dataset,
        setter?: Or[Symbol, Proc],
        foreign_key?: Symbol,
        autoload_children?: Bool
      ],
      Optblock => Any
    )
    def association(assoc_type, assoc_name, **assoc_opts, &block)
      if self.class.sync_mode?
        return if association_is_not_included?(assoc_name)

        association = Association.new(self, list, **global_opts)
        association.load(assoc_type, assoc_name, **assoc_opts, &block)
      else
        return @threads if association_is_not_included?(assoc_name)

        @threads << Thread.new do
          association = Association.new(self, list, **global_opts)
          association.load(assoc_type, assoc_name, **assoc_opts, &block)
        end
      end
    end

    contract(Symbol => Bool)
    def association_is_not_included?(assoc_name)
      return false if !only && !except

      if only
        return false if only && only.include?(assoc_name)

        if only && !only.include?(assoc_name)
          return false if autoload_children
          return true 
        end
      end

      if except
        return true if except && except.include?(assoc_name)
        return false if except && !except.include?(assoc_name)
      end
    end

    contract(Symbol, SplatOf[Any], Optblock => Any)
    def method_missing(method, *args, &block)
      return super if !agg_caller.private_methods(false).include?(method)

      agg_caller.send(method, *args, &block)
    end
  end
end