attribute_methods.rb 16.2 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 51 52 53
        end
      end

      private
      def method_body; raise NotImplementedError; end
    end

54
    module ClassMethods
55 56 57 58 59 60
      def inherited(child_class) #:nodoc:
        child_class.initialize_generated_modules
        super
      end

      def initialize_generated_modules # :nodoc:
61
        @generated_attribute_methods = Module.new { extend Mutex_m }
62
        @attribute_methods_generated = false
63 64 65
        include @generated_attribute_methods
      end

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

80
      def undefine_attribute_methods # :nodoc:
81 82 83 84
        generated_attribute_methods.synchronize do
          super if @attribute_methods_generated
          @attribute_methods_generated = false
        end
85 86
      end

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

J
Jon Leighton 已提交
106
        if superclass == Base
J
Jon Leighton 已提交
107 108
          super
        else
109
          # If B < A and A defines its own attribute method, then we don't want to overwrite that.
A
Arthur Neves 已提交
110 111 112
          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 已提交
113
        end
114
      end
115

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

122
      def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
J
Jon Leighton 已提交
123
        if klass.method_defined?(name) || klass.private_method_defined?(name)
124 125 126 127 128 129 130 131 132 133 134 135 136
          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)
137
        BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
138 139 140 141 142 143
      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 已提交
144 145 146 147 148 149
          else
            true
          end
        else
          false
        end
150
      end
151

152 153 154 155 156 157 158 159 160 161
      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

162 163 164 165 166 167 168 169 170
      # 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
171 172 173 174
      def attribute_method?(attribute)
        super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
      end

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

192 193
    # If we haven't generated any methods yet, generate them, then
    # see if we've created the method we're looking for.
194
    def method_missing(method, *args, &block) # :nodoc:
195 196
      self.class.define_attribute_methods
      if respond_to_without_attributes?(method)
197 198 199 200 201
        # 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)
202
        else
203
          send(method, *args, &block)
204
        end
205 206
      else
        super
207 208 209
      end
    end

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    # 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
226
    def respond_to?(name, include_private = false)
227
      name = name.to_s
228
      self.class.define_attribute_methods
229 230
      result = super

231
      # If the result is false the answer is false.
232 233 234 235
      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.
236 237
      # We check defined?(@attributes) not to issue warnings if called on objects that
      # have been allocated but not yet initialized.
238
      if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
239 240 241 242
        return has_attribute?(name)
      end

      return true
243
    end
244

245 246 247 248 249 250 251 252 253
    # 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
254 255 256 257 258
    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.
259 260 261 262 263 264 265
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.attribute_names
    #   # => ["id", "created_at", "updated_at", "name", "age"]
266 267 268 269 270
    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.
271 272 273 274 275 276 277
    #
    #   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}
278
    def attributes
279 280 281
      attribute_names.each_with_object({}) { |name, attrs|
        attrs[name] = read_attribute(name)
      }
282 283
    end

284 285 286 287 288
    # Placeholder so it can be overriden when needed by serialization
    def attributes_for_coder # :nodoc:
      attributes
    end

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

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

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

341 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)
    #   # => nil
353
    def column_for_attribute(name)
A
Aaron Patterson 已提交
354
      # FIXME: should this return a null object for columns that don't exist?
355 356 357
      self.class.columns_hash[name.to_s]
    end

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

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

395
    protected
396

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

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

411
    def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
J
Jon Leighton 已提交
412
      arel_attributes_with_values(attributes_for_create(attribute_names))
R
Robin Roestenburg 已提交
413
    end
414

415
    def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
R
Robin Roestenburg 已提交
416
      arel_attributes_with_values(attributes_for_update(attribute_names))
417 418
    end

419
    def attribute_method?(attr_name) # :nodoc:
420
      # We check defined? because Syck calls respond_to? before actually calling initialize.
421
      defined?(@attributes) && @attributes.include?(attr_name)
422 423
    end

R
Robin Roestenburg 已提交
424
    private
425

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

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

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

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

R
Robin Roestenburg 已提交
457
    def pk_attribute?(name)
S
Sean Griffin 已提交
458
      name == self.class.primary_key
459 460 461
    end

    def typecasted_attribute_value(name)
462 463 464 465
      # 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)
466
    end
467 468
  end
end