model_schema.rb 13.3 KB
Newer Older
1 2 3 4
module ActiveRecord
  module ModelSchema
    extend ActiveSupport::Concern

5
    included do
6 7 8 9 10 11 12
      ##
      # :singleton-method:
      # Accessor for the prefix type that will be prepended to every primary key column name.
      # The options are :table_name and :table_name_with_underscore. If the first is specified,
      # the Product class will look for "productid" instead of "id" as the primary column. If the
      # latter is specified, the Product class will look for "product_id" instead of "id". Remember
      # that this is a global setting for all Active Records.
J
Jon Leighton 已提交
13
      mattr_accessor :primary_key_prefix_type, instance_writer: false
14

15 16 17 18 19 20 21 22 23 24
      ##
      # :singleton-method:
      # Accessor for the name of the prefix string to prepend to every table name. So if set
      # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
      # etc. This is a convenient way of creating a namespace for tables in a shared database.
      # By default, the prefix is the empty string.
      #
      # If you are organising your models within modules you can add a prefix to the models within
      # a namespace by defining a singleton method in the parent module called table_name_prefix which
      # returns your chosen prefix.
J
Jon Leighton 已提交
25 26
      class_attribute :table_name_prefix, instance_writer: false
      self.table_name_prefix = ""
27 28 29 30 31

      ##
      # :singleton-method:
      # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
      # "people_basecamp"). By default, the suffix is the empty string.
J
Jon Leighton 已提交
32 33
      class_attribute :table_name_suffix, instance_writer: false
      self.table_name_suffix = ""
34 35 36 37 38 39

      ##
      # :singleton-method:
      # Indicates whether table names should be the pluralized versions of the corresponding class names.
      # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
      # See table_name for the full rules on table/class naming. This is true, by default.
J
Jon Leighton 已提交
40 41 42 43
      class_attribute :pluralize_table_names, instance_writer: false
      self.pluralize_table_names = true

      self.inheritance_column = 'type'
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    end

    module ClassMethods
      # Guesses the table name (in forced lower-case) based on the name of the class in the
      # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
      # looks like: Reply < Message < ActiveRecord::Base, then Message is used
      # to guess the table name even when called on Reply. The rules used to do the guess
      # are handled by the Inflector class in Active Support, which knows almost all common
      # English inflections. You can add new inflections in config/initializers/inflections.rb.
      #
      # Nested classes are given table names prefixed by the singular form of
      # the parent's table name. Enclosing modules are not considered.
      #
      # ==== Examples
      #
      #   class Invoice < ActiveRecord::Base
      #   end
      #
      #   file                  class               table_name
      #   invoice.rb            Invoice             invoices
      #
      #   class Invoice < ActiveRecord::Base
      #     class Lineitem < ActiveRecord::Base
      #     end
      #   end
      #
      #   file                  class               table_name
      #   invoice.rb            Invoice::Lineitem   invoice_lineitems
      #
      #   module Invoice
      #     class Lineitem < ActiveRecord::Base
      #     end
      #   end
      #
      #   file                  class               table_name
      #   invoice/lineitem.rb   Invoice::Lineitem   lineitems
      #
      # Additionally, the class-level +table_name_prefix+ is prepended and the
      # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
      # the table name guess for an Invoice class becomes "myapp_invoices".
      # Invoice::Lineitem becomes "myapp_invoice_lineitems".
      #
      # You can also set your own table name explicitly:
      #
      #   class Mouse < ActiveRecord::Base
      #     self.table_name = "mice"
      #   end
      #
      # Alternatively, you can override the table_name method to define your
      # own computation. (Possibly using <tt>super</tt> to manipulate the default
      # table name.) Example:
      #
      #   class Post < ActiveRecord::Base
      #     def self.table_name
      #       "special_" + super
      #     end
      #   end
      #   Post.table_name # => "special_posts"
      def table_name
        reset_table_name unless defined?(@table_name)
        @table_name
      end

      # Sets the table name explicitly. Example:
      #
      #   class Project < ActiveRecord::Base
      #     self.table_name = "project"
      #   end
      #
      # You can also just define your own <tt>self.table_name</tt> method; see
      # the documentation for ActiveRecord::Base#table_name.
      def table_name=(value)
