aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb20
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb7
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb160
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb2
10 files changed, 213 insertions, 128 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 499b00a815..0b33ee881b 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1520,10 +1520,16 @@ module ActiveRecord
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
# combination with the <tt>:polymorphic</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
+ # [:optional]
+ # When set to +true+, the association will not have its presence validated.
# [:required]
# When set to +true+, the association will also have its presence validated.
# This will validate the association itself, not the id. You can use
# +:inverse_of+ to avoid an extra query during validation.
+ # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If
+ # you don't want to have association presence validated, use <tt>optional: true</tt>.
+ #
+ #
#
# Option examples:
# belongs_to :firm, foreign_key: "client_of"
@@ -1536,7 +1542,7 @@ module ActiveRecord
# belongs_to :post, counter_cache: true
# belongs_to :comment, touch: true
# belongs_to :company, touch: :employees_last_updated_at
- # belongs_to :user, required: true
+ # belongs_to :user, optional: true
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index d0ad57f9c6..ec135d49b7 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
end
def self.valid_options(options)
- super + [:foreign_type, :polymorphic, :touch, :counter_cache]
+ super + [:foreign_type, :polymorphic, :touch, :counter_cache, :optional]
end
def self.valid_dependent_options
@@ -110,5 +110,23 @@ module ActiveRecord::Associations::Builder
name = reflection.name
model.after_destroy lambda { |o| o.association(name).handle_dependency }
end
+
+ def self.define_validations(model, reflection)
+ if reflection.options.key?(:required)
+ reflection.options[:optional] = !reflection.options.delete(:required)
+ end
+
+ if reflection.options[:optional].nil?
+ required = model.belongs_to_required_by_default
+ else
+ required = !reflection.options[:optional]
+ end
+
+ super
+
+ if required
+ model.validates_presence_of reflection.name, message: :required
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 64e9e6b334..a272d3c781 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -17,5 +17,12 @@ module ActiveRecord::Associations::Builder
def self.add_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
+
+ def self.define_validations(model, reflection)
+ super
+ if reflection.options[:required]
+ model.validates_presence_of reflection.name, message: :required
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index f6274c027e..42542f188e 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -27,12 +27,5 @@ module ActiveRecord::Associations::Builder
end
CODE
end
-
- def self.define_validations(model, reflection)
- super
- if reflection.options[:required]
- model.validates_presence_of reflection.name, message: :required
- end
- end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index a2777fcd0a..0acc815d51 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -142,6 +142,41 @@ module ActiveRecord
end
end
+ module ColumnMethods
+ # Appends a primary key definition to the table definition.
+ # Can be called multiple times, but this is probably not a good idea.
+ def primary_key(name, type = :primary_key, **options)
+ column(name, type, options.merge(primary_key: true))
+ end
+
+ # Appends a column or columns of a specified type.
+ #
+ # t.string(:goat)
+ # t.string(:goat, :sheep)
+ #
+ # See TableDefinition#column
+ [
+ :bigint,
+ :binary,
+ :boolean,
+ :date,
+ :datetime,
+ :decimal,
+ :float,
+ :integer,
+ :string,
+ :text,
+ :time,
+ :timestamp,
+ ].each do |column_type|
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{column_type}(*args, **options)
+ args.each { |name| column(name, :#{column_type}, options) }
+ end
+ CODE
+ end
+ end
+
# Represents the schema of an SQL table in an abstract way. This class
# provides methods for manipulating the schema representation.
#
@@ -163,6 +198,8 @@ module ActiveRecord
# The table definitions
# The Columns are stored as a ColumnDefinition in the +columns+ attribute.
class TableDefinition
+ include ColumnMethods
+
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
@@ -181,12 +218,6 @@ module ActiveRecord
def columns; @columns_hash.values; end
- # Appends a primary key definition to the table definition.
- # Can be called multiple times, but this is probably not a good idea.
- def primary_key(name, type = :primary_key, options = {})
- column(name, type, options.merge(:primary_key => true))
- end
-
# Returns a ColumnDefinition for the column with name +name+.
def [](name)
@columns_hash[name.to_s]
@@ -343,14 +374,6 @@ module ActiveRecord
@columns_hash.delete name.to_s
end
- [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
- define_method column_type do |*args|
- options = args.extract_options!
- column_names = args
- column_names.each { |name| column(name, column_type, options) }
- end
- end
-
# Adds index options to the indexes hash, keyed by column name
# This is primarily used to track indexes that need to be created after the table
#
@@ -462,6 +485,7 @@ module ActiveRecord
# Available transformations are:
#
# change_table :table do |t|
+ # t.primary_key
# t.column
# t.index
# t.rename_index
@@ -474,6 +498,7 @@ module ActiveRecord
# t.string
# t.text
# t.integer
+ # t.bigint
# t.float
# t.decimal
# t.datetime
@@ -490,6 +515,8 @@ module ActiveRecord
# end
#
class Table
+ include ColumnMethods
+
attr_reader :name
def initialize(table_name, base)
@@ -640,21 +667,6 @@ module ActiveRecord
end
alias :remove_belongs_to :remove_references
- # Adds a column or columns of a specified type.
- #
- # t.string(:goat)
- # t.string(:goat, :sheep)
- #
- # See SchemaStatements#add_column
- [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
- define_method column_type do |*args|
- options = args.extract_options!
- args.each do |column_name|
- @base.add_column(name, column_name, column_type, options)
- end
- end
- end
-
def foreign_key(*args) # :nodoc:
@base.add_foreign_key(name, *args)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 8db4bcd7e3..8ec26bc109 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,13 +6,31 @@ module ActiveRecord
class AbstractMysqlAdapter < AbstractAdapter
include Savepoints
- class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
- def primary_key(name, type = :primary_key, options = {})
- options[:auto_increment] ||= type == :bigint
+ module ColumnMethods
+ def primary_key(name, type = :primary_key, **options)
+ options[:auto_increment] = true if type == :bigint
super
end
end
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
+ include ColumnMethods
+
+ def new_column_definition(name, type, options) # :nodoc:
+ column = super
+ case column.type
+ when :primary_key
+ column.type = :integer
+ column.auto_increment = true
+ end
+ column
+ end
+ end
+
+ class Table < ActiveRecord::ConnectionAdapters::Table
+ include ColumnMethods
+ end
+
class SchemaCreation < AbstractAdapter::SchemaCreation
def visit_AddColumn(o)
add_column_position!(super, column_options(o))
@@ -57,6 +75,10 @@ module ActiveRecord
end
end
+ def update_table_definition(table_name, base) # :nodoc:
+ Table.new(table_name, base)
+ end
+
def schema_creation
SchemaCreation.new self
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 52b307c432..c1835380f9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -8,20 +8,39 @@ module ActiveRecord
def disable_referential_integrity # :nodoc:
if supports_disable_referential_integrity?
+ original_exception = nil
+
begin
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
- rescue
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";"))
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
+ end
+ rescue => e
+ original_exception = e
end
- end
- yield
- ensure
- if supports_disable_referential_integrity?
+
+ begin
+ yield
+ rescue ActiveRecord::InvalidForeignKey => e
+ warn <<-WARNING
+WARNING: Rails was not able to disable referential integrity.
+
+This is most likely caused due to missing permissions.
+Rails needs superuser privileges to disable referential integrity.
+
+ cause: #{original_exception.try(:message)}
+
+ WARNING
+ raise e
+ end
+
begin
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
+ transaction(require_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
+ end
rescue
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";"))
end
+ else
+ yield
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index b9078d4c86..022dbdfa27 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -2,90 +2,129 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ColumnMethods
- def xml(*args)
- options = args.extract_options!
- column(args[0], :xml, options)
+ # Defines the primary key field.
+ # Use of the native PostgreSQL UUID type is supported, and can be used
+ # by defining your tables as such:
+ #
+ # create_table :stuffs, id: :uuid do |t|
+ # t.string :content
+ # t.timestamps
+ # end
+ #
+ # By default, this will use the +uuid_generate_v4()+ function from the
+ # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
+ # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
+ # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
+ # set the +:default+ option to +nil+:
+ #
+ # create_table :stuffs, id: false do |t|
+ # t.primary_key :id, :uuid, default: nil
+ # t.uuid :foo_id
+ # t.timestamps
+ # end
+ #
+ # You may also pass a different UUID generation function from +uuid-ossp+
+ # or another library.
+ #
+ # Note that setting the UUID primary key default value to +nil+ will
+ # require you to assure that you always provide a UUID value before saving
+ # a record (as primary keys cannot be +nil+). This might be done via the
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
+ def primary_key(name, type = :primary_key, **options)
+ options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid
+ super
+ end
+
+ def bigserial(*args, **options)
+ args.each { |name| column(name, :bigserial, options) }
+ end
+
+ def bit(*args, **options)
+ args.each { |name| column(name, :bit, options) }
end
- def tsvector(*args)
- options = args.extract_options!
- column(args[0], :tsvector, options)
+ def bit_varying(*args, **options)
+ args.each { |name| column(name, :bit_varying, options) }
end
- def int4range(name, options = {})
- column(name, :int4range, options)
+ def cidr(*args, **options)
+ args.each { |name| column(name, :cidr, options) }
end
- def int8range(name, options = {})
- column(name, :int8range, options)
+ def citext(*args, **options)
+ args.each { |name| column(name, :citext, options) }
end
- def tsrange(name, options = {})
- column(name, :tsrange, options)
+ def daterange(*args, **options)
+ args.each { |name| column(name, :daterange, options) }
end
- def tstzrange(name, options = {})
- column(name, :tstzrange, options)
+ def hstore(*args, **options)
+ args.each { |name| column(name, :hstore, options) }
end
- def numrange(name, options = {})
- column(name, :numrange, options)
+ def inet(*args, **options)
+ args.each { |name| column(name, :inet, options) }
end
- def daterange(name, options = {})
- column(name, :daterange, options)
+ def int4range(*args, **options)
+ args.each { |name| column(name, :int4range, options) }
end
- def hstore(name, options = {})
- column(name, :hstore, options)
+ def int8range(*args, **options)
+ args.each { |name| column(name, :int8range, options) }
end
- def ltree(name, options = {})
- column(name, :ltree, options)
+ def json(*args, **options)
+ args.each { |name| column(name, :json, options) }
end
- def inet(name, options = {})
- column(name, :inet, options)
+ def jsonb(*args, **options)
+ args.each { |name| column(name, :jsonb, options) }
end
- def cidr(name, options = {})
- column(name, :cidr, options)
+ def ltree(*args, **options)
+ args.each { |name| column(name, :ltree, options) }
end
- def macaddr(name, options = {})
- column(name, :macaddr, options)
+ def macaddr(*args, **options)
+ args.each { |name| column(name, :macaddr, options) }
end
- def uuid(name, options = {})
- column(name, :uuid, options)
+ def money(*args, **options)
+ args.each { |name| column(name, :money, options) }
end
- def json(name, options = {})
- column(name, :json, options)
+ def numrange(*args, **options)
+ args.each { |name| column(name, :numrange, options) }
end
- def jsonb(name, options = {})
- column(name, :jsonb, options)
+ def point(*args, **options)
+ args.each { |name| column(name, :point, options) }
end
- def citext(name, options = {})
- column(name, :citext, options)
+ def serial(*args, **options)
+ args.each { |name| column(name, :serial, options) }
end
- def point(name, options = {})
- column(name, :point, options)
+ def tsrange(*args, **options)
+ args.each { |name| column(name, :tsrange, options) }
end
- def bit(name, options = {})
- column(name, :bit, options)
+ def tstzrange(*args, **options)
+ args.each { |name| column(name, :tstzrange, options) }
end
- def bit_varying(name, options = {})
- column(name, :bit_varying, options)
+ def tsvector(*args, **options)
+ args.each { |name| column(name, :tsvector, options) }
end
- def money(name, options = {})
- column(name, :money, options)
+ def uuid(*args, **options)
+ args.each { |name| column(name, :uuid, options) }
+ end
+
+ def xml(*args, **options)
+ args.each { |name| column(name, :xml, options) }
end
end
@@ -96,39 +135,6 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
- # Defines the primary key field.
- # Use of the native PostgreSQL UUID type is supported, and can be used
- # by defining your tables as such:
- #
- # create_table :stuffs, id: :uuid do |t|
- # t.string :content
- # t.timestamps
- # end
- #
- # By default, this will use the +uuid_generate_v4()+ function from the
- # +uuid-ossp+ extension, which MUST be enabled on your database. To enable
- # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your
- # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can
- # set the +:default+ option to +nil+:
- #
- # create_table :stuffs, id: false do |t|
- # t.primary_key :id, :uuid, default: nil
- # t.uuid :foo_id
- # t.timestamps
- # end
- #
- # You may also pass a different UUID generation function from +uuid-ossp+
- # or another library.
- #
- # Note that setting the UUID primary key default value to +nil+ will
- # require you to assure that you always provide a UUID value before saving
- # a record (as primary keys cannot be +nil+). This might be done via the
- # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
- def primary_key(name, type = :primary_key, options = {})
- options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid
- super
- end
-
def new_column_definition(name, type, options) # :nodoc:
column = super
column.array = options[:array]
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1244bd6195..de48681746 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -87,6 +87,8 @@ module ActiveRecord
mattr_accessor :maintain_test_schema, instance_accessor: false
+ mattr_accessor :belongs_to_required_by_default, instance_accessor: false
+
class_attribute :default_connection_handler, instance_writer: false
def self.connection_handler
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4a8e6bdd23..a83b90a95f 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -395,7 +395,7 @@ module ActiveRecord
def load_schema_if_pending!
if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
- # Roundrip to Rake to allow plugins to hook into database initialization.
+ # Roundtrip to Rake to allow plugins to hook into database initialization.
FileUtils.cd Rails.root do
current_config = Base.connection_config
Base.clear_all_connections!