attribute_methods.rb 15.0 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 34
    class AttributeMethodCache
      def initialize
        @module = Module.new
35
        @method_cache = ThreadSafe::Cache.new
36 37 38
      end

      def [](name)
39 40 41 42 43 44
        @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
45 46 47 48 49 50 51
        end
      end

      private
      def method_body; raise NotImplementedError; end
    end

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

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

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

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

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

J
Jon Leighton 已提交
104
        if superclass == Base
J
Jon Leighton 已提交
105 106
          super
        else
107 108 109
          # 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 已提交
110
        end
111
      end
112

113 114
      # 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.)
115
      def dangerous_attribute_method?(name) # :nodoc:
J
Jon Leighton 已提交
116 117
        method_defined_within?(name, Base)
      end
118

119
      def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc:
J
Jon Leighton 已提交
120 121 122 123 124 125 126 127 128
        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
129
      end
130

131 132 133 134 135 136 137 138 139 140
      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

141 142 143 144 145 146 147 148 149
      # 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
150 151 152 153
      def attribute_method?(attribute)
        super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
      end

154 155 156 157 158 159 160 161
      # 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"]
162 163 164 165 166 167 168
      def attribute_names
        @attribute_names ||= if !abstract_class? && table_exists?
            column_names
          else
            []
          end
      end
169
    end
J
Joshua Peek 已提交
170

171 172
    # If we haven't generated any methods yet, generate them, then
    # see if we've created the method we're looking for.
173
    def method_missing(method, *args, &block) # :nodoc:
174
      if self.class.define_attribute_methods
175
        if respond_to_without_attributes?(method)
176 177 178 179 180 181 182 183
          # 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)
          else
            send(method, *args, &block)
          end
184 185 186
        else
          super
        end
187 188
      else
        super
189 190 191
      end
    end

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
    # 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
208
    def respond_to?(name, include_private = false)
209
      name = name.to_s
210
      self.class.define_attribute_methods
211 212
      result = super

213
      # If the result is false the answer is false.
214 215 216 217
      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.
218 219
      # We check defined?(@attributes) not to issue warnings if called on objects that
      # have been allocated but not yet initialized.
220
      if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
221 222 223 224
        return has_attribute?(name)
      end

      return true
225
    end
226

227 228 229 230 231 232 233 234 235
    # 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
236 237 238 239 240
    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.
241 242 243 244 245 246 247
    #
    #   class Person < ActiveRecord::Base
    #   end
    #
    #   person = Person.new
    #   person.attribute_names
    #   # => ["id", "created_at", "updated_at", "name", "age"]
248 249 250 251 252
    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.
253 254 255 256 257 258 259
    #
    #   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}
260
    def attributes
261 262 263
      attribute_names.each_with_object({}) { |name, attrs|
        attrs[name] = read_attribute(name)
      }
264 265 266 267 268 269 270 271
    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.
    #
272
    #   person = Person.create!(name: 'David Heinemeier Hansson ' * 3)
273 274
    #
    #   person.attribute_for_inspect(:name)
275
    #   # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\""
276 277
    #
    #   person.attribute_for_inspect(:created_at)
278
    #   # => "\"2012-10-22 00:15:07\""
279 280 281 282
    def attribute_for_inspect(attr_name)
      value = read_attribute(attr_name)

      if value.is_a?(String) && value.length > 50
283
        "#{value[0, 50]}...".inspect
284 285 286 287 288 289 290
      elsif value.is_a?(Date) || value.is_a?(Time)
        %("#{value.to_s(:db)}")
      else
        value.inspect
      end
    end

291 292 293
    # 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+.
294
    # Note that it always returns +true+ with boolean attributes.
295
    #
296
    #   class Task < ActiveRecord::Base
297 298
    #   end
    #
299 300 301
    #   person = Task.new(title: '', is_done: false)
    #   person.attribute_present?(:title)   # => false
    #   person.attribute_present?(:is_done) # => true