116
        value = value && value.to_s
J
Jon Leighton 已提交
117

118 119
        if defined?(@table_name)
          return if value == @table_name
120
          reset_column_information if connected?
121
        end
J
Jon Leighton 已提交
122 123 124 125 126 127

        @table_name        = value
        @quoted_table_name = nil
        @arel_table        = nil
        @sequence_name     = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
        @relation          = Relation.new(self, arel_table)
128 129 130 131 132 133 134 135 136
      end

      # Returns a quoted version of the table name, used to construct SQL statements.
      def quoted_table_name
        @quoted_table_name ||= connection.quote_table_name(table_name)
      end

      # Computes the table name, (re)sets it internally, and returns it.
      def reset_table_name #:nodoc:
D
Dmitry Vorotilin 已提交
137
        self.table_name = if abstract_class?
J
Jon Leighton 已提交
138 139 140
          superclass == Base ? nil : superclass.table_name
        elsif superclass.abstract_class?
          superclass.table_name || compute_table_name
141
        else
D
Dmitry Vorotilin 已提交
142
          compute_table_name
143 144 145 146 147 148 149
        end
      end

      def full_table_name_prefix #:nodoc:
        (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
      end

J
Jon Leighton 已提交
150 151 152 153 154 155 156 157 158
      # Defines the name of the table column which will store the class name on single-table
      # inheritance situations.
      #
      # The default inheritance column name is +type+, which means it's a
      # reserved word inside Active Record. To be able to use single-table
      # inheritance with another column name, or to use the column +type+ in
      # your own model for something else, you can set +inheritance_column+:
      #
      #     self.inheritance_column = 'zoink'
159
      def inheritance_column
J
Jon Leighton 已提交
160
        (@inheritance_column ||= nil) || superclass.inheritance_column
161 162 163 164
      end

      # Sets the value of inheritance_column
      def inheritance_column=(value)
J
Jon Leighton 已提交
165
        @inheritance_column = value.to_s
166
        @explicit_inheritance_column = true
167 168 169 170 171 172 173 174 175 176 177
      end

      def sequence_name
        if base_class == self
          @sequence_name ||= reset_sequence_name
        else
          (@sequence_name ||= nil) || base_class.sequence_name
        end
      end

      def reset_sequence_name #:nodoc:
J
Jon Leighton 已提交
178
        @explicit_sequence_name = false
179
        @sequence_name          = connection.default_sequence_name(table_name, primary_key)
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
      end

      # Sets the name of the sequence to use when generating ids to the given
      # value, or (if the value is nil or false) to the value returned by the
      # given block. This is required for Oracle and is useful for any
      # database which relies on sequences for primary key generation.
      #
      # If a sequence name is not explicitly set when using Oracle or Firebird,
      # it will default to the commonly used pattern of: #{table_name}_seq
      #
      # If a sequence name is not explicitly set when using PostgreSQL, it
      # will discover the sequence corresponding to your primary key for you.
      #
      #   class Project < ActiveRecord::Base
      #     self.sequence_name = "projectseq"   # default would have been "project_seq"
      #   end
      def sequence_name=(value)
J
Jon Leighton 已提交
197 198
        @sequence_name          = value.to_s
        @explicit_sequence_name = true
199 200 201 202 203 204 205 206 207
      end

      # Indicates whether the table associated with this class exists
      def table_exists?
        connection.schema_cache.table_exists?(table_name)
      end

      # Returns an array of column objects for the table associated with this class.
      def columns
J
Jon Leighton 已提交
208 209 210 211
        @columns ||= connection.schema_cache.columns[table_name].map do |col|
          col = col.dup
          col.primary = (col.name == primary_key)
          col
212 213 214 215 216
        end
      end

      # Returns a hash of column objects for the table associated with this class.
      def columns_hash
J
Jon Leighton 已提交
217
        @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
218 219
      end

220 221 222 223 224 225 226 227
      def column_types # :nodoc:
        @column_types ||= decorate_columns(columns_hash.dup)
      end

      def decorate_columns(columns_hash) # :nodoc:
        return if columns_hash.empty?

        columns_hash.each do |name, col|
228 229 230
          if serialized_attributes.key?(name)
            columns_hash[name] = AttributeMethods::Serialization::Type.new(col)
          end
231 232
          if create_time_zone_conversion_attribute?(name, col)
            columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
233
          end
234 235 236
        end

        columns_hash
237 238
      end

239 240 241
      # Returns a hash where the keys are column names and the values are
      # default values when instantiating the AR object for this table.
      def column_defaults
J
Jon Leighton 已提交
242
        @column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }]
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
      end

      # Returns an array of column names as strings.
      def column_names
        @column_names ||= columns.map { |column| column.name }
      end

      # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
      # and columns used for single table inheritance have been removed.
      def content_columns
        @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
      end

      # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
      # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
      # is available.
      def column_methods_hash #:nodoc:
