diff options
Diffstat (limited to 'activerecord/lib')
12 files changed, 169 insertions, 20 deletions
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb new file mode 100644 index 0000000000..e0bee8c17e --- /dev/null +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -0,0 +1,32 @@ +require 'active_record/attribute' + +module ActiveRecord + class Attribute # :nodoc: + class UserProvidedDefault < FromUser + def initialize(name, value, type, database_default) + super(name, value, type) + @database_default = database_default + end + + def type_cast(value) + if value.is_a?(Proc) + super(value.call) + else + super + end + end + + def changed_in_place_from?(old_value) + super || changed_from?(database_default.value) + end + + def with_type(type) + self.class.new(name, value_before_type_cast, type, database_default) + end + + protected + + attr_reader :database_default + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index d0d8a968c5..fd7099e0de 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -11,6 +11,15 @@ module ActiveRecord # serialized object must be of that class on assignment and retrieval. # Otherwise <tt>SerializationTypeMismatch</tt> will be raised. # + # Keep in mind that database adapters handle certain serialization tasks + # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be + # converted between JSON object/array syntax and Ruby +Hash+ or +Array+ + # objects transparently. There is no need to use +serialize+ in this + # case. + # + # For more complex cases, such as conversion to or from your application + # domain objects, consider using the ActiveRecord::Attributes API. + # # ==== Parameters # # * +attr_name+ - The field name that should be serialized. diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 50339b6f69..8b2c4c7170 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -1,3 +1,5 @@ +require 'active_record/attribute/user_provided_default' + module ActiveRecord # See ActiveRecord::Attributes::ClassMethods for documentation module Attributes @@ -80,6 +82,14 @@ module ActiveRecord # # StoreListing.new.my_string # => "new default" # + # class Product < ActiveRecord::Base + # attribute :my_default_proc, :datetime, default: -> { Time.now } + # end + # + # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600 + # sleep 1 + # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600 + # # Attributes do not need to be backed by a database column. # # class MyModel < ActiveRecord::Base @@ -202,7 +212,8 @@ module ActiveRecord # # +default+ The default value to use when no value is provided. If this option # is not passed, the previous default value (if any) will be used. - # Otherwise, the default will be +nil+. + # Otherwise, the default will be +nil+. A proc can also be passed, and + # will be called once each time a new value is needed. # # +user_provided_default+ Whether the default value should be cast using # +cast+ or +deserialize+. @@ -236,7 +247,12 @@ module ActiveRecord if value == NO_DEFAULT_PROVIDED default_attribute = _default_attributes[name].with_type(type) elsif from_user - default_attribute = Attribute.from_user(name, value, type) + default_attribute = Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes[name], + ) else default_attribute = Attribute.from_database(name, value, type) 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 c3206c6045..00e3d2965b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -959,10 +959,12 @@ module ActiveRecord wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum) variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout) + defaults = [':default', :default].to_set + # Make MySQL reject illegal values rather than truncating or blanking them, see # http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. - unless variables.has_key?('sql_mode') + unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict]) variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' end @@ -977,7 +979,7 @@ module ActiveRecord # Gather up all of the SET variables... variable_assignments = variables.map do |k, v| - if v == ':default' || v == :default + if defaults.include?(v) "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default elsif !v.nil? "@@SESSION.#{k} = #{quote(v)}" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb new file mode 100644 index 0000000000..fe1dcbd710 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaCreation < AbstractAdapter::SchemaCreation + private + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7faca9cb70..87129c42cf 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/statement_pool' +require 'active_record/connection_adapters/sqlite3/schema_creation' gem 'sqlite3', '~> 1.3.6' require 'sqlite3' @@ -84,6 +85,10 @@ module ActiveRecord end end + def schema_creation # :nodoc: + SQLite3::SchemaCreation.new self + end + def initialize(connection, logger, connection_options, config) super(connection, logger) @@ -344,9 +349,10 @@ module ActiveRecord field["dflt_value"] = $1.gsub('""', '"') end + collation = field['collation'] sql_type = field['type'] type_metadata = fetch_type_metadata(sql_type) - new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0) + new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation) end end @@ -441,6 +447,7 @@ module ActiveRecord self.null = options[:null] if options.include?(:null) self.precision = options[:precision] if options.include?(:precision) self.scale = options[:scale] if options.include?(:scale) + self.collation = options[:collation] if options.include?(:collation) end end end @@ -454,9 +461,9 @@ module ActiveRecord protected def table_structure(table_name) - structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA') raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? - structure + table_structure_with_collation(table_name, structure) end def alter_table(table_name, options = {}) #:nodoc: @@ -491,7 +498,7 @@ module ActiveRecord @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, :precision => column.precision, :scale => column.scale, - :null => column.null) + :null => column.null, collation: column.collation) end yield @definition if block_given? end @@ -553,6 +560,46 @@ module ActiveRecord super end end + + private + COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze + + def table_structure_with_collation(table_name, basic_structure) + collation_hash = {} + sql = "SELECT sql FROM + (SELECT * FROM sqlite_master UNION ALL + SELECT * FROM sqlite_temp_master) + WHERE type='table' and name='#{ table_name }' \;" + + # Result will have following sample string + # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + # "password_digest" varchar COLLATE "NOCASE"); + result = exec_query(sql, 'SCHEMA').first + + if result + # Splitting with left parantheses and picking up last will return all + # columns separated with comma(,). + columns_string = result["sql"].split('(').last + + columns_string.split(',').each do |column_string| + # This regex will match the column name and collation type and will save + # the value in $1 and $2 respectively. + collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string) + end + + basic_structure.map! do |column| + column_name = column['name'] + + if collation_hash.has_key? column_name + column['collation'] = collation_hash[column_name] + end + + column + end + else + basic_structure.to_hash + end + end end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 2c1771dd6c..1ec8f818cd 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -644,6 +644,11 @@ module ActiveRecord row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type) end + # Resolve enums + model_class.defined_enums.each do |name, values| + row[name] = values.fetch(row[name], row[name]) + end + # If STI is used, find the correct subclass for association reflection reflection_class = if row.include?(inheritance_column_name) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 466175690e..437ba31711 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -382,7 +382,7 @@ module ActiveRecord # # => #<Account id: 1, email: 'account@example.com'> # # Attributes are reloaded from the database, and caches busted, in - # particular the associations cache. + # particular the associations cache and the QueryCache. # # If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt> # is raised. Otherwise, in addition to the in-place modification the method @@ -418,6 +418,8 @@ module ActiveRecord # end # def reload(options = nil) + self.class.connection.clear_query_cache + fresh_object = if options && options[:lock] self.class.unscoped { self.class.lock(options[:lock]).find(id) } diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index d4a8823cfe..86f2c30168 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -39,7 +39,7 @@ module ActiveRecord BLACKLISTED_ARRAY_METHODS = [ :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!, :shuffle!, :slice!, :sort!, :sort_by!, :delete_if, - :keep_if, :pop, :shift, :delete_at, :compact, :select! + :keep_if, :pop, :shift, :delete_at, :select! ].to_set # :nodoc: delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index c3054f1fe9..dd8f0aa298 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -50,7 +50,7 @@ module ActiveRecord NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS - - [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: + [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: def normal_values NORMAL_VALUES @@ -75,6 +75,7 @@ module ActiveRecord merge_multi_values merge_single_values merge_clauses + merge_preloads merge_joins relation @@ -82,6 +83,27 @@ module ActiveRecord private + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? + + if other.klass == relation.klass + relation.preload! other.preload_values unless other.preload_values.empty? + relation.includes! other.includes_values unless other.includes_values.empty? + else + reflection = relation.klass.reflect_on_all_associations.find do |r| + r.class_name == other.klass.name + end || return + + unless other.preload_values.empty? + relation.preload! reflection.name => other.preload_values + end + + unless other.includes_values.empty? + relation.includes! reflection.name => other.includes_values + end + end + end + def merge_joins return if other.joins_values.blank? diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index fd78db2e95..f85dc35e89 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1001,15 +1001,13 @@ module ActiveRecord end def arel_columns(columns) - if from_clause.value - columns - else - columns.map do |field| - if (Symbol === field || String === field) && columns_hash.key?(field.to_s) - arel_table[field] - else - field - end + columns.map do |field| + if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value + arel_table[field] + elsif Symbol === field + connection.quote_table_name(field.to_s) + else + field end end end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index bc80275a88..673386f0d9 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -59,6 +59,7 @@ module ActiveRecord args = prepare_command_options('mysqldump') args.concat(["--result-file", "#{filename}"]) args.concat(["--no-data"]) + args.concat(["--routines"]) args.concat(["#{configuration['database']}"]) unless Kernel.system(*args) $stderr.puts "Could not dump the database structure. "\ |