attribute_methods.rb 14.2 KB
Newer Older
J
Jeremy Kemper 已提交
1
require 'active_support/core_ext/enumerable'
2
require 'mutex_m'
J
Jeremy Kemper 已提交
3

4
module ActiveRecord
5
  # = Active Record Attribute Methods
6
  module AttributeMethods
7
    extend ActiveSupport::Concern
8
    include ActiveModel::AttributeMethods
9

10
    included do
11
      initialize_generated_modules
12 13 14 15 16 17 18 19 20 21
      include Read
      include Write
      include BeforeTypeCast
      include Query
      include PrimaryKey
      include TimeZoneConversion
      include Dirty
      include Serialization
    end

22 23 24 25
    AttrNames = Module.new {
      def self.set_name_cache(name, value)
        const_name = "ATTR_#{name}"
        unless const_defined? const_name
26
          const_set const_name, value.dup.freeze
27 28 29 30
        end
      end
    }

31
    module ClassMethods
32 33 34 35 36 37
      def inherited(child_class) #:nodoc:
        child_class.initialize_generated_modules
        super
      end

      def initialize_generated_modules # :nodoc:
38
        @generated_attribute_methods = Module.new { extend Mutex_m }
39
        @attribute_methods_generated = false
40 41 42
        include @generated_attribute_methods
      end

43 44
      # Generates all the attribute related methods for columns in the database
      # accessors, mutators and query methods.
45
      def define_attribute_methods # :nodoc:
V
Vipul A M 已提交
46
        # Use a mutex; we don't want two thread simultaneously trying to define
J
Jon Leighton 已提交
47
        # attribute methods.
48
        generated_attribute_methods.synchronize do
49
          return false if @attribute_methods_generated
J
Jon Leighton 已提交
50 51 52 53
          superclass.define_attribute_methods unless self == base_class
          super(column_names)
          @attribute_methods_generated = true
        end
54
        true
55 56
      end

57
      def undefine_attribute_methods # :nodoc:
58 59 60 61
        generated_attribute_methods.synchronize do
          super if @attribute_methods_generated
          @attribute_methods_generated = false
        end
62 63
      end

64 65 66 67 68 69 70 71 72 73 74 75 76 77
      # Raises a <tt>ActiveRecord::DangerousAttributeError</tt> exception when an
      # \Active \Record method is defined in the model, otherwise +false+.
      #
      #   class Person < ActiveRecord::Base
      #     def save
      #       'already defined by Active Record'
      #     end
      #   end
      #
      #   Person.instance_method_already_implemented?(:save)
      #   # => ActiveRecord::DangerousAttributeError: save is defined by ActiveRecord
      #
      #   Person.instance_method_already_implemented?(:name)
      #   # => false
78
      def instance_method_already_implemented?(method_name)
79
        if dangerous_attribute_method?(method_name)
X
Xavier Noria 已提交
80
          raise DangerousAttributeError, "#{method_name} is defined by Active Record"
81 82
        end

J
Jon Leighton 已提交
83
        if superclass == Base
J
Jon Leighton 已提交
84 85
          super
        else
86 87 88
          # If B < A and A defines its own attribute method, then we don't want to overwrite that.
          defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
          defined && !ActiveRecord::Base.method_defined?(method_name) || super
J
Jon Leighton 已提交
89
        end
90
      end
91

92 93
      # A method name is 'dangerous' if it is already defined by Active Record, but
      # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
94
      def dangerous_attribute_method?(name) # :nodoc:
J
Jon Leighton 已提交
95 96
        method_defined_within?(name, Base)
      end
97

98
      def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
J
Jon Leighton 已提交
99 100 101 102 103 104 105 106 107
        if klass.method_defined?(name) || klass.private_method_defined?(name)
          if sup.method_defined?(name) || sup.private_method_defined?(name)
            klass.instance_method(name).owner != sup.instance_method(name).owner
          else
            true
          end
        else
          false
        end
108
      end
109

110 111 112 113 114 115 116 117 118
      # Returns +true+ if +attribute+ is an attribute method and table exists,
      # +false+ otherwise.
      #
      #   class Person < ActiveRecord::Base
      #   end
      #
      #   Person.attribute_method?('name')   # => true
      #   Person.attribute_method?(:age=)    # => true
      #   Person.attribute_method?(:nothing) # => false
119 120 121 122
      def attribute_method?(attribute)
        super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
      end

123 124 125 126 127 128 129 130
      # Returns an array of column names as strings if it's not an abstract class and
      # table exists. Otherwise it returns an empty array.
      #
      #   class Person < ActiveRecord::Base
      #   end
      #
      #   Person.attribute_names
      #   # => ["id", "created_at", "updated_at", "name", "age"]
