<commit_before>module HasFields
  module ClassMethods

    def has_fields(options = {})
      unless options[:scopes].is_a?(Array)
        raise ArgumentError, "Must define :scope => [] on the has_fields class method"
      end
      
      HasFields.config||={}
      HasFields.config[self.name] = default_config.merge(options)

      base_class = self.name

      # Attempt to load Field related class. If not create it
      begin
        Object.const_get("Field")
      rescue
        HasFields.create_associated_fields_class(base_class)
      end

      # Attempt to load FieldAttribute related class. If not create it
      begin
        Object.const_get("FieldAttribute")
      rescue
        HasFields.create_associated_values_class(base_class)
      end
      
      # Attempt to load the FieldSelectOption related class. If not create it
      begin
        Object.const_get("FieldSelectOption")
      rescue
        HasFields.create_associated_select_options_class(base_class)
      end

      # Modify main class
      class_eval do
        attr_accessible :fields

        # The following is only setup once
        unless method_defined? :read_attribute_without_field_behavior

          # Carry out delayed actions before save
          after_validation :save_modified_field_attributes, :on => :update

          private

          alias_method_chain :read_attribute, :field_behavior
          alias_method_chain :write_attribute, :field_behavior
        end
        
      end
      
      instance_eval do
        has_many :field_attributes, :dependent => :destroy
      end
    end

    def fields(scope=nil)
      unless scope
        raise ArgumentError, "Please provide a scope for the fields, eg Advisor.fields(@organization)"
      end
  
      begin
        return Field.send("find_all_by_#{scope.class.name.underscore}_id_and_kind", scope.id, self.name, :order => :id)
      rescue NoMethodError
        raise InvalidScopeError, "Class #{self.name} does not have scope :#{scope.class.name.downcase} defined for has_fields"
      end
    end

    private
    
    def HasFields.create_associated_fields_class(klass)
      Object.const_set(HasFields.config[klass][:fields_class_name],
        Class.new(::HasFields::Base)).class_eval do
          self.table_name = HasFields.config[klass][:fields_table_name]
          has_many :field_select_options, :class_name => "::HasFields::FieldSelectOption", :foreign_key => :field_id, :dependent => :destroy
          has_many :field_attributes, :class_name => "::HasFields::FieldAttribute", :foreign_key => :field_id, :dependent => :destroy
          belongs_to klass.underscore.to_sym
          scope :by_scope, lambda {|s| {:conditions => "#{s}_id IS NOT NULL"}}
          validates_presence_of :kind, :message => 'Please specify the class that this field will be added to.'
          validates_presence_of :name
          validates_uniqueness_of :name, :scope => HasFields.config[klass][:scopes].map { |f| f.to_s.foreign_key }, :message => "The field name is already taken."
          validates_inclusion_of :style, :in => ALLOWABLE_TYPES, :message => "should be one of: #{ALLOWABLE_TYPES.join(", ")}."
          validate :no_duplicate_select_options
          accepts_nested_attributes_for :field_select_options, :reject_if => proc {|o| o['option'].blank? }, :allow_destroy => true
          
          def self.reloadable? #:nodoc:
            false
          end
          
          def self.scoped_by(scope_object)
            foreign_key = "#{scope_object.class.name.downcase}_id"
            where(foreign_key => scope_object.id)
          end
          
          def scoped_by_class
            attributes.detect{|k,v| k.match(/_id/) && !v.nil?}[0].gsub("_id","")
          end
          
          def scoped_by_object
            scoped_by_class.classify.constantize.send(:find,eval("#{scoped_by_class}_id"))       
          end
          
          def scope_id=(scope_class_and_id)
            scope_class, scope_id = scope_class_and_id.split("_")
            self.send("#{scope_class.underscore}_id=", scope_id)
          end
          
          def select_options_data
            HasFields::FieldSelectOption.find_all_by_field_id(id).map{|o| o.option }
          end
          
          def no_duplicate_select_options
            if style == 'select' && (field_select_options.size != field_select_options.map{|o| o.option}.uniq.size)
              errors[:base] << "There are duplicate select options."
            end
          end

        end
      ::HasFields.const_set("Field", Object.const_get("Field"))
    end

    def HasFields.create_associated_values_class(klass)
      Object.const_set(HasFields.config[klass][:attributes_class_name],
      Class.new(ActiveRecord::Base)).class_eval do
        self.table_name = HasFields.config[klass][:attributes_table_name]
        validates_presence_of :field_id
        validates_inclusion_of :value, :in => Proc.new{|v| v.field.field_select_options.map{|opt| opt.option }}, :if => Proc.new{|o| o.field && o.field.style == "select" }
        belongs_to :field, :class_name => "::HasFields::Field"
        belongs_to klass.underscore.to_sym, :foreign_key => HasFields.config[klass][:foreign_key]
        
        alias_method :base, klass.underscore.to_sym
        
        def self.reloadable? #:nodoc:
          false
        end
        
        def value
          string_value || boolean_value || date_value
        end
        
        def value=(v)
          send("#{data_type_from_field_style}_value=", v)
        end
        
        def data_type_from_field_style
          return "string" unless field
          case field.style
          when "date"
            "date"
          when "checkbox"
            "boolean"
          else
            "string"
          end
        end

        def validate
          field = self.field
          raise "Couldn't load field" if !field
          if field.style == "select" && !self.value.blank?
            if field.field_select_options.find{|f| f == self.value}.nil?<commit_msg>Fixed has_many relationship in HasFields::FieldAttribute<commit_after>module HasFields
  module ClassMethods

    def has_fields(options = {})
      unless options[:scopes].is_a?(Array)
        raise ArgumentError, "Must define :scope => [] on the has_fields class method"
      end
      
      HasFields.config||={}
      HasFields.config[self.name] = default_config.merge(options)

      base_class = self.name

      # Attempt to load Field related class. If not create it
      begin
        Object.const_get("Field")
      rescue
        HasFields.create_associated_fields_class(base_class)
      end

      # Attempt to load FieldAttribute related class. If not create it
      begin
        Object.const_get("FieldAttribute")
      rescue
        HasFields.create_associated_values_class(base_class)
      end
      
      # Attempt to load the FieldSelectOption related class. If not create it
      begin
        Object.const_get("FieldSelectOption")
      rescue
        HasFields.create_associated_select_options_class(base_class)
      end

      # Modify main class
      class_eval do
        attr_accessible :fields

        # The following is only setup once
        unless method_defined? :read_attribute_without_field_behavior

          # Carry out delayed actions before save
          after_validation :save_modified_field_attributes, :on => :update

          private

          alias_method_chain :read_attribute, :field_behavior
          alias_method_chain :write_attribute, :field_behavior
        end
        
      end
      
      instance_eval do
        has_many :field_attributes, :dependent => :destroy
      end
      
      # attach the field attributes to the class - needs to be done here so that the belongs_to doesn't get overwritten each time has_lists is called
      FieldAttribute.class_eval do
        belongs_to base_class.underscore.to_sym, :foreign_key => HasFields.config[base_class][:foreign_key]
      end
      
    end

    def fields(scope=nil)
      unless scope
        raise ArgumentError, "Please provide a scope for the fields, eg Advisor.fields(@organization)"
      end
  
      begin
        return Field.send("find_all_by_#{scope.class.name.underscore}_id_and_kind", scope.id, self.name, :order => :id)
      rescue NoMethodError
        raise InvalidScopeError, "Class #{self.name} does not have scope :#{scope.class.name.downcase} defined for has_fields"
      end
    end

    private
    
    def HasFields.create_associated_fields_class(klass)
      Object.const_set(HasFields.config[klass][:fields_class_name],
        Class.new(::HasFields::Base)).class_eval do
          self.table_name = HasFields.config[klass][:fields_table_name]
          has_many :field_select_options, :class_name => "::HasFields::FieldSelectOption", :foreign_key => :field_id, :dependent => :destroy
          has_many :field_attributes, :class_name => "::HasFields::FieldAttribute", :foreign_key => :field_id, :dependent => :destroy
          belongs_to klass.underscore.to_sym
          scope :by_scope, lambda {|s| {:conditions => "#{s}_id IS NOT NULL"}}
          validates_presence_of :kind, :message => 'Please specify the class that this field will be added to.'
          validates_presence_of :name
          validates_uniqueness_of :name, :scope => HasFields.config[klass][:scopes].map { |f| f.to_s.foreign_key }, :message => "The field name is already taken."
          validates_inclusion_of :style, :in => ALLOWABLE_TYPES, :message => "should be one of: #{ALLOWABLE_TYPES.join(", ")}."
          validate :no_duplicate_select_options
          accepts_nested_attributes_for :field_select_options, :reject_if => proc {|o| o['option'].blank? }, :allow_destroy => true
          
          def self.reloadable? #:nodoc:
            false
          end
          
          def self.scoped_by(scope_object)
            foreign_key = "#{scope_object.class.name.downcase}_id"
            where(foreign_key => scope_object.id)
          end
          
          def scoped_by_class
            attributes.detect{|k,v| k.match(/_id/) && !v.nil?}[0].gsub("_id","")
          end
          
          def scoped_by_object
            scoped_by_class.classify.constantize.send(:find,eval("#{scoped_by_class}_id"))       
          end
          
          def scope_id=(scope_class_and_id)
            scope_class, scope_id = scope_class_and_id.split("_")
            self.send("#{scope_class.underscore}_id=", scope_id)
          end
          
          def select_options_data
            HasFields::FieldSelectOption.find_all_by_field_id(id).map{|o| o.option }
          end
          
          def no_duplicate_select_options
            if style == 'select' && (field_select_options.size != field_select_options.map{|o| o.option}.uniq.size)
              errors[:base] << "There are duplicate select options."
            end
          end

        end
      ::HasFields.const_set("Field", Object.const_get("Field"))
    end

    def HasFields.create_associated_values_class(klass)
      Object.const_set(HasFields.config[klass][:attributes_class_name],
      Class.new(ActiveRecord::Base)).class_eval do
        self.table_name = HasFields.config[klass][:attributes_table_name]
        validates_presence_of :field_id
        validates_inclusion_of :value, :in => Proc.new{|v| v.field.field_select_options.map{|opt| opt.option }}, :if => Proc.new{|o| o.field && o.field.style == "select" }
        belongs_to :field, :class_name => "::HasFields::Field"
        
        def self.reloadable? #:nodoc:
          false
        end
        
        def value
          string_value || boolean_value || date_value
        end
        
        def value=(v)
          send("#{data_type_from_field_style}_value=", v)
        end
        
        def data_type_from_field_style
          return "string" unless field
          case field.style
          when "date"
            "date"
          when "checkbox"
            "boolean"
          else
            "string"
          end
        end

        def validate
          field = self.field
          raise "Couldn't load field" if !field
          if field.style == "select" && !self.value.blank?
            if field.field_select_options.find{|f| f == self.value}.nil?
{'fraction_non_alphanumeric': 0.09576278052337357, 'fraction_numerical': 0.00016203516163007373, 'mean_word_length': 2.3968079251513483, 'pattern_counts': {'":': 6, '<': 7, '<?xml version=': 0, '>': 49, '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': '5605949', 'n_tokens_mistral': 3595, 'n_tokens_neox': 3545, 'n_words': 775}