require 'referehencible/version'
require 'securerandom'

module Referehencible
  module ClassMethods
    def referenced_by(*referenced_attributes)
      referenced_attributes.each do |reference_attribute|
        validates       reference_attribute,
                          presence:         true,
                          uniqueness:       true,
                          format:           {
                            with:             /[a-f0-9]{32}/ },
                          length:           {
                            is:               32 }

        define_method(reference_attribute) do
          generate_guid(reference_attribute)
        end

        define_singleton_method("by_#{reference_attribute}") do |guid_to_find|
          where(:"#{reference_attribute}" => guid_to_find).
          first ||
          unknown_reference_object
        end

        after_initialize lambda { generate_guid reference_attribute }
      end

      private

      define_method(:generate_guid) do |reference_attribute|
        read_attribute(reference_attribute) || write_attribute(reference_attribute, Referehencible.reference_number)
      end

      define_singleton_method(:unknown_reference_object) do
        return new unless respond_to?(:as_null_object)

        as_null_object
      end
    end
  end

  def self.included(base)
    base.extend ClassMethods
  end

  def self.reference_number
    SecureRandom.hex(16)
  end
end

require 'referehencible/version'
require 'securerandom'

module Referehencible
  DEFAULT_LENGTH = 32

  module ClassMethods
    def referenced_by(*referenced_attributes)
      default_options = { length: DEFAULT_LENGTH }

      referenced_attributes = referenced_attributes.each_with_object({}) do |referenced_attribute, transformed_attributes|
                                case referenced_attribute
                                when Symbol
                                  transformed_attributes[referenced_attribute] = default_options
                                when Hash
                                  transformed_attributes.merge! referenced_attribute
                                end
                              end

      referenced_attributes.each do |reference_attribute, options|
        raise RuntimeError, "You attempted to pass in a length of #{options[:length]} for #{reference_attribute} in #{self.name}. Only even numbers are allowed." \
          if options[:length].odd?

        validates       reference_attribute,
                          presence:         true,
                          uniqueness:       true,
                          format:           {
                            with:             /[a-f0-9]{#{options[:length]}}/ },
                          length:           {
                            is:               options[:length] }

        define_method(reference_attribute) do
          generate_guid(reference_attribute, options[:length] / 2)
        end

        define_singleton_method("by_#{reference_attribute}") do |guid_to_find|
          where(:"#{reference_attribute}" => guid_to_find).
          first ||
          unknown_reference_object
        end

        after_initialize lambda { generate_guid reference_attribute, options[:length] / 2 }
      end

      private

      define_method(:generate_guid) do |reference_attribute, length|
        read_attribute(reference_attribute) || write_attribute(reference_attribute, SecureRandom.hex(length))
      end

      define_singleton_method(:unknown_reference_object) do
        return new unless respond_to?(:as_null_object)

        as_null_object
      end
    end
  end

  def self.included(base)
    base.extend ClassMethods
  end
end

Allow reference number length to be customizable
Feature: Allow reference number length to be customizable

Previously all reference numbers were forced to be 32 hex characters.  We had
a need for the exact functionality that referehencible provides, but for an
attribute where we only wanted 8 characters.

Now we can do that by passing a `length` option to `referenced_by` like so:

```ruby
referenced_by :guid => { length: 8 }
```

You can also mix and match.  If you have some reference attributes which you
want to remain the default length of 32, you can do so like this:

```ruby
referenced_by :guid,
              :access_token,
              :short_code => { length: 8 }
```