131 132 133 134 135 136 137
      def attribute_names
        @attribute_names ||= if !abstract_class? && table_exists?
            column_names
          else
            []
          end
      end
138
    end
J
Joshua Peek 已提交
139

140 141
    # If we haven't generated any methods yet, generate them, then
    # see if we've created the method we're looking for.
142
    def method_missing(method, *args, &block) # :nodoc:
143
      if self.class.define_attribute_methods
144 145 146 147 148
        if respond_to_without_attributes?(method)
          send(method, *args, &block)
        else
          super
        end
149 150
      else
        super
151 152 153
      end
    end

154
    def attribute_missing(match, *args, &block) # :nodoc:
155 156 157 158 159 160 161 162 163 164 165 166 167
      if self.class.columns_hash[match.attr_name]
        ActiveSupport::Deprecation.warn(
          "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
          "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
          "is a column of the table. If this error has happened through normal usage of Active " \
          "Record (rather than through your own code or external libraries), please report it as " \
          "a bug."
        )
      end

      super
    end

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
    # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
    # which will all return +true+. It also define the attribute methods if they have
    # not been generated.
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.respond_to(:name)    # => true
    #   person.respond_to(:name=)   # => true
    #   person.respond_to(:name?)   # => true
    #   person.respond_to('age')    # => true
    #   person.respond_to('age=')   # => true
    #   person.respond_to('age?')   # => true
    #   person.respond_to(:nothing) # => false
184
    def respond_to?(name, include_private = false)
185
      name = name.to_s
186
      self.class.define_attribute_methods
187 188
      result = super

189
      # If the result is false the answer is false.
190 191 192 193
      return false unless result

      # If the result is true then check for the select case.
      # For queries selecting a subset of columns, return false for unselected columns.
194 195
      # We check defined?(@attributes) not to issue warnings if called on objects that
      # have been allocated but not yet initialized.
196
      if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
197 198 199 200
        return has_attribute?(name)
      end

      return true
201
    end
202

203 204 205 206 207 208 209 210 211
    # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.has_attribute?(:name)    # => true
    #   person.has_attribute?('age')    # => true
    #   person.has_attribute?(:nothing) # => false
212 213 214 215 216
    def has_attribute?(attr_name)
      @attributes.has_key?(attr_name.to_s)
    end

    # Returns an array of names for the attributes available on this object.
217 218 219 220 221 222 223
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.attribute_names
    #   # => ["id", "created_at", "updated_at", "name", "age"]
224 225 226 227 228
    def attribute_names
      @attributes.keys
    end

    # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
229 230 231 232 233 234 235
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.create(name: 'Francesco', age: 22)
    #   person.attributes
    #   # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}
236
    def attributes
237 238 239
      attribute_names.each_with_object({}) { |name, attrs|
        attrs[name] = read_attribute(name)
      }
240 241 242 243 244 245 246 247
    end

    # Returns an <tt>#inspect</tt>-like string for the value of the
    # attribute +attr_name+. String attributes are truncated upto 50
    # characters, and Date and Time attributes are returned in the
    # <tt>:db</tt> format. Other attributes return the value of
    # <tt>#inspect</tt> without modification.
    #
248
    #   person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
249 250
    #
    #   person.attribute_for_inspect(:name)
251
    #   # => "\"David Heinemeier Hansson David Heinemeier Hansson D...\""
252 253
    #
    #   person.attribute_for_inspect(:created_at)
254
    #   # => "\"2012-10-22 00:15:07\""
255 256 257 258 259 260 261 262 263 264 265 266
    def attribute_for_inspect(attr_name)
      value = read_attribute(attr_name)

      if value.is_a?(String) && value.length > 50
        "#{value[0..50]}...".inspect
      elsif value.is_a?(Date) || value.is_a?(Time)
        %("#{value.to_s(:db)}")
      else
        value.inspect
      end
    end

267 268 269
    # Returns +true+ if the specified +attribute+ has been set by the user or by a
    # database load and is neither +nil+ nor <tt>empty?</tt> (the latter only applies
    # to objects that respond to <tt>empty?</tt>, most notably Strings). Otherwise, +false+.
270
    # Note that it always returns +true+ with boolean attributes.
271
    #
272
    #   class Task < ActiveRecord::Base
273 274
    #   end
    #
275 276 277
    #   person = Task.new(title: '', is_done: false)
    #   person.attribute_present?(:title)   # => false
    #   person.attribute_present?(:is_done) # => true
