aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb4
-rw-r--r--activerecord/lib/active_record/attribute_set.rb10
-rw-r--r--activerecord/lib/active_record/attributes.rb3
-rw-r--r--activerecord/lib/active_record/base.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb54
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/core.rb7
-rw-r--r--activerecord/lib/active_record/inheritance.rb31
-rw-r--r--activerecord/lib/active_record/model_schema.rb20
17 files changed, 98 insertions, 139 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 51f6a009db..e3ac891520 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -202,8 +202,8 @@ module ActiveRecord
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]`
+ in Rails 5.0. Use `has_attribute?` if you need to check for an
+ attribute's existence.
MESSAGE
end
column
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 5be11e6ab9..8a964fb03c 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -13,11 +13,11 @@ module ActiveRecord
end
def values_before_type_cast
- attributes.each_with_object({}) { |(k, v), h| h[k] = v.value_before_type_cast }
+ attributes.transform_values(&:value_before_type_cast)
end
def to_hash
- initialized_attributes.each_with_object({}) { |(k, v), h| h[k] = v.value }
+ initialized_attributes.transform_values(&:value)
end
alias_method :to_h, :to_hash
@@ -43,11 +43,7 @@ module ActiveRecord
end
def initialize_dup(_)
- @attributes = attributes.dup
- attributes.each do |key, attr|
- attributes[key] = attr.dup
- end
-
+ @attributes = attributes.transform_values(&:dup)
super
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 492d8f3560..890a1314d9 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -110,13 +110,12 @@ module ActiveRecord
def clear_caches_calculated_from_columns
@attributes_builder = nil
- @column_defaults = nil
@column_names = nil
@column_types = nil
@columns = nil
@columns_hash = nil
@content_columns = nil
- @raw_column_defaults = nil
+ @default_attributes = nil
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 662c99269e..64cc5b68cc 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -9,6 +9,7 @@ require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/hash/transform_values'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/introspection'
@@ -219,25 +220,9 @@ module ActiveRecord #:nodoc:
#
# == Single table inheritance
#
- # Active Record allows inheritance by storing the name of the class in a column that by
- # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
- # This means that an inheritance looking like this:
- #
- # class Company < ActiveRecord::Base; end
- # class Firm < Company; end
- # class Client < Company; end
- # class PriorityClient < Client; end
- #
- # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
- # the companies table with type = "Firm". You can then fetch this row again using
- # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
- #
- # If you don't have a type column defined in your table, single-table inheritance won't
- # be triggered. In that case, it'll work just like normal subclasses with no special magic
- # for differentiating between them or reloading the right type with find.
- #
- # Note, all the attributes for all the cases are kept in the same table. Read more:
- # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
+ # Active Record allows inheritance by storing the name of the class in a
+ # column that is named "type" by default. See ActiveRecord::Inheritance for
+ # more details.
#
# == Connection to multiple databases in different models
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index ff92375820..bee99e5fc9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -9,12 +9,7 @@ module ActiveRecord
# records are quoted as their primary key
return value.quoted_id if value.respond_to?(:quoted_id)
- # FIXME: The only case we get an object other than nil or a real column
- # is `SchemaStatements#add_column` with a PG array that has a non-empty default
- # 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?(:cast_type)
+ if column
value = column.cast_type.type_cast_for_database(value)
end
@@ -29,12 +24,7 @@ module ActiveRecord
return value.id
end
- # FIXME: The only case we get an object other than nil or a real column
- # is `SchemaStatements#add_column` with a PG array that has a non-empty default
- # 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?(:cast_type)
+ if column
value = column.cast_type.type_cast_for_database(value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index c1379f6bec..adad6cd542 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -94,6 +94,7 @@ module ActiveRecord
def quote_value(value, column)
column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
+ column.cast_type ||= type_for_column(column)
@conn.quote(value, column)
end
@@ -114,6 +115,10 @@ module ActiveRecord
MSG
end
end
+
+ def type_for_column(column)
+ @conn.lookup_cast_type(column.sql_type)
+ 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 33b522f391..98e6795f10 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type, :cast_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 294ed6d7bf..f8c054eb69 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -375,12 +375,12 @@ module ActiveRecord
Column.new(name, default, cast_type, sql_type, null)
end
- protected
-
def lookup_cast_type(sql_type) # :nodoc:
type_map.lookup(sql_type)
end
+ protected
+
def initialize_type_map(m) # :nodoc:
register_class_with_limit m, %r(boolean)i, Type::Boolean
register_class_with_limit m, %r(char)i, Type::String
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index a865c5c310..c916c0795d 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -2,18 +2,9 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module Cast # :nodoc:
- def point_to_string(point) # :nodoc:
- "(#{number_for_point(point[0])},#{number_for_point(point[1])})"
- end
-
- def number_for_point(number)
- number.to_s.gsub(/\.0$/, '')
- end
-
- def hstore_to_string(object, array_member = false) # :nodoc:
+ def hstore_to_string(object) # :nodoc:
if Hash === object
string = object.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(', ')
- string = escape_hstore(string) if array_member
string
else
object
@@ -34,28 +25,12 @@ module ActiveRecord
end
end
- def json_to_string(object) # :nodoc:
- if Hash === object || Array === object
- ActiveSupport::JSON.encode(object)
- else
- object
- end
- end
-
def range_to_string(object) # :nodoc:
from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
"[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
end
- def string_to_json(string) # :nodoc:
- if String === string
- ActiveSupport::JSON.decode(string)
- else
- string
- end
- end
-
private
HstorePair = begin
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
index ab1165f301..e12ddd9901 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb
@@ -10,11 +10,19 @@ module ActiveRecord
end
def type_cast_from_database(value)
- ConnectionAdapters::PostgreSQLColumn.string_to_json(value)
+ if value.is_a?(::String)
+ ::ActiveSupport::JSON.decode(value)
+ else
+ super
+ end
end
def type_cast_for_database(value)
- ConnectionAdapters::PostgreSQLColumn.json_to_string(value)
+ if value.is_a?(::Array) || value.is_a?(::Hash)
+ ::ActiveSupport::JSON.encode(value)
+ else
+ super
+ end
end
def accessor
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
index 9b6494867f..bac8b01d6b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/point.rb
@@ -25,11 +25,17 @@ module ActiveRecord
def type_cast_for_database(value)
if value.is_a?(::Array)
- PostgreSQLColumn.point_to_string(value)
+ "(#{number_for_point(value[0])},#{number_for_point(value[1])})"
else
super
end
end
+
+ private
+
+ def number_for_point(number)
+ number.to_s.gsub(/\.0$/, '')
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 4caed77952..f9541b437a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -27,17 +27,9 @@ module ActiveRecord
else
super
end
- when Array
- case sql_type
- when 'point' then super(PostgreSQLColumn.point_to_string(value))
- when 'json' then super(PostgreSQLColumn.json_to_string(value))
- else
- super(value, array_column(column))
- end
when Hash
case sql_type
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
- when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
when Float
@@ -71,39 +63,29 @@ module ActiveRecord
end
end
- def type_cast(value, column, array_member = false)
- return super(value, column) unless column
+ def type_cast(value, column)
+ return super unless column
case value
when Range
if /range$/ =~ column.sql_type
PostgreSQLColumn.range_to_string(value)
else
- super(value, column)
+ super
end
when NilClass
- if column.array && array_member
- 'NULL'
- elsif column.array
+ if column.array
value
else
- super(value, column)
- end
- when Array
- case column.sql_type
- when 'point' then PostgreSQLColumn.point_to_string(value)
- when 'json' then PostgreSQLColumn.json_to_string(value)
- else
- super(value, array_column(column))
+ super
end
when Hash
case column.sql_type
- when 'hstore' then PostgreSQLColumn.hstore_to_string(value, array_member)
- when 'json' then PostgreSQLColumn.json_to_string(value)
- else super(value, column)
+ when 'hstore' then PostgreSQLColumn.hstore_to_string(value)
+ else super
end
else
- super(value, column)
+ super
end
end
@@ -177,26 +159,6 @@ module ActiveRecord
super
end
end
-
- def array_column(column)
- if column.array && !column.respond_to?(:cast_type)
- Column.new('', nil, OID::Array.new(AdapterProxyType.new(column, self)))
- else
- column
- end
- end
-
- class AdapterProxyType < SimpleDelegator # :nodoc:
- def initialize(column, adapter)
- @column = column
- @adapter = adapter
- super(column)
- end
-
- def type_cast_for_database(value)
- @adapter.type_cast(value, @column)
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 596ebca5d6..e09672d239 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -31,6 +31,14 @@ module ActiveRecord
super
end
end
+
+ def type_for_column(column)
+ if column.array
+ @conn.lookup_cast_type("#{column.sql_type}[]")
+ else
+ super
+ end
+ end
end
module SchemaStatements
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 34262cf91d..a164758640 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -383,6 +383,11 @@ module ActiveRecord
PostgreSQL::Table.new(table_name, base)
end
+ def lookup_cast_type(sql_type) # :nodoc:
+ oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").first['oid'].to_i
+ super(oid)
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 3321e268d5..224112b559 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -248,12 +248,7 @@ module ActiveRecord
# # Instantiates a single new object
# User.new(first_name: 'Jamie')
def initialize(attributes = nil, options = {})
- defaults = {}
- self.class.raw_column_defaults.each do |k, v|
- defaults[k] = v.duplicable? ? v.dup : v
- end
-
- @attributes = self.class.attributes_builder.build_from_database(defaults)
+ @attributes = self.class.default_attributes.dup
init_internals
initialize_internals_callback
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 08fc91c9df..5c2f1215d2 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -1,6 +1,37 @@
require 'active_support/core_ext/hash/indifferent_access'
module ActiveRecord
+ # == Single table inheritance
+ #
+ # Active Record allows inheritance by storing the name of the class in a column that by
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
+ # This means that an inheritance looking like this:
+ #
+ # class Company < ActiveRecord::Base; end
+ # class Firm < Company; end
+ # class Client < Company; end
+ # class PriorityClient < Client; end
+ #
+ # When you do <tt>Firm.create(name: "37signals")</tt>, this record will be saved in
+ # the companies table with type = "Firm". You can then fetch this row again using
+ # <tt>Company.where(name: '37signals').first</tt> and it will return a Firm object.
+ #
+ # Be aware that because the type column is an attribute on the record every new
+ # subclass will instantly be marked as dirty and the type column will be included
+ # in the list of changed attributes on the record. This is different from non
+ # STI classes:
+ #
+ # Company.new.changed? # => false
+ # Firm.new.changed? # => true
+ # Firm.new.changes # => {"type"=>["","Firm"]}
+ #
+ # If you don't have a type column defined in your table, single-table inheritance won't
+ # be triggered. In that case, it'll work just like normal subclasses with no special magic
+ # for differentiating between them or reloading the right type with find.
+ #
+ # Note, all the attributes for all the cases are kept in the same table. Read more:
+ # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
+ #
module Inheritance
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 099042cab2..092c3b4fb7 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -224,8 +224,8 @@ module ActiveRecord
end
def column_types # :nodoc:
- @column_types ||= Hash.new(Type::Value.new).tap do |column_types|
- columns.each { |column| column_types[column.name] = column.cast_type }
+ @column_types ||= columns_hash.transform_values(&:cast_type).tap do |h|
+ h.default = Type::Value.new
end
end
@@ -236,17 +236,12 @@ module ActiveRecord
# 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[raw_column_defaults.map { |name, default|
- [name, type_for_attribute(name).type_cast_from_database(default)]
- }]
+ default_attributes.to_hash
end
- # Returns a hash where the keys are the column names and the values
- # are the default values suitable for use in `@raw_attriubtes`
- def raw_column_defaults # :nodoc:
- @raw_column_defaults ||= Hash[columns_hash.map { |name, column|
- [name, column.default]
- }]
+ def default_attributes # :nodoc:
+ @default_attributes ||= attributes_builder.build_from_database(
+ columns_hash.transform_values(&:default))
end
# Returns an array of column names as strings.
@@ -292,11 +287,10 @@ module ActiveRecord
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
@arel_engine = nil
- @column_defaults = nil
- @raw_column_defaults = nil
@column_names = nil
@column_types = nil
@content_columns = nil
+ @default_attributes = nil
@dynamic_methods_hash = nil
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
@relation = nil