attribute.rb 2.7 KB
Newer Older
1 2 3
module ActiveRecord
  class Attribute # :nodoc:
    class << self
4 5
      def from_database(name, value, type)
        FromDatabase.new(name, value, type)
6 7
      end

8 9
      def from_user(name, value, type)
        FromUser.new(name, value, type)
10
      end
11

12 13 14 15 16 17
      def null(name)
        Null.new(name)
      end

      def uninitialized(name, type)
        Uninitialized.new(name, type)
18
      end
19 20
    end

21
    attr_reader :name, :value_before_type_cast, :type
22 23 24

    # This method should not be called directly.
    # Use #from_database or #from_user
25 26
    def initialize(name, value_before_type_cast, type)
      @name = name
27 28 29 30 31 32 33 34 35 36 37 38 39 40
      @value_before_type_cast = value_before_type_cast
      @type = type
    end

    def value
      # `defined?` is cheaper than `||=` when we get back falsy values
      @value = type_cast(value_before_type_cast) unless defined?(@value)
      @value
    end

    def value_for_database
      type.type_cast_for_database(value)
    end

41 42 43 44 45 46 47 48
    def changed_from?(old_value)
      type.changed?(old_value, value, value_before_type_cast)
    end

    def changed_in_place_from?(old_value)
      type.changed_in_place?(old_value, value)
    end

49
    def with_value_from_user(value)
50
      self.class.from_user(name, value, type)
51 52 53
    end

    def with_value_from_database(value)
54
      self.class.from_database(name, value, type)
55 56
    end

57 58 59 60
    def type_cast
      raise NotImplementedError
    end

61 62 63 64
    def initialized?
      true
    end

65 66 67 68 69 70 71
    def ==(other)
      self.class == other.class &&
        name == other.name &&
        value_before_type_cast == other.value_before_type_cast &&
        type == other.type
    end

72 73 74 75 76 77 78 79
    protected

    def initialize_dup(other)
      if defined?(@value) && @value.duplicable?
        @value = @value.dup
      end
    end

80
    class FromDatabase < Attribute # :nodoc:
81 82 83 84 85
      def type_cast(value)
        type.type_cast_from_database(value)
      end
    end

86
    class FromUser < Attribute # :nodoc:
87 88 89 90
      def type_cast(value)
        type.type_cast_from_user(value)
      end
    end
91

92 93 94
    class Null < Attribute # :nodoc:
      def initialize(name)
        super(name, nil, Type::Value.new)
95
      end
96

97 98
      def value
        nil
99
      end
100 101 102 103 104

      def with_value_from_database(value)
        raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
      end
      alias_method :with_value_from_user, :with_value_from_database
105 106 107
    end

    class Uninitialized < Attribute # :nodoc:
108 109
      def initialize(name, type)
        super(name, nil, type)
110 111 112
      end

      def value
113 114 115 116 117 118
        if block_given?
          yield name
        end
      end

      def value_for_database
119 120 121 122
      end

      def initialized?
        false
123 124
      end
    end
125
    private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
126 127
  end
end