diff options
Diffstat (limited to 'activerecord/lib/active_record')
18 files changed, 162 insertions, 40 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78159d13d4..360e494af1 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -115,8 +115,8 @@ module ActiveRecord #:nodoc: # When joining tables, nested hashes or keys written in the form 'table_name.column_name' # can be used to qualify the table name of a particular condition. For instance: # - # Student.joins(:schools).where(:schools => { :type => 'public' }) - # Student.joins(:schools).where('schools.type' => 'public' ) + # Student.joins(:schools).where(:schools => { :category => 'public' }) + # Student.joins(:schools).where('schools.category' => 'public' ) # # == Overwriting default accessors # @@ -2145,7 +2145,7 @@ MSG # AutosaveAssociation needs to be included before Transactions, because we want # #save_with_autosave_associations to be wrapped inside a transaction. include AutosaveAssociation, NestedAttributes - include Aggregations, Transactions, Reflection, Serialization + include Aggregations, Transactions, Reflection, Serialization, Store NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner) 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 20863e73aa..77a5fe1efb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -314,7 +314,7 @@ module ActiveRecord end def current_connection_id #:nodoc: - Thread.current.object_id + ActiveRecord::Base.connection_id ||= Thread.current.object_id end def checkout_new_connection diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index c08c0263b9..3d0f146fed 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -115,6 +115,14 @@ module ActiveRecord retrieve_connection end + def connection_id + Thread.current['ActiveRecord::Base.connection_id'] + end + + def connection_id=(connection_id) + Thread.current['ActiveRecord::Base.connection_id'] = connection_id + end + # Returns the configuration of the associated connection as a hash: # # ActiveRecord::Base.connection_config 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 82f564e41d..989a4fcbca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -252,7 +252,7 @@ module ActiveRecord # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and # <tt>:updated_at</tt> to the table. def timestamps(*args) - options = args.extract_options! + options = { :null => false }.merge(args.extract_options!) column(:created_at, :datetime, options) column(:updated_at, :datetime, options) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 8e3ba1297e..b4a9e29ef1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -507,8 +507,8 @@ module ActiveRecord # ===== Examples # add_timestamps(:suppliers) def add_timestamps(table_name) - add_column table_name, :created_at, :datetime - add_column table_name, :updated_at, :datetime + add_column table_name, :created_at, :datetime, :null => false + add_column table_name, :updated_at, :datetime, :null => false end # Removes the timestamp columns (created_at and updated_at) from the table definition. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 443e61b527..4c3a8f7233 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -238,6 +238,10 @@ module ActiveRecord node end + def case_insensitive_comparison(table, attribute, column, value) + table[attribute].lower.eq(table.lower(value)) + end + def current_savepoint_name "active_record_#{open_transactions}" 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 4b7c74e0b8..dd573ba569 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -4,6 +4,13 @@ module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter class Column < ConnectionAdapters::Column # :nodoc: + attr_reader :collation + + def initialize(name, default, sql_type = nil, null = true, collation = nil) + super(name, default, sql_type, null) + @collation = collation + end + def extract_default(default) if sql_type =~ /blob/i || type == :text if default.blank? @@ -28,6 +35,10 @@ module ActiveRecord raise NotImplementedError end + def case_sensitive? + collation && !collation.match(/_ci$/) + end + private def simplified_type(field_type) @@ -157,8 +168,8 @@ module ActiveRecord end # Overridden by the adapters to instantiate their specific Column type. - def new_column(field, default, type, null) # :nodoc: - Column.new(field, default, type, null) + def new_column(field, default, type, null, collation) # :nodoc: + Column.new(field, default, type, null, collation) end # Must return the Mysql error number from the exception, if the exception has an @@ -393,10 +404,10 @@ module ActiveRecord # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name, name = nil)#:nodoc: - sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" + sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}" execute_and_free(sql, 'SCHEMA') do |result| each_hash(result).map do |field| - new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") + new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation]) end end end @@ -501,6 +512,14 @@ module ActiveRecord Arel::Nodes::Bin.new(node) end + def case_insensitive_comparison(table, attribute, column, value) + if column.case_sensitive? + super + else + table[attribute].eq(value) + end + end + def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) where_sql end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 8b574518e5..971f3c35f3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -47,8 +47,8 @@ module ActiveRecord end end - def new_column(field, default, type, null) # :nodoc: - Column.new(field, default, type, null) + def new_column(field, default, type, null, collation) # :nodoc: + Column.new(field, default, type, null, collation) end def error_number(exception) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a1824fe396..f092edecda 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -150,8 +150,8 @@ module ActiveRecord end end - def new_column(field, default, type, null) # :nodoc: - Column.new(field, default, type, null) + def new_column(field, default, type, null, collation) # :nodoc: + Column.new(field, default, type, null, collation) end def error_number(exception) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5402918b1d..d859843260 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1035,13 +1035,14 @@ module ActiveRecord end def exec_cache(sql, binds) - unless @statements.key? sql + sql_key = "#{schema_search_path}-#{sql}" + unless @statements.key? sql_key nextkey = @statements.next_key @connection.prepare nextkey, sql - @statements[sql] = nextkey + @statements[sql_key] = nextkey end - key = @statements[sql] + key = @statements[sql_key] # Clear the queue @connection.get_last_result diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index ad7d8cd63c..fc80f3081e 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -99,6 +99,16 @@ module ActiveRecord # # Read more about optimistic locking in ActiveRecord::Locking module RDoc. class StaleObjectError < ActiveRecordError + attr_reader :record, :attempted_action + + def initialize(record, attempted_action) + @record = record + @attempted_action = attempted_action + end + + def message + "Attempted to #{attempted_action} a stale object: #{record.class.name}" + end end # Raised when association is being configured improperly or @@ -169,4 +179,17 @@ module ActiveRecord @errors = errors end end + + # Raised when a primary key is needed, but there is not one specified in the schema or model. + class UnknownPrimaryKey < ActiveRecordError + attr_reader :model + + def initialize(model) + @model = model + end + + def message + "Unknown primary key for table #{model.table_name} in model #{model}." + end + end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6f1ec7f9b3..cad9417216 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -842,9 +842,12 @@ module ActiveRecord @loaded_fixtures = load_fixtures @@already_loaded_fixtures[self.class] = @loaded_fixtures end - ActiveRecord::Base.connection.increment_open_transactions - ActiveRecord::Base.connection.transaction_joinable = false - ActiveRecord::Base.connection.begin_db_transaction + @fixture_connections = enlist_fixture_connections + @fixture_connections.each do |connection| + connection.increment_open_transactions + connection.transaction_joinable = false + connection.begin_db_transaction + end # Load fixtures for every test. else ActiveRecord::Fixtures.reset_cache @@ -864,13 +867,22 @@ module ActiveRecord end # Rollback changes if a transaction is active. - if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0 - ActiveRecord::Base.connection.rollback_db_transaction - ActiveRecord::Base.connection.decrement_open_transactions + if run_in_transaction? + @fixture_connections.each do |connection| + if connection.open_transactions != 0 + connection.rollback_db_transaction + connection.decrement_open_transactions + end + end + @fixture_connections.clear end ActiveRecord::Base.clear_active_connections! end + def enlist_fixture_connections + ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection) + end + private def load_fixtures fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index d9ad7e4132..2df3309648 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -103,7 +103,7 @@ module ActiveRecord affected_rows = connection.update stmt unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError.new(self, "update") end affected_rows @@ -127,7 +127,7 @@ module ActiveRecord affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError.new(self, "destroy") end end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 10c0dc6f2a..466d148901 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -28,9 +28,10 @@ module ActiveRecord end class BodyProxy # :nodoc: - def initialize(original_cache_value, target) + def initialize(original_cache_value, target, connection_id) @original_cache_value = original_cache_value @target = target + @connection_id = connection_id end def method_missing(method_sym, *arguments, &block) @@ -48,6 +49,7 @@ module ActiveRecord def close @target.close if @target.respond_to?(:close) ensure + ActiveRecord::Base.connection_id = @connection_id ActiveRecord::Base.connection.clear_query_cache unless @original_cache_value ActiveRecord::Base.connection.disable_query_cache! @@ -60,7 +62,7 @@ module ActiveRecord ActiveRecord::Base.connection.enable_query_cache! status, headers, body = @app.call(env) - [status, headers, BodyProxy.new(old, body)] + [status, headers, BodyProxy.new(old, body, ActiveRecord::Base.connection_id)] rescue Exception => e ActiveRecord::Base.connection.clear_query_cache unless old diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b3316fd1a2..4fb19b14ea 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -288,7 +288,7 @@ db_namespace = namespace :db do pending_migrations.each do |pending_migration| puts ' %4d %s' % [pending_migration.version, pending_migration.name] end - abort %{Run "rake db:migrate" to update your database then try again.} + abort %{Run `rake db:migrate` to update your database then try again.} end end end @@ -341,7 +341,7 @@ db_namespace = namespace :db do namespace :schema do desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' - task :dump => :load_config do + task :dump => [:environment, :load_config] do require 'active_record/schema_dumper' filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" File.open(filename, "w:utf-8") do |file| @@ -357,7 +357,7 @@ db_namespace = namespace :db do if File.exists?(file) load(file) else - abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} + abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} end end end @@ -424,10 +424,10 @@ db_namespace = namespace :db do ENV['PGHOST'] = abcs['test']['host'] if abcs['test']['host'] ENV['PGPORT'] = abcs['test']['port'].to_s if abcs['test']['port'] ENV['PGPASSWORD'] = abcs['test']['password'].to_s if abcs['test']['password'] - `psql -U "#{abcs['test']['username']}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs['test']['database']} #{abcs['test']['template']}` + `psql -U "#{abcs['test']['username']}" -f "#{Rails.root}/db/#{Rails.env}_structure.sql" #{abcs['test']['database']} #{abcs['test']['template']}` when /sqlite/ dbfile = abcs['test']['database'] || abcs['test']['dbfile'] - `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` + `sqlite3 #{dbfile} < "#{Rails.root}/db/#{Rails.env}_structure.sql"` when 'sqlserver' `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` when 'oci', 'oracle' diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 1929a808ed..5285060288 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -212,12 +212,12 @@ module ActiveRecord end # klass option is necessary to support loading polymorphic associations - def association_primary_key(klass = self.klass) - options[:primary_key] || klass.primary_key + def association_primary_key(klass = nil) + options[:primary_key] || primary_key(klass || self.klass) end def active_record_primary_key - @active_record_primary_key ||= options[:primary_key] || active_record.primary_key + @active_record_primary_key ||= options[:primary_key] || primary_key(active_record) end def counter_cache_column @@ -357,6 +357,10 @@ module ActiveRecord active_record.name.foreign_key end end + + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + end end # Holds all the meta-data about a :through association as it was specified @@ -461,7 +465,7 @@ module ActiveRecord # We want to use the klass from this reflection, rather than just delegate straight to # the source_reflection, because the source_reflection may be polymorphic. We still # need to respect the source_reflection's :primary_key option, though. - def association_primary_key(klass = self.klass) + def association_primary_key(klass = nil) # Get the "actual" source reflection if the immediate source reflection has a # source reflection itself source_reflection = self.source_reflection @@ -469,7 +473,7 @@ module ActiveRecord source_reflection = source_reflection.source_reflection end - source_reflection.options[:primary_key] || klass.primary_key + source_reflection.options[:primary_key] || primary_key(klass || self.klass) end # Gets an array of possible <tt>:through</tt> source reflection names: diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb new file mode 100644 index 0000000000..d5910df891 --- /dev/null +++ b/activerecord/lib/active_record/store.rb @@ -0,0 +1,49 @@ +module ActiveRecord + # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. + # It's like a simple key/value store backed into your record when you don't care about being able to + # query that store outside the context of a single record. + # + # You can then declare accessors to this store that are then accessible just like any other attribute + # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's + # already built around just accessing attributes on the model. + # + # Make sure that you declare the database column used for the serialized store as a text, so there's + # plenty of room. + # + # Examples: + # + # class User < ActiveRecord::Base + # store :settings, accessors: [ :color, :homepage ] + # end + # + # u = User.new(color: 'black', homepage: '37signals.com') + # u.color # Accessor stored attribute + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # Add additional accessors to an existing store through store_accessor + # class SuperUser < User + # store_accessor :settings, :privileges, :servants + # end + module Store + extend ActiveSupport::Concern + + module ClassMethods + def store(store_attribute, options = {}) + serialize store_attribute, Hash + store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors + end + + def store_accessor(store_attribute, *keys) + Array(keys).flatten.each do |key| + define_method("#{key}=") do |value| + send(store_attribute)[key] = value + end + + define_method(key) do + send(store_attribute)[key] + end + end + end + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 484b1d369b..2e2ea8c42b 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -57,8 +57,8 @@ module ActiveRecord value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? if !options[:case_sensitive] && value && column.text? - # will use SQL LOWER function before comparison - relation = table[attribute].lower.eq(table.lower(value)) + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + relation = klass.connection.case_insensitive_comparison(table, attribute, column, value) else value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) |