260
        @dynamic_methods_hash ||= column_names.each_with_object(Hash.new(false)) do |attr, methods|
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
          attr_name = attr.to_s
          methods[attr.to_sym]       = attr_name
          methods["#{attr}=".to_sym] = attr_name
          methods["#{attr}?".to_sym] = attr_name
          methods["#{attr}_before_type_cast".to_sym] = attr_name
        end
      end

      # Resets all the cached information about columns, which will cause them
      # to be reloaded on the next request.
      #
      # The most common usage pattern for this method is probably in a migration,
      # when just after creating a table you want to populate it with some default
      # values, eg:
      #
      #  class CreateJobLevels < ActiveRecord::Migration
      #    def up
      #      create_table :job_levels do |t|
      #        t.integer :id
      #        t.string :name
      #
      #        t.timestamps
      #      end
      #
      #      JobLevel.reset_column_information
      #      %w{assistant executive manager director}.each do |type|
A
AvnerCohen 已提交
287
      #        JobLevel.create(name: type)
288 289 290 291 292 293 294 295 296 297 298 299
      #      end
      #    end
      #
      #    def down
      #      drop_table :job_levels
      #    end
      #  end
      def reset_column_information
        connection.clear_cache!
        undefine_attribute_methods
        connection.schema_cache.clear_table_cache!(table_name) if table_exists?

300 301 302 303 304 305 306 307
        @arel_engine          = nil
        @column_defaults      = nil
        @column_names         = nil
        @columns              = nil
        @columns_hash         = nil
        @column_types         = nil
        @content_columns      = nil
        @dynamic_methods_hash = nil
308
        @inheritance_column   = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
309
        @relation             = nil
310 311
      end

312 313 314 315 316 317 318
      # This is a hook for use by modules that need to do extra stuff to
      # attributes when they are initialized. (e.g. attribute
      # serialization)
      def initialize_attributes(attributes, options = {}) #:nodoc:
        attributes
      end

319 320 321 322 323
      private

      # Guesses the table name, but does not decorate it with prefix and suffix information.
      def undecorated_table_name(class_name = base_class.name)
        table_name = class_name.to_s.demodulize.underscore
324
        pluralize_table_names ? table_name.pluralize : table_name
325 326 327 328 329 330 331
      end

      # Computes and returns a table name according to default conventions.
      def compute_table_name
        base = base_class
        if self == base
          # Nested classes are prefixed with singular parent table name.
J
Jon Leighton 已提交
332
          if parent < Base && !parent.abstract_class?
333 334 335 336 337 338 339 340 341 342 343 344 345
            contained = parent.table_name
            contained = contained.singularize if parent.pluralize_table_names
            contained += '_'
          end
          "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
        else
          # STI subclasses always use their superclass' table.
          base.table_name
        end
      end
    end
  end
end