<commit_before>require 'json'
require 'multi_require'

module Rasti
  class Form

    extend MultiRequire

    require_relative_pattern 'form/*'
    require_relative_pattern 'form/types/*'

    class << self

      def [](attributes)
        Class.new(self) do
          attributes.each do |name, type, options={}|
            attribute name, type, options
          end

          def self.inherited(subclass)
            subclass.instance_variable_set :@attributes, attributes.dup
          end
        end
      end

      def attribute(name, type, options={})
        attributes[name.to_sym] = options.merge(type: type)
        attr_reader name
      end

      def attributes
        @attributes ||= {}
      end

      def attribute_names
        attributes.keys
      end

      def to_s
        "#{name || self.superclass.name}[#{attribute_names.map(&:inspect).join(', ')}]"
      end
      alias_method :inspect, :to_s

    end

    def initialize(attrs={})
      assign_attributes attrs
      set_defaults
      validate!
    end

    def to_s
      "#<#{self.class.name || self.class.superclass.name}[#{to_h.map { |n,v| "#{n}: #{v.inspect}" }.join(', ')}]>"
    end
    alias_method :inspect, :to_s

    def attributes
      assigned_attribute_names.each_with_object({}) do |name, hash|
        hash[name] = read_attribute name
      end
    end
    alias_method :to_h, :attributes

    def assigned?(name)
      assigned_attribute_names.include? name
    end

    private

    def assign_attributes(attrs={})
      self.class.attribute_names.each do |name|
        begin
          write_attribute name, attrs[name] if attrs.key? name
        
        rescue CastError => error
          errors[name] << error.message
        
        rescue ValidationError => error
          error.errors.each do |inner_name, inner_errors| 
            inner_errors.each { |message| errors["#{name}.#{inner_name}"] << message }
          end
        end
      end
    end

    def set_defaults
      (self.class.attribute_names - attributes.keys).each do |name|
        if self.class.attributes[name].key? :default
          value = self.class.attributes[name][:default]
          write_attribute name, value.is_a?(Proc) ? value.call(self) : value
        end
      end
    end

    def assigned_attribute_names
      self.class.attribute_names & instance_variables.map { |v| v.to_s[1..-1].to_sym }
    end

    def read_attribute(name)
      instance_variable_get "@#{name}"
    end

    def write_attribute(name, value)
      typed_value = value.nil? ? nil : self.class.attributes[name][:type].cast(value)
      instance_variable_set "@#{name}", typed_value
    end

    def fetch(attribute)
      attribute.to_s.split('.').inject(self) do |target, attr_name|<commit_msg>Cache assigned_attriute_names<commit_after>require 'json'
require 'multi_require'

module Rasti
  class Form

    extend MultiRequire

    require_relative_pattern 'form/*'
    require_relative_pattern 'form/types/*'

    class << self

      def [](attributes)
        Class.new(self) do
          attributes.each do |name, type, options={}|
            attribute name, type, options
          end

          def self.inherited(subclass)
            subclass.instance_variable_set :@attributes, attributes.dup
          end
        end
      end

      def attribute(name, type, options={})
        attributes[name.to_sym] = options.merge(type: type)
        attr_reader name
      end

      def attributes
        @attributes ||= {}
      end

      def attribute_names
        attributes.keys
      end

      def to_s
        "#{name || self.superclass.name}[#{attribute_names.map(&:inspect).join(', ')}]"
      end
      alias_method :inspect, :to_s

    end

    def initialize(attrs={})
      assign_attributes attrs
      set_defaults
      validate!
    end

    def to_s
      "#<#{self.class.name || self.class.superclass.name}[#{to_h.map { |n,v| "#{n}: #{v.inspect}" }.join(', ')}]>"
    end
    alias_method :inspect, :to_s

    def attributes
      assigned_attribute_names.each_with_object({}) do |name, hash|
        hash[name] = read_attribute name
      end
    end
    alias_method :to_h, :attributes

    def assigned?(name)
      assigned_attribute_names.include? name
    end

    private

    def assign_attributes(attrs={})
      self.class.attribute_names.each do |name|
        begin
          write_attribute name, attrs[name] if attrs.key? name
        
        rescue CastError => error
          errors[name] << error.message
        
        rescue ValidationError => error
          error.errors.each do |inner_name, inner_errors| 
            inner_errors.each { |message| errors["#{name}.#{inner_name}"] << message }
          end
        end
      end
    end

    def set_defaults
      (self.class.attribute_names - attributes.keys).each do |name|
        if self.class.attributes[name].key? :default
          value = self.class.attributes[name][:default]
          write_attribute name, value.is_a?(Proc) ? value.call(self) : value
        end
      end
    end

    def assigned_attribute_names
      @assigned_attribute_names ||= self.class.attribute_names & instance_variables.map { |v| v.to_s[1..-1].to_sym }
    end

    def read_attribute(name)
      instance_variable_get "@#{name}"
    end

    def write_attribute(name, value)
      typed_value = value.nil? ? nil : self.class.attributes[name][:type].cast(value)
      instance_variable_set "@#{name}", typed_value
    end

    def fetch(attribute)
      attribute.to_s.split('.').inject(self) do |target, attr_name|
{'fraction_non_alphanumeric': 0.11067335243553009, 'fraction_numerical': 0.0007163323782234957, 'mean_word_length': 2.3483213429256593, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '575084', 'n_tokens_mistral': 1689, 'n_tokens_neox': 1675, 'n_words': 369}