PageRenderTime 25ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/activerecord/lib/active_record/model_schema.rb

https://gitlab.com/franzejr/rails
Ruby | 394 lines | 195 code | 44 blank | 155 comment | 30 complexity | 97d90367d5f13bfff2ca8a1c4dc21a7d MD5 | raw file
  1. module ActiveRecord
  2. module ModelSchema
  3. extend ActiveSupport::Concern
  4. included do
  5. ##
  6. # :singleton-method:
  7. # Accessor for the prefix type that will be prepended to every primary key column name.
  8. # The options are :table_name and :table_name_with_underscore. If the first is specified,
  9. # the Product class will look for "productid" instead of "id" as the primary column. If the
  10. # latter is specified, the Product class will look for "product_id" instead of "id". Remember
  11. # that this is a global setting for all Active Records.
  12. mattr_accessor :primary_key_prefix_type, instance_writer: false
  13. ##
  14. # :singleton-method:
  15. # Accessor for the name of the prefix string to prepend to every table name. So if set
  16. # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
  17. # etc. This is a convenient way of creating a namespace for tables in a shared database.
  18. # By default, the prefix is the empty string.
  19. #
  20. # If you are organising your models within modules you can add a prefix to the models within
  21. # a namespace by defining a singleton method in the parent module called table_name_prefix which
  22. # returns your chosen prefix.
  23. class_attribute :table_name_prefix, instance_writer: false
  24. self.table_name_prefix = ""
  25. ##
  26. # :singleton-method:
  27. # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
  28. # "people_basecamp"). By default, the suffix is the empty string.
  29. #
  30. # If you are organising your models within modules, you can add a suffix to the models within
  31. # a namespace by defining a singleton method in the parent module called table_name_suffix which
  32. # returns your chosen suffix.
  33. class_attribute :table_name_suffix, instance_writer: false
  34. self.table_name_suffix = ""
  35. ##
  36. # :singleton-method:
  37. # Accessor for the name of the schema migrations table. By default, the value is "schema_migrations"
  38. class_attribute :schema_migrations_table_name, instance_accessor: false
  39. self.schema_migrations_table_name = "schema_migrations"
  40. ##
  41. # :singleton-method:
  42. # Indicates whether table names should be the pluralized versions of the corresponding class names.
  43. # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
  44. # See table_name for the full rules on table/class naming. This is true, by default.
  45. class_attribute :pluralize_table_names, instance_writer: false
  46. self.pluralize_table_names = true
  47. ##
  48. # :singleton-method:
  49. # Accessor for the list of columns names the model should ignore. Ignored columns won't have attribute
  50. # accessors defined, and won't be referenced in SQL queries.
  51. class_attribute :ignored_columns, instance_accessor: false
  52. self.ignored_columns = [].freeze
  53. self.inheritance_column = 'type'
  54. delegate :type_for_attribute, to: :class
  55. end
  56. # Derives the join table name for +first_table+ and +second_table+. The
  57. # table names appear in alphabetical order. A common prefix is removed
  58. # (useful for namespaced models like Music::Artist and Music::Record):
  59. #
  60. # artists, records => artists_records
  61. # records, artists => artists_records
  62. # music_artists, music_records => music_artists_records
  63. def self.derive_join_table_name(first_table, second_table) # :nodoc:
  64. [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
  65. end
  66. module ClassMethods
  67. # Guesses the table name (in forced lower-case) based on the name of the class in the
  68. # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
  69. # looks like: Reply < Message < ActiveRecord::Base, then Message is used
  70. # to guess the table name even when called on Reply. The rules used to do the guess
  71. # are handled by the Inflector class in Active Support, which knows almost all common
  72. # English inflections. You can add new inflections in config/initializers/inflections.rb.
  73. #
  74. # Nested classes are given table names prefixed by the singular form of
  75. # the parent's table name. Enclosing modules are not considered.
  76. #
  77. # ==== Examples
  78. #
  79. # class Invoice < ActiveRecord::Base
  80. # end
  81. #
  82. # file class table_name
  83. # invoice.rb Invoice invoices
  84. #
  85. # class Invoice < ActiveRecord::Base
  86. # class Lineitem < ActiveRecord::Base
  87. # end
  88. # end
  89. #
  90. # file class table_name
  91. # invoice.rb Invoice::Lineitem invoice_lineitems
  92. #
  93. # module Invoice
  94. # class Lineitem < ActiveRecord::Base
  95. # end
  96. # end
  97. #
  98. # file class table_name
  99. # invoice/lineitem.rb Invoice::Lineitem lineitems
  100. #
  101. # Additionally, the class-level +table_name_prefix+ is prepended and the
  102. # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
  103. # the table name guess for an Invoice class becomes "myapp_invoices".
  104. # Invoice::Lineitem becomes "myapp_invoice_lineitems".
  105. #
  106. # You can also set your own table name explicitly:
  107. #
  108. # class Mouse < ActiveRecord::Base
  109. # self.table_name = "mice"
  110. # end
  111. def table_name
  112. reset_table_name unless defined?(@table_name)
  113. @table_name
  114. end
  115. # Sets the table name explicitly. Example:
  116. #
  117. # class Project < ActiveRecord::Base
  118. # self.table_name = "project"
  119. # end
  120. def table_name=(value)
  121. value = value && value.to_s
  122. if defined?(@table_name)
  123. return if value == @table_name
  124. reset_column_information if connected?
  125. end
  126. @table_name = value
  127. @quoted_table_name = nil
  128. @arel_table = nil
  129. @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
  130. @predicate_builder = nil
  131. end
  132. # Returns a quoted version of the table name, used to construct SQL statements.
  133. def quoted_table_name
  134. @quoted_table_name ||= connection.quote_table_name(table_name)
  135. end
  136. # Computes the table name, (re)sets it internally, and returns it.
  137. def reset_table_name #:nodoc:
  138. self.table_name = if abstract_class?
  139. superclass == Base ? nil : superclass.table_name
  140. elsif superclass.abstract_class?
  141. superclass.table_name || compute_table_name
  142. else
  143. compute_table_name
  144. end
  145. end
  146. def full_table_name_prefix #:nodoc:
  147. (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
  148. end
  149. def full_table_name_suffix #:nodoc:
  150. (parents.detect {|p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
  151. end
  152. # Defines the name of the table column which will store the class name on single-table
  153. # inheritance situations.
  154. #
  155. # The default inheritance column name is +type+, which means it's a
  156. # reserved word inside Active Record. To be able to use single-table
  157. # inheritance with another column name, or to use the column +type+ in
  158. # your own model for something else, you can set +inheritance_column+:
  159. #
  160. # self.inheritance_column = 'zoink'
  161. def inheritance_column
  162. (@inheritance_column ||= nil) || superclass.inheritance_column
  163. end
  164. # Sets the value of inheritance_column
  165. def inheritance_column=(value)
  166. @inheritance_column = value.to_s
  167. @explicit_inheritance_column = true
  168. end
  169. def sequence_name
  170. if base_class == self
  171. @sequence_name ||= reset_sequence_name
  172. else
  173. (@sequence_name ||= nil) || base_class.sequence_name
  174. end
  175. end
  176. def reset_sequence_name #:nodoc:
  177. @explicit_sequence_name = false
  178. @sequence_name = connection.default_sequence_name(table_name, primary_key)
  179. end
  180. # Sets the name of the sequence to use when generating ids to the given
  181. # value, or (if the value is nil or false) to the value returned by the
  182. # given block. This is required for Oracle and is useful for any
  183. # database which relies on sequences for primary key generation.
  184. #
  185. # If a sequence name is not explicitly set when using Oracle,
  186. # it will default to the commonly used pattern of: #{table_name}_seq
  187. #
  188. # If a sequence name is not explicitly set when using PostgreSQL, it
  189. # will discover the sequence corresponding to your primary key for you.
  190. #
  191. # class Project < ActiveRecord::Base
  192. # self.sequence_name = "projectseq" # default would have been "project_seq"
  193. # end
  194. def sequence_name=(value)
  195. @sequence_name = value.to_s
  196. @explicit_sequence_name = true
  197. end
  198. # Indicates whether the table associated with this class exists
  199. def table_exists?
  200. connection.schema_cache.data_source_exists?(table_name)
  201. end
  202. def attributes_builder # :nodoc:
  203. @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key)
  204. end
  205. def columns_hash # :nodoc:
  206. load_schema
  207. @columns_hash
  208. end
  209. def columns
  210. load_schema
  211. @columns ||= columns_hash.values
  212. end
  213. def attribute_types # :nodoc:
  214. load_schema
  215. @attribute_types ||= Hash.new(Type::Value.new)
  216. end
  217. def type_for_attribute(attr_name) # :nodoc:
  218. attribute_types[attr_name]
  219. end
  220. # Returns a hash where the keys are column names and the values are
  221. # default values when instantiating the Active Record object for this table.
  222. def column_defaults
  223. load_schema
  224. _default_attributes.to_hash
  225. end
  226. def _default_attributes # :nodoc:
  227. @default_attributes ||= AttributeSet.new({})
  228. end
  229. # Returns an array of column names as strings.
  230. def column_names
  231. @column_names ||= columns.map(&:name)
  232. end
  233. # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
  234. # and columns used for single table inheritance have been removed.
  235. def content_columns
  236. @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
  237. end
  238. # Resets all the cached information about columns, which will cause them
  239. # to be reloaded on the next request.
  240. #
  241. # The most common usage pattern for this method is probably in a migration,
  242. # when just after creating a table you want to populate it with some default
  243. # values, eg:
  244. #
  245. # class CreateJobLevels < ActiveRecord::Migration
  246. # def up
  247. # create_table :job_levels do |t|
  248. # t.integer :id
  249. # t.string :name
  250. #
  251. # t.timestamps
  252. # end
  253. #
  254. # JobLevel.reset_column_information
  255. # %w{assistant executive manager director}.each do |type|
  256. # JobLevel.create(name: type)
  257. # end
  258. # end
  259. #
  260. # def down
  261. # drop_table :job_levels
  262. # end
  263. # end
  264. def reset_column_information
  265. connection.clear_cache!
  266. undefine_attribute_methods
  267. connection.schema_cache.clear_data_source_cache!(table_name)
  268. reload_schema_from_cache
  269. end
  270. private
  271. def schema_loaded?
  272. defined?(@columns_hash) && @columns_hash
  273. end
  274. def load_schema
  275. unless schema_loaded?
  276. load_schema!
  277. end
  278. end
  279. def load_schema!
  280. @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns)
  281. @columns_hash.each do |name, column|
  282. warn_if_deprecated_type(column)
  283. define_attribute(
  284. name,
  285. connection.lookup_cast_type_from_column(column),
  286. default: column.default,
  287. user_provided_default: false
  288. )
  289. end
  290. end
  291. def reload_schema_from_cache
  292. @arel_engine = nil
  293. @arel_table = nil
  294. @column_names = nil
  295. @attribute_types = nil
  296. @content_columns = nil
  297. @default_attributes = nil
  298. @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
  299. @attributes_builder = nil
  300. @columns = nil
  301. @columns_hash = nil
  302. @attribute_names = nil
  303. direct_descendants.each do |descendant|
  304. descendant.send(:reload_schema_from_cache)
  305. end
  306. end
  307. # Guesses the table name, but does not decorate it with prefix and suffix information.
  308. def undecorated_table_name(class_name = base_class.name)
  309. table_name = class_name.to_s.demodulize.underscore
  310. pluralize_table_names ? table_name.pluralize : table_name
  311. end
  312. # Computes and returns a table name according to default conventions.
  313. def compute_table_name
  314. base = base_class
  315. if self == base
  316. # Nested classes are prefixed with singular parent table name.
  317. if parent < Base && !parent.abstract_class?
  318. contained = parent.table_name
  319. contained = contained.singularize if parent.pluralize_table_names
  320. contained += '_'
  321. end
  322. "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
  323. else
  324. # STI subclasses always use their superclass' table.
  325. base.table_name
  326. end
  327. end
  328. def warn_if_deprecated_type(column)
  329. return if attributes_to_define_after_schema_loads.key?(column.name)
  330. if column.respond_to?(:oid) && column.sql_type.start_with?("point")
  331. if column.array?
  332. array_arguments = ", array: true"
  333. else
  334. array_arguments = ""
  335. end
  336. ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc)
  337. The behavior of the `:point` type will be changing in Rails 5.1 to
  338. return a `Point` object, rather than an `Array`. If you'd like to
  339. keep the old behavior, you can add this line to #{self.name}:
  340. attribute :#{column.name}, :legacy_point#{array_arguments}
  341. If you'd like the new behavior today, you can add this line:
  342. attribute :#{column.name}, :rails_5_1_point#{array_arguments}
  343. WARNING
  344. end
  345. end
  346. end
  347. end
  348. end