<commit_before># Relation constructs queries based on a chained series of functions that extend the chain
# or execute the built query, then building the result and returning it as either an
# ActiveResource::Base or Collection of ActiveResource::Base
#
# ActiveResource::Base extends Relation and Relation.prototype in order to add class level and instance level
# Relation functions to its class level. Relation instances build extended Relation instances, but
# ActiveResource::Base subclasses can build extended Relation instances much like Rails
#
# @example
#   Order.where(price: 5.0).all()
#
# @example
#   Order.where(price: 5.0).order('updatedAt').page(2).per(5).all()
#
# @example
#   Order.includes('transactions').select('id','price',transactions: ['id','amount']).first(5)
#
# @example
#   Order.find(token: '<PASSWORD>')
#
# @example
#   Order.includes('transactions').find_by(token: '<PASSWORD>')
#
class ActiveResource::Relation
  ActiveResource.include(@, ActiveResource::Typing)

  # @param [ActiveResource::Base] base the resource class this relation is for
  # @param [Object] __queryOptions the queryOptions already built by previous links in
  #   the Relation chain
  constructor: (@base, @__queryOptions) ->
    @queryName = @base.queryName

  # Returns links to the server for the resource that this relation is for
  # This will always be { related: baseUrl + '/[@base.queryName]' }
  # @return [Object] string URLs for the resource
  links: ->
    @base.links()

  # Adds filters to the query
  #
  # @example
  #  .where(price: 5.0) = { filter: { price: 5.0 } }
  #
  # @param [Object] options the hash of filters to add the query
  # @return [ActiveResource::Relation] the extended relation with added `filter` params
  #
  # 1. Extend queryOptions['filter'] with the additional options
  # 2. Create new relation with the extended queryOptions
  where: (options) ->
    @__newRelation(@__extendObjectParam('filter', options))

  # Sorts the query based on columns
  #
  # @example
  #  .order(updatedAt: 'asc') = { sort: 'updatedAt' }
  #
  # @example
  #  .order(price: 'desc') = { sort: '-price' }
  #
  # @example
  #  .order(price: 'desc', updatedAt: 'asc') = { sort: '-price,updatedAt' }
  #
  # @param [Array<String>] args a list of columns to order the query by
  # @return [ActiveResource::Relation] the extended relation with added `sort` params
  #
  # 1. Add sorting key/value pairs to queryOptions['sort'] object
  # 2. Create new relation with the extended queryOptions
  order: (args) ->
    @__newRelation(@__extendObjectParam('sort', args))

  # Selects the fields to return from the query
  #
  # @example
  #  Order.select('id', 'updatedAt') = { fields: { orders: 'id,updatedAt' } }
  #
  # @example
  #  Order.includes('transactions').select('id', 'updatedAt', transactions: 'amount') =
  #    { fields: { orders: 'id,updatedAt', transactions: 'amount' } }
  #
  # @example
  #  Order.includes(transactions: 'merchant')
  #  .select('id', 'updatedAt', transactions: 'amount', merchant: ['id', 'name']) =
  #    { fields: { orders: 'id,updatedAt', transactions: 'amount', merchant: 'id,name' } }
  # @note Just because the merchant include is nested, does not mean we nest the merchant fields definition
  #
  # @param [Array<String,Object>] args an array of field representations to cull the query by
  # @return [ActiveResource::Relation] the extended relation with added `sort` params
  #
  # 1. Build new queryOptions so we don't persist across relation constructions
  # 2. Flatten the field arguments into an array of strings/objects and iterate over it
  # 3. Determine the model name for each field
  #   * If object: model name is the key (Order.select({ transactions: [...] }) # => transactions)
  #   * If string: model name is @base.queryName (Order.select('id') # => orders)
  # 4. Append the list of fields to the array of fields for that model
  #   * If object: first value of arg is array to append (Order.select({ transactions: ['id'] }) => ['id'])
  #   * If string: arg itself is item to append to array (Order.select('id') => ['id'])
  # 5. Create new relation with the extended queryOptions
  select: (args...) ->
    queryOptions = _.clone(@__queryOptions || {})
    queryOptions['fields'] ||= {}
    _.each _.flatten(args), (arg) =>
      modelName =
        if _.isObject(arg)
          _.keys(arg)[0]
        else
          @queryName

      queryOptions['fields'] =
        @__extendArrayParam(
          modelName,
          if _.isObject(arg)
            [_.values(arg)[0]]
          else
            [arg]
          ,
          queryOptions['fields']
        )

    @__newRelation(queryOptions)

  # Defines the page number of the query
  #
  # @example
  #  .page(2) = { page: { number: 2 } }
  #
  # @param [Integer] value the page number to define for the query
  # @return [ActiveResource::Relation] the extended relation with added `page.number` param
  #
  # 1. Replace queryOptions['page']['number'] with value
  # 2. Create new relation with the extended queryOptions
  page: (value) ->
    @__newRelation(@__extendObjectParam('page', { number: value }))

  # Defines the page size of the query
  #
  # @example
  #  .per(5) = { page: { size: 5 } }
  #
  # @param [Integer] value the page size to define for the query
  # @return [ActiveResource::Relation] the extended relation with added `page.size` param
  #
  # 1. Replace queryOptions['page']['size'] with value
  # 2. Create new relation with the extended queryOptions
  per: (value) ->
    @__newRelation(@__extendObjectParam('page', { size: value }))

  # Adds association includes to the query
  #
  # @example
  #   .includes('merchant','product') = { include: ['merchant','product'] }
  #
  # @example
  #   .includes('merchant','product',transactions: ['paymentMethod','paymentGateway']) =
  #     { ['merchant','product',{ transactions: ['paymentMethod','paymentGateway'] }] }
  #
  # @example
  #   .includes('merchant','product',transactions: { paymentMethod: 'customer' }]) =
  #     { ['merchant','product',{ transactions: { paymentMethod: 'customer' } }] }
  #
  # @param [Array<String,Object>] args the representations of includes to add to the query
  # @return [ActiveResource::Relation] the extended relation with added `include` params
  #
  # 1. Append array of string args to queryOptions['include'] collection
  # 2. Create new relation with extended queryOptions
  includes: (args...) -><commit_msg>Refactor Relation includes/select object arg handling<commit_after># Relation constructs queries based on a chained series of functions that extend the chain
