<commit_before>module ActiveScaffold
  # Provides support for param hashes assumed to be model attributes.
  # Support is primarily needed for creating/editing associated records using a nested hash structure.
  #
  # Paradigm Params Hash (should write unit tests on this):
  #   params[:record] = {
  #     # a simple record attribute
  #     'name' => 'John',
  #     # a plural association hash
  #     'roles' => {
  #       # associate with an existing role
  #       '5' => {'id' => 5}
  #       # associate with an existing role and edit it
  #       '6' => {'id' => 6, 'name' => 'designer'}
  #       # create and associate a new role
  #       '124521' => {'name' => 'marketer'}
  #     }
  #     # a singular association hash
  #     'location' => {'id' => 12, 'city' => 'New York'}
  #   }
  #
  # Simpler association structures are also supported, like:
  #   params[:record] = {
  #     # a simple record attribute
  #     'name' => 'John',
  #     # a plural association ... all ids refer to existing records
  #     'roles' => ['5', '6'],
  #     # a singular association ... all ids refer to existing records
  #     'location' => '12'
  # }
  module AttributeParams
    # Takes attributes (as from params[:record]) and applies them to the parent_record. Also looks for
    # association attributes and attempts to instantiate them as associated objects.
    #
    # This is a secure way to apply params to a record, because it's based on a loop over the columns
    # set. The columns set will not yield unauthorized columns, and it will not yield unregistered columns.
    def update_record_from_params(parent_record, columns, attributes)
      action = parent_record.new_record? ? :create : :update
      return parent_record unless parent_record.authorized_for?(:action => action)

      multi_parameter_attributes = {}
      attributes.each do |k, v|
        next unless k.include? '('
        column_name = k.split('(').first.to_sym
        multi_parameter_attributes[column_name] ||= []
        multi_parameter_attributes[column_name] << [k, v]
      end

      columns.each :for => parent_record, :flatten => true do |column|
        if multi_parameter_attributes.has_key? column.name
          parent_record.send(:assign_multiparameter_attributes, multi_parameter_attributes[column.name])
        elsif attributes.has_key? column.name
          value = attributes[column.name]

          # convert the value, possibly by instantiating associated objects
          value = if value.is_a?(Hash)
            # this is just for backwards compatibility. we should clean this up in 2.0.
            if column.form_ui == :select
              ids = if column.singular_association?
                value[:id]
              else
                value.values.collect {|hash| hash[:id]}
              end
              (ids and not ids.empty?) ? column.association.klass.find(ids) : nil

            elsif column.singular_association?
              hash = value
              record = find_or_create_for_params(hash, column.association.klass, parent_record.send("#{column.name}"))
              if record
                record_columns = active_scaffold_config_for(column.association.klass).subform.columns
                update_record_from_params(record, record_columns, hash)
                record.unsaved = true
              end
              record

            elsif column.plural_association?
              collection = value.collect do |key_value_pair|
                hash = key_value_pair[1]
                record = find_or_create_for_params(hash, column.association.klass, parent_record.send("#{column.name}"))
                if record
                  record_columns = active_scaffold_config_for(column.association.klass).subform.columns
                  update_record_from_params(record, record_columns, hash)
                  record.unsaved = true<commit_msg>Fix ordering of new association records in a subform so that they don't randomly reorder on a validation error.<commit_after>module ActiveScaffold
  # Provides support for param hashes assumed to be model attributes.
  # Support is primarily needed for creating/editing associated records using a nested hash structure.
  #
  # Paradigm Params Hash (should write unit tests on this):
  #   params[:record] = {
  #     # a simple record attribute
  #     'name' => 'John',
  #     # a plural association hash
  #     'roles' => {
  #       # associate with an existing role
  #       '5' => {'id' => 5}
  #       # associate with an existing role and edit it
  #       '6' => {'id' => 6, 'name' => 'designer'}
  #       # create and associate a new role
  #       '124521' => {'name' => 'marketer'}
  #     }
  #     # a singular association hash
  #     'location' => {'id' => 12, 'city' => 'New York'}
  #   }
  #
  # Simpler association structures are also supported, like:
  #   params[:record] = {
  #     # a simple record attribute
  #     'name' => 'John',
  #     # a plural association ... all ids refer to existing records
  #     'roles' => ['5', '6'],
  #     # a singular association ... all ids refer to existing records
  #     'location' => '12'
  # }
  module AttributeParams
    # Takes attributes (as from params[:record]) and applies them to the parent_record. Also looks for
    # association attributes and attempts to instantiate them as associated objects.
    #
    # This is a secure way to apply params to a record, because it's based on a loop over the columns
    # set. The columns set will not yield unauthorized columns, and it will not yield unregistered columns.
    def update_record_from_params(parent_record, columns, attributes)
      action = parent_record.new_record? ? :create : :update
      return parent_record unless parent_record.authorized_for?(:action => action)

      multi_parameter_attributes = {}
      attributes.each do |k, v|
        next unless k.include? '('
        column_name = k.split('(').first.to_sym
        multi_parameter_attributes[column_name] ||= []
        multi_parameter_attributes[column_name] << [k, v]
      end

      columns.each :for => parent_record, :flatten => true do |column|
        if multi_parameter_attributes.has_key? column.name
          parent_record.send(:assign_multiparameter_attributes, multi_parameter_attributes[column.name])
        elsif attributes.has_key? column.name
          value = attributes[column.name]

          # convert the value, possibly by instantiating associated objects
          value = if value.is_a?(Hash)
            # this is just for backwards compatibility. we should clean this up in 2.0.
            if column.form_ui == :select
              ids = if column.singular_association?
                value[:id]
              else
                value.values.collect {|hash| hash[:id]}
              end
              (ids and not ids.empty?) ? column.association.klass.find(ids) : nil

            elsif column.singular_association?
              hash = value
              record = find_or_create_for_params(hash, column.association.klass, parent_record.send("#{column.name}"))
              if record
                record_columns = active_scaffold_config_for(column.association.klass).subform.columns
                update_record_from_params(record, record_columns, hash)
                record.unsaved = true
              end
              record

            elsif column.plural_association?
              # Sort by keys so that new association records (which have monotonically
              # increasing temporary IDs) redisplay in the same order in case
              # there is a validation error.
              collection = value.keys.sort.collect do |key|
                hash = value[key]
                record = find_or_create_for_params(hash, column.association.klass, parent_record.send("#{column.name}"))
                if record
                  record_columns = active_scaffold_config_for(column.association.klass).subform.columns
                  update_record_from_params(record, record_columns, hash)
                  record.unsaved = true
{'fraction_non_alphanumeric': 0.09272997032640949, 'fraction_numerical': 0.004574678536102868, 'mean_word_length': 2.387353433835846, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 39, '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': '7451305', 'n_tokens_mistral': 2205, 'n_tokens_neox': 2150, 'n_words': 667}