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

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

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

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

32 33
    BLACKLISTED_CLASS_METHODS = %w(private public protected)

34 35 36
    class AttributeMethodCache
      def initialize
        @module = Module.new
37
        @method_cache = ThreadSafe::Cache.new
38 39 40
      end

      def [](name)
41 42 43 44 45 46
        @method_cache.compute_if_absent(name) do
          safe_name = name.unpack('h*').first
          temp_method = "__temp__#{safe_name}"
          ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
          @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
          @module.instance_method temp_method
47 48 49 50
        end
      end

      private
B
Bin Huang 已提交
51 52 53 54 55

      # Override this method in the subclasses for method body.
      def method_body(method_name, const_name)
        raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
      end
56 57
    end

58
    module ClassMethods
59 60 61 62 63 64
      def inherited(child_class) #:nodoc:
        child_class.initialize_generated_modules
        super
      end

      def initialize_generated_modules # :nodoc:
65
        @generated_attribute_methods = Module.new { extend Mutex_m }
66
        @attribute_methods_generated = false
67 68 69
        include @generated_attribute_methods
      end

70 71
      # Generates all the attribute related methods for columns in the database
      # accessors, mutators and query methods.
72
      def define_attribute_methods # :nodoc:
73
        return false if @attribute_methods_generated
V
Vipul A M 已提交
74
        # Use a mutex; we don't want two thread simultaneously trying to define
J
Jon Leighton 已提交
75
        # attribute methods.
76
        generated_attribute_methods.synchronize do
77
          return false if @attribute_methods_generated
J
Jon Leighton 已提交
78 79 80 81
          superclass.define_attribute_methods unless self == base_class
          super(column_names)
          @attribute_methods_generated = true
        end
82
        true
83 84
      end

85
      def undefine_attribute_methods # :nodoc:
86 87 88 89
        generated_attribute_methods.synchronize do
          super if @attribute_methods_generated
          @attribute_methods_generated = false
        end
90 91
      end

92 93 94 95 96 97 98 99 100 101 102 103 104 105
      # 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
106
      def instance_method_already_implemented?(method_name)
107
        if dangerous_attribute_method?(method_name)
X
Xavier Noria 已提交
108
          raise DangerousAttributeError, "#{method_name} is defined by Active Record"
109 110
        end

J
Jon Leighton 已提交
111
        if superclass == Base
J
Jon Leighton 已提交
112 113
          super
        else
114
          # If B < A and A defines its own attribute method, then we don't want to overwrite that.
A
Arthur Neves 已提交
115 116 117
          defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
          base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
          defined && !base_defined || super
J
Jon Leighton 已提交
118
        end
119
      end
120

121
      # A method name is 'dangerous' if it is already (re)defined by Active Record, but
122
      # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
123
      def dangerous_attribute_method?(name) # :nodoc:
J
Jon Leighton 已提交
124 125
        method_defined_within?(name, Base)
      end
126

127
      def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
J
Jon Leighton 已提交
128
        if klass.method_defined?(name) || klass.private_method_defined?(name)
129 130 131 132 133 134 135 136 137 138 139 140 141
          if superklass.method_defined?(name) || superklass.private_method_defined?(name)
            klass.instance_method(name).owner != superklass.instance_method(name).owner
          else
            true
          end
        else
          false
        end
      end

      # A class method is 'dangerous' if it is already (re)defined by Active Record, but
      # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
      def dangerous_class_method?(method_name)
142
        BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
143 144 145 146 147 148
      end

      def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
        if klass.respond_to?(name, true)
          if superklass.respond_to?(name, true)
            klass.method(name).owner != superklass.method(name).owner
J
Jon Leighton 已提交
149 150 151 152 153 154
          else
            true
          end
        else
          false
        end
155
      end
156

157 158 159 160 161 162 163 164 165 166
      def find_generated_attribute_method(method_name) # :nodoc:
        klass = self
        until klass == Base
          gen_methods = klass.generated_attribute_methods
          return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object)
          klass = klass.superclass
        end
        nil
      end

167 168 169 170 171 172 173 174 175
      # 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
