aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md22
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb13
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb26
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb4
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb30
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb4
-rw-r--r--activerecord/lib/active_record/attribute_set.rb53
-rw-r--r--activerecord/lib/active_record/attributes.rb11
-rw-r--r--activerecord/lib/active_record/autosave_association.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/core.rb14
-rw-r--r--activerecord/lib/active_record/model_schema.rb14
-rw-r--r--activerecord/lib/active_record/persistence.rb10
-rw-r--r--activerecord/lib/active_record/querying.rb9
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb20
-rw-r--r--activerecord/lib/active_record/store.rb2
-rw-r--r--activerecord/lib/active_record/type/numeric.rb18
-rw-r--r--activerecord/lib/active_record/type/value.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb11
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb30
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb27
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb31
-rw-r--r--activerecord/test/cases/attribute_set_test.rb65
-rw-r--r--activerecord/test/cases/calculations_test.rb5
-rw-r--r--activerecord/test/cases/persistence_test.rb7
-rw-r--r--activerecord/test/cases/quoting_test.rb25
-rw-r--r--activerecord/test/cases/reflection_test.rb22
-rw-r--r--activerecord/test/models/club.rb7
-rw-r--r--activerecord/test/models/publisher/article.rb1
-rw-r--r--activerecord/test/schema/schema.rb5
42 files changed, 374 insertions, 177 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 823597fc92..33dec99ec7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,22 @@
+* `reload` no longer merges with the existing attributes.
+ The attribute hash is fully replaced. The record is put into the same state
+ as it would be with `Model.find(model.id)`.
+
+ *Sean Griffin*
+
+* The object returned from `select_all` must respond to `column_types`.
+ If this is not the case a `NoMethodError` is raised.
+
+ *Sean Griffin*
+
+* `has_many :through` associations will no longer save the through record
+ twice when added in an `after_create` callback defined before the
+ associations.
+
+ Fixes #3798.
+
+ *Sean Griffin*
+
* Detect in-place modifications of PG array types
*Sean Griffin*
@@ -98,7 +117,8 @@
*Lauro Caetano*, *Carlos Antonio da Silva*
-* Return a null column from `column_for_attribute` when no column exists.
+* Deprecate returning `nil` from `column_for_attribute` when no column exists.
+ It will return a null object in Rails 5.0
*Sean Griffin*
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 53fa132219..ab85414277 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -32,6 +32,7 @@ module ActiveRecord
extend ActiveSupport::Autoload
autoload :Attribute
+ autoload :AttributeSet
autoload :Base
autoload :Callbacks
autoload :Core
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 0ad5206980..34a555dfd4 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -15,7 +15,10 @@ module ActiveRecord::Associations::Builder
end
private
- def klass; @rhs_class_name.constantize; end
+
+ def klass
+ @lhs_class.send(:compute_type, @rhs_class_name)
+ end
end
def self.build(lhs_class, name, options)
@@ -23,13 +26,7 @@ module ActiveRecord::Associations::Builder
KnownTable.new options[:join_table].to_s
else
class_name = options.fetch(:class_name) {
- model_name = name.to_s.camelize.singularize
-
- if lhs_class.parent_name
- model_name.prepend("#{lhs_class.parent_name}::")
- end
-
- model_name
+ name.to_s.camelize.singularize
}
KnownClass.new lhs_class, class_name
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 306588ac66..065a2cff01 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -55,9 +55,9 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_column = reflection.primary_key_column
+ pk_type = reflection.primary_key_type
ids = Array(ids).reject { |id| id.blank? }
- ids.map! { |i| pk_column.type_cast_from_user(i) }
+ ids.map! { |i| pk_type.type_cast_from_user(i) }
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 2727e23870..477888228d 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -83,6 +83,13 @@ module ActiveRecord
if has_cached_counter?(reflection)
counter = cached_counter_attribute_name(reflection)
owner.class.update_counters(owner.id, counter => difference)
+ update_counter_in_memory(difference, reflection)
+ end
+ end
+
+ def update_counter_in_memory(difference, reflection = reflection())
+ if has_cached_counter?(reflection)
+ counter = cached_counter_attribute_name(reflection)
owner[counter] += difference
owner.changed_attributes.delete(counter) # eww
end
@@ -137,6 +144,25 @@ module ActiveRecord
false
end
end
+
+ def concat_records(records, *)
+ update_counter_if_success(super, records.length)
+ end
+
+ def _create_record(attributes, *)
+ if attributes.is_a?(Array)
+ super
+ else
+ update_counter_if_success(super, 1)
+ end
+ end
+
+ def update_counter_if_success(saved_successfully, difference)
+ if saved_successfully
+ update_counter_in_memory(difference)
+ end
+ saved_successfully
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 175019a72b..af38f2f6dd 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -63,7 +63,6 @@ module ActiveRecord
end
save_through_record(record)
- update_counter(1)
record
end
@@ -93,7 +92,9 @@ module ActiveRecord
end
def through_scope_attributes
- scope.where_values_hash(through_association.reflection.name.to_s).except!(through_association.reflection.foreign_key)
+ scope.where_values_hash(through_association.reflection.name.to_s).
+ except!(through_association.reflection.foreign_key,
+ through_association.reflection.klass.inheritance_column)
end
def save_through_record(record)
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 33c8619359..441768f302 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -104,11 +104,11 @@ module ActiveRecord
end
def association_key_type
- @klass.column_for_attribute(association_key_name).type
+ @klass.type_for_attribute(association_key_name.to_s).type
end
def owner_key_type
- @model.column_for_attribute(owner_key_name).type
+ @model.type_for_attribute(owner_key_name.to_s).type
end
def load_slices(slices)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 40e2918777..833ffa158b 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -134,7 +134,7 @@ module ActiveRecord
elsif klass == Date
read_date
else
- read_other(klass)
+ read_other
end
end
@@ -181,7 +181,7 @@ module ActiveRecord
end
end
- def read_other(klass)
+ def read_other
max_position = extract_max_param
positions = (1..max_position)
validate_required_parameters!(positions)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index b4d75d6556..51f6a009db 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -186,8 +186,7 @@ module ActiveRecord
end
# Returns the column object for the named attribute.
- # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the
- # named attribute does not exist.
+ # Returns nil if the named attribute does not exist.
#
# class Person < ActiveRecord::Base
# end
@@ -197,12 +196,17 @@ module ActiveRecord
# # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
- # # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
+ # # => nil
def column_for_attribute(name)
- name = name.to_s
- columns_hash.fetch(name) do
- ConnectionAdapters::NullColumn.new(name)
+ column = columns_hash[name.to_s]
+ if column.nil?
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `column_for_attribute` will return a null object for non-existent columns
+ in Rails 5.0. If you would like to continue to receive `nil`, you should
+ instead call `model.class.columns_hash[name]`
+ MESSAGE
end
+ column
end
end
@@ -230,7 +234,7 @@ module ActiveRecord
# For queries selecting a subset of columns, return false for unselected columns.
# We check defined?(@attributes) not to issue warnings if called on objects that
# have been allocated but not yet initialized.
- if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
+ if defined?(@attributes) && self.class.column_names.include?(name)
return has_attribute?(name)
end
@@ -247,7 +251,7 @@ module ActiveRecord
# person.has_attribute?('age') # => true
# person.has_attribute?(:nothing) # => false
def has_attribute?(attr_name)
- @attributes.has_key?(attr_name.to_s)
+ @attributes.include?(attr_name.to_s)
end
# Returns an array of names for the attributes available on this object.
@@ -271,9 +275,7 @@ module ActiveRecord
# 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}
def attributes
- attribute_names.each_with_object({}) { |name, attrs|
- attrs[name] = read_attribute(name)
- }
+ @attributes.to_hash
end
# Returns an <tt>#inspect</tt>-like string for the value of the
@@ -367,12 +369,6 @@ module ActiveRecord
protected
- def clone_attributes # :nodoc:
- @attributes.each_with_object({}) do |(name, attr), h|
- h[name] = attr.dup
- end
- end
-
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
value = send(reader_method, attribute_name)
value.duplicable? ? value.clone : value
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index d057f0941a..9ee9a7815f 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -43,9 +43,7 @@ module ActiveRecord
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
def read_attribute_before_type_cast(attr_name)
- if attr = @attributes[attr_name.to_s]
- attr.value_before_type_cast
- end
+ @attributes[attr_name.to_s].value_before_type_cast
end
# Returns a hash of attributes before typecasting and deserialization.
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index ca71834641..e1a86fd3aa 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -129,7 +129,7 @@ module ActiveRecord
end
def _field_changed?(attr, old_value)
- attribute_named(attr).changed_from?(old_value)
+ @attributes[attr].changed_from?(old_value)
end
def changed_in_place
@@ -140,7 +140,7 @@ module ActiveRecord
def changed_in_place?(attr_name)
old_value = original_raw_attribute(attr_name)
- attribute_named(attr_name).changed_in_place_from?(old_value)
+ @attributes[attr_name].changed_in_place_from?(old_value)
end
def original_raw_attribute(attr_name)
@@ -154,7 +154,7 @@ module ActiveRecord
end
def store_original_raw_attribute(attr_name)
- original_raw_attributes[attr_name] = attribute_named(attr_name).value_for_database
+ original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
end
def store_original_raw_attributes
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 525c46970a..8c1cc128f7 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -99,10 +99,6 @@ module ActiveRecord
def attribute(attribute_name)
read_attribute(attribute_name)
end
-
- def attribute_named(attribute_name)
- @attributes.fetch(attribute_name, Attribute::Null)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
new file mode 100644
index 0000000000..5e5f7ca1b5
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -0,0 +1,53 @@
+module ActiveRecord
+ class AttributeSet # :nodoc:
+ delegate :[], :[]=, :fetch, :include?, :keys, :each_with_object, to: :attributes
+
+ def initialize(attributes)
+ @attributes = attributes
+ end
+
+ def to_hash
+ attributes.each_with_object({}) { |(k, v), h| h[k] = v.value }
+ end
+ alias_method :to_h, :to_hash
+
+ def freeze
+ @attributes.freeze
+ super
+ end
+
+ def initialize_dup(_)
+ @attributes = attributes.dup
+ attributes.each do |key, attr|
+ attributes[key] = attr.dup
+ end
+
+ super
+ end
+
+ def initialize_clone(_)
+ @attributes = attributes.clone
+ super
+ end
+
+ class Builder
+ def initialize(types)
+ @types = types
+ end
+
+ def build_from_database(values, additional_types = {})
+ attributes = Hash.new(Attribute::Null)
+ values.each_with_object(attributes) do |(name, value), hash|
+ type = additional_types.fetch(name, @types[name])
+ hash[name] = Attribute.from_database(value, type)
+ end
+ AttributeSet.new(attributes)
+ end
+ end
+
+ protected
+
+ attr_reader :attributes
+
+ end
+end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 9c80121d70..9a329ac564 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -27,7 +27,7 @@ module ActiveRecord
#
# ==== Examples
#
- # The type detected by Active Record can be overriden.
+ # The type detected by Active Record can be overridden.
#
# # db/schema.rb
# create_table :store_listings, force: true do |t|
@@ -109,13 +109,14 @@ module ActiveRecord
end
def clear_caches_calculated_from_columns
- @columns = nil
- @columns_hash = nil
- @column_types = nil
+ @attributes_builder = nil
@column_defaults = nil
- @raw_column_defaults = nil
@column_names = nil
+ @column_types = nil
+ @columns = nil
+ @columns_hash = nil
@content_columns = nil
+ @raw_column_defaults = nil
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index b3c3e26c9f..dd92e29199 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -184,9 +184,7 @@ module ActiveRecord
before_save :before_save_collection_association
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
- after_create save_method
- after_update save_method
+ after_save save_method
elsif reflection.has_one?
define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
# Configures two callbacks instead of a single after_save so that
@@ -364,6 +362,7 @@ module ActiveRecord
raise ActiveRecord::Rollback unless saved
end
+ @new_record_before_save = false
end
# reconstruct the scope now that we know the owner's id
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index db80c0faee..cb75070e3a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -364,7 +364,7 @@ module ActiveRecord
conn.expire
end
- release conn, owner
+ release owner
@available.add conn
end
@@ -377,7 +377,7 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- release conn, conn.owner
+ release conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
@@ -425,7 +425,7 @@ module ActiveRecord
end
end
- def release(conn, owner)
+ def release(owner)
thread_id = owner.object_id
@reserved_connections.delete thread_id
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 04ae67234f..ff92375820 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -14,8 +14,8 @@ module ActiveRecord
# value. Is this really the only case? Are we missing tests for other types?
# We should have a real column object passed (or nil) here, and check for that
# instead
- if column.respond_to?(:type_cast_for_database)
- value = column.type_cast_for_database(value)
+ if column.respond_to?(:cast_type)
+ value = column.cast_type.type_cast_for_database(value)
end
_quote(value)
@@ -34,8 +34,8 @@ module ActiveRecord
# value. Is this really the only case? Are we missing tests for other types?
# We should have a real column object passed (or nil) here, and check for that
# instead
- if column.respond_to?(:type_cast_for_database)
- value = column.type_cast_for_database(value)
+ if column.respond_to?(:cast_type)
+ value = column.cast_type.type_cast_for_database(value)
end
_type_cast(value)
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 8be4678ace..1f1e2c46f4 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -57,12 +57,6 @@ module ActiveRecord
end
end
end
-
- class NullColumn < Column
- def initialize(name)
- super name, nil, Type::Value.new
- end
- end
end
# :startdoc:
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 17fabe5af6..3fea8f490d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -179,8 +179,8 @@ module ActiveRecord
end
def array_column(column)
- if column.array && !column.respond_to?(:type_cast_for_database)
- OID::Array.new(AdapterProxyType.new(column, self))
+ if column.array && !column.respond_to?(:cast_type)
+ Column.new('', nil, OID::Array.new(AdapterProxyType.new(column, self)))
else
column
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 71b05cdbae..6d5151dfe4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -306,10 +306,6 @@ module ActiveRecord
self.client_min_messages = old
end
- def supports_insert_with_returning?
- true
- end
-
def supports_ddl_transactions?
true
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index d39e5fddfe..10b9e27b7d 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -16,7 +16,6 @@ module ActiveRecord
mattr_accessor :logger, instance_writer: false
##
- # :singleton-method:
# Contains the database configuration - as is typically stored in config/database.yml -
# as a Hash.
#
@@ -251,12 +250,10 @@ module ActiveRecord
def initialize(attributes = nil, options = {})
defaults = {}
self.class.raw_column_defaults.each do |k, v|
- default = v.duplicable? ? v.dup : v
- defaults[k] = Attribute.from_database(default, type_for_attribute(k))
+ defaults[k] = v.duplicable? ? v.dup : v
end
- @attributes = defaults
- @column_types = self.class.column_types
+ @attributes = self.class.attributes_builder.build_from_database(defaults)
init_internals
initialize_internals_callback
@@ -282,7 +279,6 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
@attributes = coder['attributes']
- @column_types = self.class.column_types
init_internals
@@ -325,7 +321,7 @@ module ActiveRecord
##
def initialize_dup(other) # :nodoc:
pk = self.class.primary_key
- @attributes = other.clone_attributes
+ @attributes = @attributes.dup
@attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@@ -525,7 +521,9 @@ module ActiveRecord
def init_internals
pk = self.class.primary_key
- @attributes[pk] ||= Attribute.from_database(nil, type_for_attribute(pk))
+ unless @attributes.include?(pk)
+ @attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
+ end
@aggregation_cache = {}
@association_cache = {}
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index fad7eae461..099042cab2 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -219,19 +219,25 @@ module ActiveRecord
connection.schema_cache.table_exists?(table_name)
end
+ def attributes_builder # :nodoc:
+ @attributes_builder ||= AttributeSet::Builder.new(column_types)
+ end
+
def column_types # :nodoc:
- @column_types ||= Hash[columns.map { |column| [column.name, column.cast_type] }]
+ @column_types ||= Hash.new(Type::Value.new).tap do |column_types|
+ columns.each { |column| column_types[column.name] = column.cast_type }
+ end
end
def type_for_attribute(attr_name) # :nodoc:
- column_types.fetch(attr_name) { Type::Value.new }
+ column_types[attr_name]
end
# 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
- @column_defaults ||= Hash[columns_hash.map { |name, column|
- [name, column.type_cast_from_database(column.default)]
+ @column_defaults ||= Hash[raw_column_defaults.map { |name, default|
+ [name, type_for_attribute(name).type_cast_from_database(default)]
}]
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 5c744762d9..ee634d7bb3 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -48,11 +48,7 @@ module ActiveRecord
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
-
- attributes = attributes.each_with_object({}) do |(name, value), h|
- type = column_types.fetch(name) { klass.type_for_attribute(name) }
- h[name] = Attribute.from_database(value, type)
- end
+ attributes = klass.attributes_builder.build_from_database(attributes, column_types)
klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
end
@@ -399,9 +395,7 @@ module ActiveRecord
self.class.unscoped { self.class.find(id) }
end
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
-
- @column_types = self.class.column_types
+ @attributes = fresh_object.instance_variable_get('@attributes')
self
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 39817703cd..a9ddd9141f 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -37,14 +37,7 @@ module ActiveRecord
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
def find_by_sql(sql, binds = [])
result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
- column_types = {}
-
- if result_set.respond_to? :column_types
- column_types = result_set.column_types.except(*columns_hash.keys)
- else
- ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
- end
-
+ column_types = result_set.column_types.except(*columns_hash.keys)
result_set.map { |record| instantiate(record, column_types) }
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 51c96373ee..28c39bdd5c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -290,8 +290,8 @@ module ActiveRecord
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
- def primary_key_column
- klass.columns_hash[klass.primary_key]
+ def primary_key_type
+ klass.type_for_attribute(klass.primary_key)
end
def association_foreign_key
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 316a59e0bb..c80ffbae92 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -273,7 +273,7 @@ module ActiveRecord
row = result.first
value = row && row.values.first
column = result.column_types.fetch(column_alias) do
- column_for(column_name)
+ type_for(column_name)
end
type_cast_calculated_value(value, column, operation)
@@ -336,14 +336,14 @@ module ActiveRecord
Hash[calculated_data.map do |row|
key = group_columns.map { |aliaz, col_name|
column = calculated_data.column_types.fetch(aliaz) do
- column_for(col_name)
+ type_for(col_name)
end
type_cast_calculated_value(row[aliaz], column)
}
key = key.first if key.size == 1
key = key_records[key] if associated
- column_type = calculated_data.column_types.fetch(aggregate_alias) { column_for(column_name) }
+ column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
[key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
end]
end
@@ -370,24 +370,20 @@ module ActiveRecord
@klass.connection.table_alias_for(table_name)
end
- def column_for(field)
+ def type_for(field)
field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
- @klass.columns_hash[field_name]
+ @klass.type_for_attribute(field_name)
end
- def type_cast_calculated_value(value, column, operation = nil)
+ def type_cast_calculated_value(value, type, operation = nil)
case operation
when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || 0, column)
+ when 'sum' then type.type_cast_from_database(value || 0)
when 'average' then value.respond_to?(:to_d) ? value.to_d : value
- else type_cast_using_column(value, column)
+ else type.type_cast_from_database(value)
end
end
- def type_cast_using_column(value, column)
- column ? column.type_cast_from_database(value) : value
- end
-
# TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
def select_for_count
if select_values.present?
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 7014bc6d45..219e19dbb8 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -129,7 +129,7 @@ module ActiveRecord
private
def store_accessor_for(store_attribute)
- @column_types[store_attribute.to_s].accessor
+ type_for_attribute(store_attribute.to_s).accessor
end
class HashAccessor
diff --git a/activerecord/lib/active_record/type/numeric.rb b/activerecord/lib/active_record/type/numeric.rb
index 137c9e4c99..a7bf0657b9 100644
--- a/activerecord/lib/active_record/type/numeric.rb
+++ b/activerecord/lib/active_record/type/numeric.rb
@@ -16,26 +16,20 @@ module ActiveRecord
end
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
- # 0 => 'wibble' should mark as changed so numericality validations run
- if nil_or_zero?(old_value) && non_numeric_string?(new_value_before_type_cast)
- # nil => '' should not mark as changed
- old_value != new_value_before_type_cast.presence
- else
- super
- end
+ super || zero_to_non_number?(old_value, new_value_before_type_cast)
end
private
+ def zero_to_non_number?(old_value, new_value_before_type_cast)
+ old_value == 0 && non_numeric_string?(new_value_before_type_cast)
+ end
+
def non_numeric_string?(value)
# 'wibble'.to_i will give zero, we want to make sure
# that we aren't marking int zero to string zero as
# changed.
- value !~ /\A\d+\.?\d*\z/
- end
-
- def nil_or_zero?(value)
- value.nil? || value == 0
+ value.to_s !~ /\A\d+\.?\d*\z/
end
end
end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 875fb98c4b..063139e620 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -63,7 +63,7 @@ module ActiveRecord
private
# Takes an input from the database, or from attribute setters,
# and casts it to a type appropriate for this object. This method
- # should not be overriden by subclasses. Instead, override `cast_value`.
+ # should not be overridden by subclasses. Instead, override `cast_value`.
def type_cast(value) # :api: public
cast_value(value) unless value.nil?
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2e7b1d7206..04e28a0cfe 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -48,7 +48,7 @@ module ActiveRecord
def build_relation(klass, table, attribute, value) #:nodoc:
if reflection = klass._reflect_on_association(attribute)
attribute = reflection.foreign_key
- value = value.attributes[reflection.primary_key_column.name] unless value.nil?
+ value = value.attributes[reflection.klass.primary_key] unless value.nil?
end
attribute_name = attribute.to_s
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 080c499444..cc58a4a1a2 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -860,7 +860,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 'edges', Vertex.reflect_on_association(:sources).join_table
end
- def test_namespaced_habtm
+ def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_namespaced_model
magazine = Publisher::Magazine.create
article = Publisher::Article.create
magazine.articles << article
@@ -869,6 +869,15 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_includes magazine.articles, article
end
+ def test_has_and_belongs_to_many_in_a_namespaced_model_pointing_to_a_non_namespaced_model
+ article = Publisher::Article.create
+ tag = Tag.create
+ article.tags << tag
+ article.save
+
+ assert_includes article.tags, tag
+ end
+
def test_redefine_habtm
child = SubDeveloper.new("name" => "Aredridel")
child.special_projects << SpecialProject.new("name" => "Special Project")
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 5f01352ab4..3c0b735607 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -772,6 +772,36 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal topic.replies.to_a.size, topic.replies_count
end
+ def test_counter_cache_updates_in_memory_after_concat
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies << Reply.create(title: "re: zoom", content: "speedy quick!")
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.replies.size
+ assert_equal 1, topic.reload.replies.size
+ end
+
+ def test_counter_cache_updates_in_memory_after_create
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies.create!(title: "re: zoom", content: "speedy quick!")
+ assert_equal 1, topic.replies_count
+ assert_equal 1, topic.replies.size
+ assert_equal 1, topic.reload.replies.size
+ end
+
+ def test_counter_cache_updates_in_memory_after_create_with_array
+ topic = Topic.create title: "Zoom-zoom-zoom"
+
+ topic.replies.create!([
+ { title: "re: zoom", content: "speedy quick!" },
+ { title: "re: zoom 2", content: "OMG lol!" },
+ ])
+ assert_equal 2, topic.replies_count
+ assert_equal 2, topic.replies.size
+ assert_equal 2, topic.reload.replies.size
+ end
+
def test_pushing_association_updates_counter_cache
topic = Topic.order("id ASC").first
reply = Reply.create!
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 8641584c0c..0fa34e829e 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1139,4 +1139,31 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size
assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size
end
+
+ def test_has_many_through_add_with_sti_middle_relation
+ club = SuperClub.create!(name: 'Fight Club')
+ member = Member.create!(name: 'Tyler Durden')
+
+ club.members << member
+ assert_equal 1, SuperMembership.where(member_id: member.id, club_id: club.id).count
+ end
+
+ class ClubWithCallbacks < ActiveRecord::Base
+ self.table_name = 'clubs'
+ after_create :add_a_member
+
+ has_many :memberships, inverse_of: :club, foreign_key: :club_id
+ has_many :members, through: :memberships
+
+ def add_a_member
+ members << Member.last
+ end
+ end
+
+ def test_has_many_with_callback_before_association
+ Member.create!
+ club = ClubWithCallbacks.create!
+
+ assert_equal 1, club.reload.memberships.count
+ end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index f832ca3451..7566af920f 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -831,6 +831,37 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_attribute_method?
+ assert @target.attribute_method?(:title)
+ assert @target.attribute_method?(:title=)
+ assert_not @target.attribute_method?(:wibble)
+ end
+
+ def test_attribute_method_returns_false_if_table_does_not_exist
+ @target.table_name = 'wibble'
+ assert_not @target.attribute_method?(:title)
+ end
+
+ def test_attribute_names_on_new_record
+ model = @target.new
+
+ assert_equal @target.column_names, model.attribute_names
+ end
+
+ def test_attribute_names_on_queried_record
+ model = @target.last!
+
+ assert_equal @target.column_names, model.attribute_names
+ end
+
+ def test_attribute_names_with_custom_select
+ model = @target.select('id').last!
+
+ assert_equal ['id'], model.attribute_names
+ # Sanity check, make sure other columns exist
+ assert_not_equal ['id'], @target.column_names
+ end
+
private
def new_topic_like_ar_class(&block)
diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb
new file mode 100644
index 0000000000..402a611efa
--- /dev/null
+++ b/activerecord/test/cases/attribute_set_test.rb
@@ -0,0 +1,65 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class AttributeSetTest < ActiveRecord::TestCase
+ test "building a new set from raw attributes" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2.2, attributes[:bar].value
+ end
+
+ test "building with custom types" do
+ builder = AttributeSet::Builder.new(foo: Type::Float.new)
+ attributes = builder.build_from_database({ foo: '3.3', bar: '4.4' }, { bar: Type::Integer.new })
+
+ assert_equal 3.3, attributes[:foo].value
+ assert_equal 4, attributes[:bar].value
+ end
+
+ test "[] returns a null object" do
+ builder = AttributeSet::Builder.new(foo: Type::Float.new)
+ attributes = builder.build_from_database(foo: '3.3')
+
+ assert_equal '3.3', attributes[:foo].value_before_type_cast
+ assert_equal nil, attributes[:bar].value_before_type_cast
+ end
+
+ test "duping creates a new hash and dups each attribute" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
+ attributes = builder.build_from_database(foo: 1, bar: 'foo')
+
+ # Ensure the type cast value is cached
+ attributes[:foo].value
+ attributes[:bar].value
+
+ duped = attributes.dup
+ duped[:foo] = Attribute.from_database(2, Type::Integer.new)
+ duped[:bar].value << 'bar'
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2, duped[:foo].value
+ assert_equal 'foo', attributes[:bar].value
+ assert_equal 'foobar', duped[:bar].value
+ end
+
+ test "freezing cloned set does not freeze original" do
+ attributes = AttributeSet.new({})
+ clone = attributes.clone
+
+ clone.freeze
+
+ assert clone.frozen?
+ assert_not attributes.frozen?
+ end
+
+ test "to_hash returns a hash of the type cast values" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
+
+ assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash)
+ assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
+ end
+ end
+end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 222b1505a8..319ea9260a 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -53,11 +53,6 @@ class CalculationsTest < ActiveRecord::TestCase
assert_nil NumericData.average(:bank_balance)
end
- def test_type_cast_calculated_value_should_convert_db_averages_of_fixnum_class_to_decimal
- assert_equal 0, NumericData.all.send(:type_cast_calculated_value, 0, nil, 'avg')
- assert_equal 53.0, NumericData.all.send(:type_cast_calculated_value, 53, nil, 'avg')
- end
-
def test_should_get_maximum_of_field
assert_equal 60, Account.maximum(:credit_limit)
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 28341d0b42..1192ecd6b4 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -858,4 +858,11 @@ class PersistenceTest < ActiveRecord::TestCase
post.body
end
end
+
+ def test_reload_removes_custom_selects
+ post = Post.select('posts.*, 1 as wibble').last!
+
+ assert_equal 1, post[:wibble]
+ assert_nil post.reload[:wibble]
+ end
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index bbd5298da1..70d9b9dbf5 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -93,12 +93,10 @@ module ActiveRecord
def test_quote_true
assert_equal @quoter.quoted_true, @quoter.quote(true, nil)
- assert_equal '1', @quoter.quote(true, Type::Integer.new)
end
def test_quote_false
assert_equal @quoter.quoted_false, @quoter.quote(false, nil)
- assert_equal '0', @quoter.quote(false, Type::Integer.new)
end
def test_quote_float
@@ -157,25 +155,6 @@ module ActiveRecord
assert_equal "'lo\\\\l'", @quoter.quote(string, nil)
end
- def test_quote_string_int_column
- assert_equal "1", @quoter.quote('1', Type::Integer.new)
- assert_equal "1", @quoter.quote('1.2', Type::Integer.new)
- end
-
- def test_quote_string_float_column
- assert_equal "1.0", @quoter.quote('1', Type::Float.new)
- assert_equal "1.2", @quoter.quote('1.2', Type::Float.new)
- end
-
- def test_quote_as_mb_chars_binary_column
- string = ActiveSupport::Multibyte::Chars.new('lo\l')
- assert_equal "'lo\\\\l'", @quoter.quote(string, Type::Binary.new)
- end
-
- def test_quote_binary_without_string_to_binary
- assert_equal "'lo\\\\l'", @quoter.quote('lo\l', Type::Binary.new)
- end
-
def test_string_with_crazy_column
assert_equal "'lo\\\\l'", @quoter.quote('lo\l')
end
@@ -183,10 +162,6 @@ module ActiveRecord
def test_quote_duration
assert_equal "1800", @quoter.quote(30.minutes)
end
-
- def test_quote_duration_int_column
- assert_equal "7200", @quoter.quote(2.hours, Type::Integer.new)
- end
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index c6e73f78cc..acbd065649 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -80,24 +80,10 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
- def test_non_existent_columns_return_null_object
- column = @first.column_for_attribute("attribute_that_doesnt_exist")
- assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
- assert_equal "attribute_that_doesnt_exist", column.name
- assert_equal nil, column.sql_type
- assert_equal nil, column.type
- assert_not column.number?
- assert_not column.text?
- assert_not column.binary?
- end
-
- def test_non_existent_columns_are_identity_types
- column = @first.column_for_attribute("attribute_that_doesnt_exist")
- object = Object.new
-
- assert_equal object, column.type_cast_from_database(object)
- assert_equal object, column.type_cast_from_user(object)
- assert_equal object, column.type_cast_for_database(object)
+ def test_non_existent_columns_return_nil
+ assert_deprecated do
+ assert_nil @first.column_for_attribute("attribute_that_doesnt_exist")
+ end
end
def test_reflection_klass_for_nested_class_name
diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb
index a762ad4bb5..6ceafe5858 100644
--- a/activerecord/test/models/club.rb
+++ b/activerecord/test/models/club.rb
@@ -14,3 +14,10 @@ class Club < ActiveRecord::Base
"I'm sorry sir, this is a *private* club, not a *pirate* club"
end
end
+
+class SuperClub < ActiveRecord::Base
+ self.table_name = "clubs"
+
+ has_many :memberships, class_name: 'SuperMembership', foreign_key: 'club_id'
+ has_many :members, through: :memberships
+end
diff --git a/activerecord/test/models/publisher/article.rb b/activerecord/test/models/publisher/article.rb
index 03a277bbdd..d73a8eb936 100644
--- a/activerecord/test/models/publisher/article.rb
+++ b/activerecord/test/models/publisher/article.rb
@@ -1,3 +1,4 @@
class Publisher::Article < ActiveRecord::Base
has_and_belongs_to_many :magazines
+ has_and_belongs_to_many :tags
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a7c7fc70bc..fd85050dd4 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -62,6 +62,11 @@ ActiveRecord::Schema.define do
t.references :magazine
end
+ create_table :articles_tags, force: true do |t|
+ t.references :article
+ t.references :tag
+ end
+
create_table :audit_logs, force: true do |t|
t.column :message, :string, null: false
t.column :developer_id, :integer, null: false