attribute.rb 5.3 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, original_attribute = nil)
        FromUser.new(name, value, type, original_attribute)
10
      end
11

12 13 14 15
      def with_cast_value(name, value, type)
        WithCastValue.new(name, value, type)
      end

16 17 18 19 20 21
      def null(name)
        Null.new(name)
      end

      def uninitialized(name, type)
        Uninitialized.new(name, type)
22
      end
23 24
    end

25
    attr_reader :name, :value_before_type_cast, :type
26 27 28

    # This method should not be called directly.
    # Use #from_database or #from_user
29
    def initialize(name, value_before_type_cast, type, original_attribute = nil)
30
      @name = name
31 32
      @value_before_type_cast = value_before_type_cast
      @type = type
33
      @original_attribute = original_attribute
34 35 36 37
    end

    def value
      # `defined?` is cheaper than `||=` when we get back falsy values
S
Sean Griffin 已提交
38
      @value = type_cast(value_before_type_cast) unless defined?(@value)
39 40 41
      @value
    end

42 43 44 45 46 47 48 49
    def original_value
      if assigned?
        original_attribute.original_value
      else
        type_cast(value_before_type_cast)
      end
    end

50
    def value_for_database
51
      type.serialize(value)
52 53
    end

54 55 56 57 58 59
    def changed?
      changed_from_assignment? || changed_in_place?
    end

    def changed_in_place?
      has_been_read? && type.changed_in_place?(original_value_for_database, value)
60 61
    end

62 63
    def forgetting_assignment
      with_value_from_database(value_for_database)
64 65
    end

66
    def with_value_from_user(value)
67
      type.assert_valid_value(value)
68
      self.class.from_user(name, value, type, self)
69 70 71
    end

    def with_value_from_database(value)
72
      self.class.from_database(name, value, type)
73 74
    end

75 76 77 78
    def with_cast_value(value)
      self.class.with_cast_value(name, value, type)
    end

79
    def with_type(type)
S
Sean Griffin 已提交
80 81 82 83 84
      if changed_in_place?
        with_value_from_user(value).with_type(type)
      else
        self.class.new(name, value_before_type_cast, type, original_attribute)
      end
85 86
    end

87
    def type_cast(*)
88 89 90
      raise NotImplementedError
    end

91 92 93 94
    def initialized?
      true
    end

95 96 97 98
    def came_from_user?
      false
    end

99 100 101 102
    def has_been_read?
      defined?(@value)
    end

103 104 105 106 107 108
    def ==(other)
      self.class == other.class &&
        name == other.name &&
        value_before_type_cast == other.value_before_type_cast &&
        type == other.type
    end
S
Sean Griffin 已提交
109 110 111 112 113
    alias eql? ==

    def hash
      [self.class, name, value_before_type_cast, type].hash
    end
114

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    def init_with(coder)
      @name = coder["name"]
      @value_before_type_cast = coder["value_before_type_cast"]
      @type = coder["type"]
      @original_attribute = coder["original_attribute"]
      @value = coder["value"] if coder.map.key?("value")
    end

    def encode_with(coder)
      coder["name"] = name
      coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
      coder["type"] = type if type
      coder["original_attribute"] = original_attribute if original_attribute
      coder["value"] = value if defined?(@value)
    end

131 132
    protected

133 134 135
    attr_reader :original_attribute
    alias_method :assigned?, :original_attribute

136 137 138 139 140 141
    def initialize_dup(other)
      if defined?(@value) && @value.duplicable?
        @value = @value.dup
      end
    end

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    def changed_from_assignment?
      assigned? && type.changed?(original_value, value, value_before_type_cast)
    end

    def original_value_for_database
      if assigned?
        original_attribute.original_value_for_database
      else
        _original_value_for_database
      end
    end

    def _original_value_for_database
      value_for_database
    end

158
    class FromDatabase < Attribute # :nodoc:
159
      def type_cast(value)
160
        type.deserialize(value)
161
      end
162 163 164 165

      def _original_value_for_database
        value_before_type_cast
      end
166 167
    end

168
    class FromUser < Attribute # :nodoc:
169
      def type_cast(value)
S
Sean Griffin 已提交
170
        type.cast(value)
171
      end
172 173 174 175

      def came_from_user?
        true
      end
176
    end
177

178 179 180 181 182 183 184 185 186 187
    class WithCastValue < Attribute # :nodoc:
      def type_cast(value)
        value
      end

      def changed_in_place_from?(old_value)
        false
      end
    end

188 189 190
    class Null < Attribute # :nodoc:
      def initialize(name)
        super(name, nil, Type::Value.new)
191
      end
192

193
      def type_cast(*)
194
        nil
195
      end
196

197 198 199 200
      def with_type(type)
        self.class.with_cast_value(name, nil, type)
      end

201 202 203 204
      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
205 206 207
    end

    class Uninitialized < Attribute # :nodoc:
208 209
      UNINITIALIZED_ORIGINAL_VALUE = Object.new

210 211
      def initialize(name, type)
        super(name, nil, type)
212 213 214
      end

      def value
215 216 217 218 219
        if block_given?
          yield name
        end
      end

220 221 222 223
      def original_value
        UNINITIALIZED_ORIGINAL_VALUE
      end

224
      def value_for_database
225 226 227 228
      end

      def initialized?
        false
229
      end
230 231 232 233

      def with_type(type)
        self.class.new(name, type)
      end
234
    end
235
    private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
236 237
  end
end