176 177 178 179
      def attribute_method?(attribute)
        super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
      end

180 181 182 183 184 185 186 187
      # 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"]
188 189 190 191 192 193 194
      def attribute_names
        @attribute_names ||= if !abstract_class? && table_exists?
            column_names
          else
            []
          end
      end
195
    end
J
Joshua Peek 已提交
196

197 198
    # If we haven't generated any methods yet, generate them, then
    # see if we've created the method we're looking for.
199
    def method_missing(method, *args, &block) # :nodoc:
200 201
      self.class.define_attribute_methods
      if respond_to_without_attributes?(method)
202 203 204 205 206
        # make sure to invoke the correct attribute method, as we might have gotten here via a `super`
        # call in a overwritten attribute method
        if attribute_method = self.class.find_generated_attribute_method(method)
          # this is probably horribly slow, but should only happen at most once for a given AR class
          attribute_method.bind(self).call(*args, &block)
207
        else
208
          return super unless respond_to_missing?(method, true)
209
          send(method, *args, &block)
210
        end
211 212
      else
        super
213 214 215
      end
    end

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
    # 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
232
    def respond_to?(name, include_private = false)
233
      name = name.to_s
234
      self.class.define_attribute_methods
235 236
      result = super

237
      # If the result is false the answer is false.
238 239 240 241
      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.
242
      # We check defined?(@raw_attributes) not to issue warnings if called on objects that
243
      # have been allocated but not yet initialized.
244
      if defined?(@raw_attributes) && @raw_attributes.any? && self.class.column_names.include?(name)
245 246 247 248
        return has_attribute?(name)
      end

      return true
249
    end
250

251 252 253 254 255 256 257 258 259
    # 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
260
    def has_attribute?(attr_name)
261
      @raw_attributes.has_key?(attr_name.to_s)
262 263 264
    end

    # Returns an array of names for the attributes available on this object.
265 266 267 268 269 270 271
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.attribute_names
    #   # => ["id", "created_at", "updated_at", "name", "age"]
272
    def attribute_names
273
      @raw_attributes.keys
274 275 276
    end

    # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
277 278 279 280 281 282 283
    #
    #   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}
284
    def attributes
285 286 287
      attribute_names.each_with_object({}) { |name, attrs|
        attrs[name] = read_attribute(name)
      }
288 289 290 291
    end

    # Returns an <tt>#inspect</tt>-like string for the value of the
    # attribute +attr_name+. String attributes are truncated upto 50
J
Jan Bernacki 已提交
292 293 294 295
    # characters, Date and Time attributes are returned in the
    # <tt>:db</tt> format, Array attributes are truncated upto 10 values.
    # Other attributes return the value of <tt>#inspect</tt> without
    # modification.
296
    #
297
    #   person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
298 299
    #
    #   person.attribute_for_inspect(:name)
300
    #   # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
301 302
    #
    #   person.attribute_for_inspect(:created_at)
303
    #   # => "\"2012-10-22 00:15:07\""
J
Jan Bernacki 已提交
304 305 306
    #
    #   person.attribute_for_inspect(:tag_ids)
    #   # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"
307 308 309 310
    def attribute_for_inspect(attr_name)
      value = read_attribute(attr_name)

      if value.is_a?(String) && value.length > 50
311
        "#{value[0, 50]}...".inspect
312 313
      elsif value.is_a?(Date) || value.is_a?(Time)
        %("#{value.to_s(:db)}")