278
    #   person.name = 'Francesco'
279 280 281
    #   person.is_done = true
    #   person.attribute_present?(:title)   # => true
    #   person.attribute_present?(:is_done) # => true
282 283
    def attribute_present?(attribute)
      value = read_attribute(attribute)
284
      !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
285 286
    end

287 288 289 290 291 292 293 294 295 296 297 298
    # Returns the column object for the named attribute. Returns +nil+ if the
    # named attribute not exists.
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
    #   # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
    #
    #   person.column_for_attribute(:nothing)
    #   # => nil
299
    def column_for_attribute(name)
A
Aaron Patterson 已提交
300
      # FIXME: should this return a null object for columns that don't exist?
301 302 303
      self.class.columns_hash[name.to_s]
    end

304
    # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
305 306 307 308
    # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). It raises
    # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
    #
    # Alias for the <tt>read_attribute</tt> method.
309 310
    #
    #   class Person < ActiveRecord::Base
311
    #     belongs_to :organization
312 313
    #   end
    #
314
    #   person = Person.new(name: 'Francesco', age: '22')
315 316
    #   person[:name] # => "Francesco"
    #   person[:age]  # => 22
317 318 319 320
    #
    #   person = Person.select('id').first
    #   person[:name]            # => ActiveModel::MissingAttributeError: missing attribute: name
    #   person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
321
    def [](attr_name)
322
      read_attribute(attr_name) { |n| missing_attribute(n, caller) }
323 324 325
    end

    # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
326 327 328 329 330 331 332 333 334
    # (Alias for the protected <tt>write_attribute</tt> method).
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person[:age] = '22'
    #   person[:age] # => 22
    #   person[:age] # => Fixnum
335 336 337 338
    def []=(attr_name, value)
      write_attribute(attr_name, value)
    end

339
    protected
340

341
    def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
342 343 344 345 346 347
      attribute_names.each do |name|
        attributes[name] = clone_attribute_value(reader_method, name)
      end
      attributes
    end

348
    def clone_attribute_value(reader_method, attribute_name) # :nodoc:
349 350 351 352 353 354
      value = send(reader_method, attribute_name)
      value.duplicable? ? value.clone : value
    rescue TypeError, NoMethodError
      value
    end

355
    def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
J
Jon Leighton 已提交
356
      arel_attributes_with_values(attributes_for_create(attribute_names))
R
Robin Roestenburg 已提交
357
    end
358

359
    def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
R
Robin Roestenburg 已提交
360
      arel_attributes_with_values(attributes_for_update(attribute_names))
361 362
    end

363
    def attribute_method?(attr_name) # :nodoc:
364
      # We check defined? because Syck calls respond_to? before actually calling initialize.
365
      defined?(@attributes) && @attributes.include?(attr_name)
366 367
    end

R
Robin Roestenburg 已提交
368
    private
369

R
Robin Roestenburg 已提交
370
    # Returns a Hash of the Arel::Attributes and attribute values that have been
371
    # typecasted for use in an Arel insert/update method.
R
Robin Roestenburg 已提交
372 373 374
    def arel_attributes_with_values(attribute_names)
      attrs = {}
      arel_table = self.class.arel_table
375

R
Robin Roestenburg 已提交
376 377 378
      attribute_names.each do |name|
        attrs[arel_table[name]] = typecasted_attribute_value(name)
      end
379 380 381
      attrs
    end

R
Robin Roestenburg 已提交
382 383 384
    # Filters the primary keys and readonly attributes from the attribute names.
    def attributes_for_update(attribute_names)
      attribute_names.select do |name|
385
        column_for_attribute(name) && !readonly_attribute?(name)
R
Robin Roestenburg 已提交
386 387 388 389 390
      end
    end

    # Filters out the primary keys, from the attribute names, when the primary
    # key is to be generated (e.g. the id attribute has no value).
J
Jon Leighton 已提交
391 392 393
    def attributes_for_create(attribute_names)
      attribute_names.select do |name|
        column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
R
Robin Roestenburg 已提交
394
      end
395 396 397 398 399 400
    end

    def readonly_attribute?(name)
      self.class.readonly_attributes.include?(name)
    end

R
Robin Roestenburg 已提交
401 402
    def pk_attribute?(name)
      column_for_attribute(name).primary
403 404 405
    end

    def typecasted_attribute_value(name)
406 407 408 409
      # FIXME: we need @attributes to be used consistently.
      # If the values stored in @attributes were already typecasted, this code
      # could be simplified
      read_attribute(name)
410
    end
411 412
  end
end