<commit_before>require 'active_support/concern'
require 'active_support/core_ext/array/extract_options'

module CryptKeeper
  module Model
    extend ActiveSupport::Concern

    def before_save_encrypt
      crypt_keeper_fields.each do |field|
        self[field] = self.class.encrypt read_attribute(field)
      end
    end

    def after_save_decrypt
      crypt_keeper_fields.each do |field|
        self[field] = self.class.decrypt read_attribute(field)
      end
    end

    module ClassMethods
      def crypt_keeper(*fields)
        class_attribute :crypt_keeper_options
        class_attribute :crypt_keeper_fields
        class_attribute :crypt_keeper_encryptor

        self.crypt_keeper_options   = fields.extract_options!
        self.crypt_keeper_encryptor = crypt_keeper_options.delete(:encryptor)
        self.crypt_keeper_fields    = fields

        ensure_valid_encryptor!
        define_crypt_keeper_callbacks
      end

      def encrypt(value)
        encryptor.encrypt value
      end

      def decrypt(value)
        encryptor.decrypt value
      end

      private

      def encryptor
        crypt_keeper_encryptor.new(crypt_keeper_options)
      end

      def ensure_valid_encryptor!
        unless crypt_keeper_encryptor.respond_to?(:new)
          raise "You must specify an encryption class `crypt_keeper encryptor: EncryptionClass`"
        end
      end

      def define_crypt_keeper_callbacks
        after_save :after_save_decrypt
        after_find :after_save_decrypt
        before_save :before_save_encrypt

        crypt_keeper_fields.each do |field|
          ensure_field_is_encryptable! field
        end
      end

      def ensure_field_is_encryptable!(field)
        unless columns_hash["#{field}"].type == :text
          raise ArgumentError, ":#{field} must be of type 'text' to be used for encryption"
        end
      end
    end
  end
end
<commit_msg>Adding docs, moving some methods to private<commit_after>require 'active_support/concern'
require 'active_support/core_ext/array/extract_options'

module CryptKeeper
  module Model
    extend ActiveSupport::Concern

    private

    # Private: Encrypt each crypt_keeper_fields
    def before_save_encrypt
      crypt_keeper_fields.each do |field|
        self[field] = self.class.encrypt read_attribute(field)
      end
    end

    # Private: Decrypt each crypt_keeper_fields
    def after_save_decrypt
      crypt_keeper_fields.each do |field|
        self[field] = self.class.decrypt read_attribute(field)
      end
    end

    module ClassMethods
      # Public: Setup fields for encryption
      #
      #   args - An array of fields to encrypt. The last argument should be
      #   a hash of options. Note, an :encryptor is required. This should be
      #   a class that takes a hash for initialize and provides an encrypt
      #   and decrypt method.
      #
      # Example
      #
      #   class MyModel < ActiveRecord::Base
      #     crypt_keeper :field, :other_field, encryptor: EncryptionClass,
      #       passphrase: '<PASSWORD>'
      #
      #   end
      #
      def crypt_keeper(*args)
        class_attribute :crypt_keeper_options
        class_attribute :crypt_keeper_fields
        class_attribute :crypt_keeper_encryptor

        self.crypt_keeper_options   = args.extract_options!
        self.crypt_keeper_encryptor = crypt_keeper_options.delete(:encryptor)
        self.crypt_keeper_fields    = args

        ensure_valid_encryptor!
        define_crypt_keeper_callbacks
      end

      # Public: Encrypts a string with the encryptor
      def encrypt(value)
        encryptor.encrypt value
      end

      # Public: Decrypts a string with the encryptor
      def decrypt(value)
        encryptor.decrypt value
      end

      private

      # Private: An instance of the encryptor class
      def encryptor
        crypt_keeper_encryptor.new(crypt_keeper_options)
      end

      # Private: Ensure that the encryptor responds to new
      def ensure_valid_encryptor!
        unless crypt_keeper_encryptor.respond_to?(:new)
          raise "You must specify an encryption class `crypt_keeper encryptor: EncryptionClass`"
        end
      end

      # Private: Define callbacks for encryption
      def define_crypt_keeper_callbacks
        after_save :after_save_decrypt
        after_find :after_save_decrypt
        before_save :before_save_encrypt

        crypt_keeper_fields.each do |field|
          ensure_field_is_encryptable! field
        end
      end

      # Private: Ensures that each field is of type text. This prevents
      # encrypted data from being truncated
      def ensure_field_is_encryptable!(field)
        unless columns_hash["#{field}"].type == :text
          raise ArgumentError, ":#{field} must be of type 'text' to be used for encryption"
        end
      end
    end
  end
end

{'fraction_non_alphanumeric': 0.05354200988467875, 'fraction_numerical': 0.0, 'mean_word_length': 2.58980044345898, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 4, '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': '3594003', 'n_tokens_mistral': 1426, 'n_tokens_neox': 1358, 'n_words': 357}