# or execute the built query, then building the result and returning it as either an
# ActiveResource::Base or Collection of ActiveResource::Base
#
# ActiveResource::Base extends Relation and Relation.prototype in order to add class level and instance level
# Relation functions to its class level. Relation instances build extended Relation instances, but
# ActiveResource::Base subclasses can build extended Relation instances much like Rails
#
# @example
#   Order.where(price: 5.0).all()
#
# @example
#   Order.where(price: 5.0).order('updatedAt').page(2).per(5).all()
#
# @example
#   Order.includes('transactions').select('id','price',transactions: ['id','amount']).first(5)
#
# @example
#   Order.find(token: 'as8h2nW')
#
# @example
#   Order.includes('transactions').find_by(token: 'as8h2nW')
#
class ActiveResource::Relation
  ActiveResource.include(@, ActiveResource::Typing)

  # @param [ActiveResource::Base] base the resource class this relation is for
  # @param [Object] __queryOptions the queryOptions already built by previous links in
  #   the Relation chain
  constructor: (@base, @__queryOptions) ->
    @queryName = @base.queryName

  # Returns links to the server for the resource that this relation is for
  # This will always be { related: baseUrl + '/[@base.queryName]' }
  # @return [Object] string URLs for the resource
  links: ->
    @base.links()

  # Adds filters to the query
  #
  # @example
  #  .where(price: 5.0) = { filter: { price: 5.0 } }
  #
  # @param [Object] options the hash of filters to add the query
  # @return [ActiveResource::Relation] the extended relation with added `filter` params
  #
  # 1. Extend queryOptions['filter'] with the additional options
  # 2. Create new relation with the extended queryOptions
  where: (options) ->
    @__newRelation(@__extendObjectParam('filter', options))

  # Sorts the query based on columns
  #
  # @example
  #  .order(updatedAt: 'asc') = { sort: 'updatedAt' }
  #
  # @example
  #  .order(price: 'desc') = { sort: '-price' }
  #
  # @example
  #  .order(price: 'desc', updatedAt: 'asc') = { sort: '-price,updatedAt' }
  #
  # @param [Array<String>] args a list of columns to order the query by
  # @return [ActiveResource::Relation] the extended relation with added `sort` params
  #
  # 1. Add sorting key/value pairs to queryOptions['sort'] object
  # 2. Create new relation with the extended queryOptions
  order: (args) ->
    @__newRelation(@__extendObjectParam('sort', args))

  # Selects the fields to return from the query
  #
  # @example
  #  Order.select('id', 'updatedAt') = { fields: { orders: 'id,updatedAt' } }
  #
  # @example
  #  Order.includes('transactions').select('id', 'updatedAt', transactions: 'amount') =
  #    { fields: { orders: 'id,updatedAt', transactions: 'amount' } }
  #
  # @example
  #  Order.includes(transactions: 'merchant')
  #  .select('id', 'updatedAt', transactions: 'amount', merchant: ['id', 'name']) =
  #    { fields: { orders: 'id,updatedAt', transactions: 'amount', merchant: 'id,name' } }
  # @note Just because the merchant include is nested, does not mean we nest the merchant fields definition
  #
  # @param [Array<String,Object>] args an array of field representations to cull the query by
  # @return [ActiveResource::Relation] the extended relation with added `sort` params
  #
  # 1. Build new queryOptions so we don't persist across relation constructions
  # 2. Flatten the field arguments into an array of strings/objects and iterate over it
  # 3. Determine the model name for each field
  #   * If object: model name is the key (Order.select({ transactions: [...] }) # => transactions)
  #   * If string: model name is @base.queryName (Order.select('id') # => orders)
  # 4. Append the list of fields to the array of fields for that model
  #   * If object: first value of arg is array to append (Order.select({ transactions: ['id'] }) => ['id'])
  #   * If string: arg itself is item to append to array (Order.select('id') => ['id'])
  # 5. Create new relation with the extended queryOptions
  select: (args...) ->
    queryOptions = _.clone(@__queryOptions || {})
    queryOptions['fields'] ||= {}

    ActiveResource::Collection.build(args)
    .map((a) ->
      if _.isObject(a)
        (_.pick(a, key) for key in _.keys(a))
      else
        a
    )
    .flatten().each (arg) =>
      modelName =
        if _.isObject(arg)
          _.keys(arg)[0]
        else
          @queryName

      queryOptions['fields'] =
        @__extendArrayParam(
          modelName,
          if _.isObject(arg)
            [_.values(arg)[0]]
          else
            [arg]
          ,
          queryOptions['fields']
        )

    @__newRelation(queryOptions)

  # Defines the page number of the query
  #
  # @example
  #  .page(2) = { page: { number: 2 } }
  #
  # @param [Integer] value the page number to define for the query
  # @return [ActiveResource::Relation] the extended relation with added `page.number` param
  #
  # 1. Replace queryOptions['page']['number'] with value
  # 2. Create new relation with the extended queryOptions
  page: (value) ->
    @__newRelation(@__extendObjectParam('page', { number: value }))

  # Defines the page size of the query
  #
  # @example
  #  .per(5) = { page: { size: 5 } }
  #
  # @param [Integer] value the page size to define for the query
  # @return [ActiveResource::Relation] the extended relation with added `page.size` param
  #
  # 1. Replace queryOptions['page']['size'] with value
  # 2. Create new relation with the extended queryOptions
  per: (value) ->
    @__newRelation(@__extendObjectParam('page', { size: value }))

  # Adds association includes to the query
  #
  # @example
  #   .includes('merchant','product') = { include: ['merchant','product'] }
  #
  # @example
  #   .includes('merchant','product',transactions: ['paymentMethod','paymentGateway']) =
  #     { ['merchant','product',{ transactions: ['paymentMethod','paymentGateway'] }] }
  #
  # @example
  #   .includes('merchant','product',transactions: { paymentMethod: 'customer' }]) =
  #     { ['merchant','product',{ transactions: { paymentMethod: 'customer' } }] }
  #
  # @param [Array<String,Object>] args the representations of includes to add to the query
  # @return [ActiveResource::Relation] the extended relation with added `include` params
  #
  # 1. Go through array of args and separate objects with multiple keys in arrays of single key objects so
  #    the array does this: ['1', '2', { 3: 'a', 4: 'b' }] => ['1', '2', { 3: 'a' }, { 4: 'b' }]
  # 1. Append flattened array args to queryOptions['include'] collection
  # 2. Create new relation with extended queryOptions
  includes: (args...) ->
    args =
      ActiveResource::Collection.build(args)
      .map((a) ->
        if _.isObject(a)
          (_.pick(a, key) for key in _.keys(a))
        else
          a
      )
      .flatten().toArray()
{'fraction_non_alphanumeric': 0.13514313188845328, 'fraction_numerical': 0.005695687550854353, 'mean_word_length': 3.5629429632129597, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1314320', 'n_tokens_mistral': 4066, 'n_tokens_neox': 3975, 'n_words': 1322}