302
    #   person.name = 'Francesco'
303 304 305
    #   person.is_done = true
    #   person.attribute_present?(:title)   # => true
    #   person.attribute_present?(:is_done) # => true
306 307
    def attribute_present?(attribute)
      value = read_attribute(attribute)
308
      !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
309 310
    end

311 312 313 314 315 316 317 318 319 320 321 322
    # 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
323
    def column_for_attribute(name)
A
Aaron Patterson 已提交
324
      # FIXME: should this return a null object for columns that don't exist?
325 326 327
      self.class.columns_hash[name.to_s]
    end

328
    # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
329 330 331 332
    # "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.
333 334
    #
    #   class Person < ActiveRecord::Base
335
    #     belongs_to :organization
336 337
    #   end
    #
338
    #   person = Person.new(name: 'Francesco', age: '22')
339 340
    #   person[:name] # => "Francesco"
    #   person[:age]  # => 22
341 342 343 344
    #
    #   person = Person.select('id').first
    #   person[:name]            # => ActiveModel::MissingAttributeError: missing attribute: name
    #   person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
345
    def [](attr_name)
346
      read_attribute(attr_name) { |n| missing_attribute(n, caller) }
347 348 349
    end

    # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
350 351 352 353 354 355 356 357 358
    # (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
359 360 361 362
    def []=(attr_name, value)
      write_attribute(attr_name, value)
    end

363
    protected
364

365
    def clone_attributes(reader_method = :read_attribute, attributes = {}) # :nodoc:
366 367 368 369 370 371
      attribute_names.each do |name|
        attributes[name] = clone_attribute_value(reader_method, name)
      end
      attributes
    end

372
    def clone_attribute_value(reader_method, attribute_name) # :nodoc:
373 374 375 376 377 378
      value = send(reader_method, attribute_name)
      value.duplicable? ? value.clone : value
    rescue TypeError, NoMethodError
      value
    end

379
    def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
J
Jon Leighton 已提交
380
      arel_attributes_with_values(attributes_for_create(attribute_names))
R
Robin Roestenburg 已提交
381
    end
382

383
    def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
R
Robin Roestenburg 已提交
384
      arel_attributes_with_values(attributes_for_update(attribute_names))
385 386
    end

387
    def attribute_method?(attr_name) # :nodoc:
388
      # We check defined? because Syck calls respond_to? before actually calling initialize.
389
      defined?(@attributes) && @attributes.include?(attr_name)
390 391
    end

R
Robin Roestenburg 已提交
392
    private
393

R
Robin Roestenburg 已提交
394
    # Returns a Hash of the Arel::Attributes and attribute values that have been
395
    # typecasted for use in an Arel insert/update method.
R
Robin Roestenburg 已提交
396 397 398
    def arel_attributes_with_values(attribute_names)
      attrs = {}
      arel_table = self.class.arel_table
399

R
Robin Roestenburg 已提交
400 401 402
      attribute_names.each do |name|
        attrs[arel_table[name]] = typecasted_attribute_value(name)
      end
403 404 405
      attrs
    end

R
Robin Roestenburg 已提交
406 407 408
    # Filters the primary keys and readonly attributes from the attribute names.
    def attributes_for_update(attribute_names)
      attribute_names.select do |name|
409
        column_for_attribute(name) && !readonly_attribute?(name)
R
Robin Roestenburg 已提交
410 411 412 413 414
      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 已提交
415 416 417
    def attributes_for_create(attribute_names)
      attribute_names.select do |name|
        column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
R
Robin Roestenburg 已提交
418
      end
419 420 421 422 423 424
    end

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

R
Robin Roestenburg 已提交
425 426
    def pk_attribute?(name)
      column_for_attribute(name).primary
427 428 429
    end

    def typecasted_attribute_value(name)
430 431 432 433
      # 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)
434
    end
435 436
  end
end