J
Jan Bernacki 已提交
314 315 316
      elsif value.is_a?(Array) && value.size > 10
        inspected = value.first(10).inspect
        %(#{inspected[0...-1]}, ...])
317 318 319 320 321
      else
        value.inspect
      end
    end

322 323 324
    # 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+.
325
    # Note that it always returns +true+ with boolean attributes.
326
    #
327
    #   class Task < ActiveRecord::Base
328 329
    #   end
    #
330 331 332
    #   task = Task.new(title: '', is_done: false)
    #   task.attribute_present?(:title)   # => false
    #   task.attribute_present?(:is_done) # => true
G
Guillermo Iguaran 已提交
333
    #   task.title = 'Buy milk'
334 335 336
    #   task.is_done = true
    #   task.attribute_present?(:title)   # => true
    #   task.attribute_present?(:is_done) # => true
337 338
    def attribute_present?(attribute)
      value = read_attribute(attribute)
339
      !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
340 341
    end

342 343 344 345 346 347 348 349 350 351 352
    # 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)
353
    #   # => #<ActiveRecord::ConnectionAdapters::Column:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
354
    def column_for_attribute(name)
355 356 357 358
      name = name.to_s
      self.class.columns_hash.fetch(name) do
        ConnectionAdapters::Column.new(name, nil, Type::Value.new)
      end
359 360
    end

361
    # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
362
    # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises
363 364
    # <tt>ActiveModel::MissingAttributeError</tt> if the identified attribute is missing.
    #
365 366
    # Note: +:id+ is always present.
    #
367
    # Alias for the <tt>read_attribute</tt> method.
368 369
    #
    #   class Person < ActiveRecord::Base
370
    #     belongs_to :organization
371 372
    #   end
    #
373
    #   person = Person.new(name: 'Francesco', age: '22')
374 375
    #   person[:name] # => "Francesco"
    #   person[:age]  # => 22
376 377 378 379
    #
    #   person = Person.select('id').first
    #   person[:name]            # => ActiveModel::MissingAttributeError: missing attribute: name
    #   person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
380
    def [](attr_name)
381
      read_attribute(attr_name) { |n| missing_attribute(n, caller) }
382 383 384
    end

    # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
385 386 387 388 389 390 391 392 393
    # (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
394 395 396 397
    def []=(attr_name, value)
      write_attribute(attr_name, value)
    end

398
    protected
399

400
    def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
401 402 403 404 405 406
      attribute_names.each do |name|
        attributes[name] = clone_attribute_value(reader_method, name)
      end
      attributes
    end

407
    def clone_attribute_value(reader_method, attribute_name) # :nodoc:
408 409 410 411 412 413
      value = send(reader_method, attribute_name)
      value.duplicable? ? value.clone : value
    rescue TypeError, NoMethodError
      value
    end

414
    def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
J
Jon Leighton 已提交
415
      arel_attributes_with_values(attributes_for_create(attribute_names))
R
Robin Roestenburg 已提交
416
    end
417

418
    def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
R
Robin Roestenburg 已提交
419
      arel_attributes_with_values(attributes_for_update(attribute_names))
420 421
    end

422
    def attribute_method?(attr_name) # :nodoc:
423
      # We check defined? because Syck calls respond_to? before actually calling initialize.
424
      defined?(@raw_attributes) && @raw_attributes.include?(attr_name)
425 426
    end

R
Robin Roestenburg 已提交
427
    private
428

R
Robin Roestenburg 已提交
429
    # Returns a Hash of the Arel::Attributes and attribute values that have been
430
    # typecasted for use in an Arel insert/update method.
R
Robin Roestenburg 已提交
431 432 433
    def arel_attributes_with_values(attribute_names)
      attrs = {}
      arel_table = self.class.arel_table
434

R
Robin Roestenburg 已提交
435 436 437
      attribute_names.each do |name|
        attrs[arel_table[name]] = typecasted_attribute_value(name)
      end
438 439 440
      attrs
    end

R
Robin Roestenburg 已提交
441 442
    # Filters the primary keys and readonly attributes from the attribute names.
    def attributes_for_update(attribute_names)
443 444
      attribute_names.reject do |name|
        readonly_attribute?(name)
R
Robin Roestenburg 已提交
445 446 447 448 449
      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 已提交
450
    def attributes_for_create(attribute_names)
451 452
      attribute_names.reject do |name|
        pk_attribute?(name) && id.nil?
R
Robin Roestenburg 已提交
453
      end
454 455 456 457 458 459
    end

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

R
Robin Roestenburg 已提交
460
    def pk_attribute?(name)
S
Sean Griffin 已提交
461
      name == self.class.primary_key
462 463 464
    end

    def typecasted_attribute_value(name)
465 466 467 468
      # 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)
469
    end
470 471
  end
end