From fbebdb0c091c37b0bc75ab774d187d8bc8795bd2 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Fri, 2 May 2008 00:00:42 +0100 Subject: Ensure correct record is returned when preloading has_one where more than one row exists Signed-off-by: Michael Koziarski [#73 state:closed] --- activerecord/lib/active_record/association_preload.rb | 7 ++++++- activerecord/test/cases/associations/eager_test.rb | 4 ++++ activerecord/test/models/post.rb | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 3e7c787dee..da4ebdef51 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -65,7 +65,13 @@ module ActiveRecord end def set_association_single_records(id_to_record_map, reflection_name, associated_records, key) + seen_keys = {} associated_records.each do |associated_record| + #this is a has_one or belongs_to: there should only be one record. + #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please + # only one row per distinct foo_id' so this where we enforce that + next if seen_keys[associated_record[key].to_s] + seen_keys[associated_record[key].to_s] = true mapped_records = id_to_record_map[associated_record[key].to_s] mapped_records.each do |mapped_record| mapped_record.send("set_#{reflection_name}_target", associated_record) @@ -122,7 +128,6 @@ module ActiveRecord else records.each {|record| record.send("set_#{reflection.name}_target", nil)} - set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name) end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 546ed80894..67b57ceb42 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -29,6 +29,10 @@ class EagerAssociationTest < ActiveRecord::TestCase post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'") assert_equal 2, post.comments.size assert post.comments.include?(comments(:greetings)) + + posts = Post.find(:all, :include => :last_comment) + post = posts.find { |p| p.id == 1 } + assert_equal Post.find(1).last_comment, post.last_comment end def test_loading_conditions_with_or diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 22c5a645b8..d9101706b5 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -9,6 +9,8 @@ class Post < ActiveRecord::Base belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts + has_one :last_comment, :class_name => 'Comment', :order => 'id desc' + has_many :comments, :order => "body" do def find_most_recent find(:first, :order => "id DESC") -- cgit v1.2.3 From 8ded457b1b31b157d6fe89b553749579e5ac4a27 Mon Sep 17 00:00:00 2001 From: John Devine Date: Sat, 3 May 2008 22:49:18 -0500 Subject: Added logic to associations.rb to make sure select_for_limited_ids includes joins that are needed to reach tables listed in the :order or :conditions options if they are not joined directly to the main active_record table. Signed-off-by: Michael Koziarski [#109 state:resolved] --- activerecord/lib/active_record/associations.rb | 25 ++++++++++++++++++++++++- activerecord/test/cases/finder_test.rb | 9 +++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) mode change 100755 => 100644 activerecord/lib/active_record/associations.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb old mode 100755 new mode 100644 index 7d27b0607a..0809b271d7 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1446,6 +1446,12 @@ module ActiveRecord tables_from_conditions = conditions_tables(options) tables_from_order = order_tables(options) all_tables = tables_from_conditions + tables_from_order + distinct_join_associations = all_tables.uniq.map{|table| + join_dependency.joins_for_table_name(table) + }.flatten.compact.uniq + + + is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order) sql = "SELECT " @@ -1457,7 +1463,7 @@ module ActiveRecord sql << " FROM #{connection.quote_table_name table_name} " if is_distinct - sql << join_dependency.join_associations.reject{ |ja| !all_tables.include?(ja.table_name) }.collect(&:association_join).join + sql << distinct_join_associations.collect(&:association_join).join add_joins!(sql, options, scope) end @@ -1617,6 +1623,23 @@ module ActiveRecord end end + def join_for_table_name(table_name) + @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil + end + + def joins_for_table_name(table_name) + join = join_for_table_name(table_name) + result = nil + if join && join.is_a?(JoinAssociation) + result = [join] + if join.parent && join.parent.is_a?(JoinAssociation) + result = joins_for_table_name(join.parent.aliased_table_name) + + result + end + end + result + end + protected def build(associations, parent = nil) parent ||= @joins.last diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index b7f87fe6e8..2acfe9b387 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -8,6 +8,7 @@ require 'models/entrant' require 'models/developer' require 'models/post' require 'models/customer' +require 'models/job' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers @@ -857,6 +858,14 @@ class FinderTest < ActiveRecord::TestCase Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}} end + def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct + assert_equal 2, Post.find(:all,:include=>{:authors=>:author_address},:order=>' author_addresses.id DESC ', :limit=>2).size + + assert_equal 3, Post.find(:all,:include=>{:author=>:author_address,:authors=>:author_address}, + :order=>' author_addresses_authors.id DESC ', :limit=>3).size + end + + protected def bind(statement, *vars) if vars.first.is_a?(Hash) -- cgit v1.2.3 From 8877ab5852d9a1133eb9a730ae47dde214bafe55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarmo=20T=C3=A4nav?= Date: Wed, 7 May 2008 02:08:23 +0300 Subject: Added AbstractAdapter#table_exists? and made AbstractAdapter#table implementation non-optional Signed-off-by: Michael Koziarski --- activerecord/lib/active_record/base.rb | 13 +- .../abstract/schema_statements.rb | 4 + activerecord/test/cases/adapter_test.rb | 19 +- activerecord/test/cases/schema_dumper_test.rb | 211 ++++++++++----------- 4 files changed, 119 insertions(+), 128 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ffefc3cef3..12234184c1 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1118,18 +1118,7 @@ module ActiveRecord #:nodoc: # Indicates whether the table associated with this class exists def table_exists? - if connection.respond_to?(:tables) - connection.tables.include? table_name - else - # if the connection adapter hasn't implemented tables, there are two crude tests that can be - # used - see if getting column info raises an error, or if the number of columns returned is zero - begin - reset_column_information - columns.size > 0 - rescue ActiveRecord::StatementInvalid - false - end - end + connection.table_exists?(table_name) end # Returns an array of column objects for the table associated with this class. 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 b556516572..e2b8896d42 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -20,6 +20,10 @@ module ActiveRecord # def tables(name = nil) end + def table_exists?(table_name) + tables.include?(table_name.to_s) + end + # Returns an array of indexes for the given table. # def indexes(table_name, name = nil) end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 8a74b6d6f5..91504af901 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -6,15 +6,16 @@ class AdapterTest < ActiveRecord::TestCase end def test_tables - if @connection.respond_to?(:tables) - tables = @connection.tables - assert tables.include?("accounts") - assert tables.include?("authors") - assert tables.include?("tasks") - assert tables.include?("topics") - else - warn "#{@connection.class} does not respond to #tables" - end + tables = @connection.tables + assert tables.include?("accounts") + assert tables.include?("authors") + assert tables.include?("tasks") + assert tables.include?("topics") + end + + def test_table_exists? + assert @connection.table_exists?("accounts") + assert !@connection.table_exists?("nonexistingtable") end def test_indexes diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index ba8bff3b44..c42b0efba0 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -2,140 +2,137 @@ require "cases/helper" require 'active_record/schema_dumper' require 'stringio' -if ActiveRecord::Base.connection.respond_to?(:tables) - class SchemaDumperTest < ActiveRecord::TestCase - def standard_dump - stream = StringIO.new - ActiveRecord::SchemaDumper.ignore_tables = [] - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - stream.string - end +class SchemaDumperTest < ActiveRecord::TestCase + def standard_dump + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + stream.string + end - def test_schema_dump - output = standard_dump - assert_match %r{create_table "accounts"}, output - assert_match %r{create_table "authors"}, output - assert_no_match %r{create_table "schema_migrations"}, output - end + def test_schema_dump + output = standard_dump + assert_match %r{create_table "accounts"}, output + assert_match %r{create_table "authors"}, output + assert_no_match %r{create_table "schema_migrations"}, output + end - def test_schema_dump_excludes_sqlite_sequence - output = standard_dump - assert_no_match %r{create_table "sqlite_sequence"}, output - end + def test_schema_dump_excludes_sqlite_sequence + output = standard_dump + assert_no_match %r{create_table "sqlite_sequence"}, output + end - def assert_line_up(lines, pattern, required = false) - return assert(true) if lines.empty? - matches = lines.map { |line| line.match(pattern) } - assert matches.all? if required - matches.compact! - return assert(true) if matches.empty? - assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length - end + def assert_line_up(lines, pattern, required = false) + return assert(true) if lines.empty? + matches = lines.map { |line| line.match(pattern) } + assert matches.all? if required + matches.compact! + return assert(true) if matches.empty? + assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length + end - def column_definition_lines(output = standard_dump) - output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) } - end + def column_definition_lines(output = standard_dump) + output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) } + end - def test_types_line_up - column_definition_lines.each do |column_set| - next if column_set.empty? + def test_types_line_up + column_definition_lines.each do |column_set| + next if column_set.empty? - lengths = column_set.map do |column| - if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/) - match[0].length - end + lengths = column_set.map do |column| + if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/) + match[0].length end - - assert_equal 1, lengths.uniq.length end - end - def test_arguments_line_up - column_definition_lines.each do |column_set| - assert_line_up(column_set, /:default => /) - assert_line_up(column_set, /:limit => /) - assert_line_up(column_set, /:null => /) - end + assert_equal 1, lengths.uniq.length end + end - def test_no_dump_errors - output = standard_dump - assert_no_match %r{\# Could not dump table}, output + def test_arguments_line_up + column_definition_lines.each do |column_set| + assert_line_up(column_set, /:default => /) + assert_line_up(column_set, /:limit => /) + assert_line_up(column_set, /:null => /) end + end - def test_schema_dump_includes_not_null_columns - stream = StringIO.new + def test_no_dump_errors + output = standard_dump + assert_no_match %r{\# Could not dump table}, output + end - ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/] - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - output = stream.string - assert_match %r{:null => false}, output - end + def test_schema_dump_includes_not_null_columns + stream = StringIO.new - def test_schema_dump_with_string_ignored_table - stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_match %r{:null => false}, output + end - ActiveRecord::SchemaDumper.ignore_tables = ['accounts'] - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - output = stream.string - assert_no_match %r{create_table "accounts"}, output - assert_match %r{create_table "authors"}, output - assert_no_match %r{create_table "schema_migrations"}, output - end + def test_schema_dump_with_string_ignored_table + stream = StringIO.new + + ActiveRecord::SchemaDumper.ignore_tables = ['accounts'] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_no_match %r{create_table "accounts"}, output + assert_match %r{create_table "authors"}, output + assert_no_match %r{create_table "schema_migrations"}, output + end + + def test_schema_dump_with_regexp_ignored_table + stream = StringIO.new - def test_schema_dump_with_regexp_ignored_table - stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [/^account/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_no_match %r{create_table "accounts"}, output + assert_match %r{create_table "authors"}, output + assert_no_match %r{create_table "schema_migrations"}, output + end - ActiveRecord::SchemaDumper.ignore_tables = [/^account/] + def test_schema_dump_illegal_ignored_table_value + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [5] + assert_raise(StandardError) do ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - output = stream.string - assert_no_match %r{create_table "accounts"}, output - assert_match %r{create_table "authors"}, output - assert_no_match %r{create_table "schema_migrations"}, output end + end - def test_schema_dump_illegal_ignored_table_value - stream = StringIO.new - ActiveRecord::SchemaDumper.ignore_tables = [5] - assert_raise(StandardError) do - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - end + if current_adapter?(:MysqlAdapter) + def test_schema_dump_should_not_add_default_value_for_mysql_text_field + output = standard_dump + assert_match %r{t.text\s+"body",\s+:null => false$}, output end - if current_adapter?(:MysqlAdapter) - def test_schema_dump_should_not_add_default_value_for_mysql_text_field - output = standard_dump - assert_match %r{t.text\s+"body",\s+:null => false$}, output - end - - def test_mysql_schema_dump_should_honor_nonstandard_primary_keys - output = standard_dump - match = output.match(%r{create_table "movies"(.*)do}) - assert_not_nil(match, "nonstandardpk table not found") - assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved" - end - - def test_schema_dump_includes_length_for_mysql_blob_and_text_fields - output = standard_dump - assert_match %r{t.binary\s+"tiny_blob",\s+:limit => 255$}, output - assert_match %r{t.binary\s+"normal_blob"$}, output - assert_match %r{t.binary\s+"medium_blob",\s+:limit => 16777215$}, output - assert_match %r{t.binary\s+"long_blob",\s+:limit => 2147483647$}, output - assert_match %r{t.text\s+"tiny_text",\s+:limit => 255$}, output - assert_match %r{t.text\s+"normal_text"$}, output - assert_match %r{t.text\s+"medium_text",\s+:limit => 16777215$}, output - assert_match %r{t.text\s+"long_text",\s+:limit => 2147483647$}, output - end + def test_mysql_schema_dump_should_honor_nonstandard_primary_keys + output = standard_dump + match = output.match(%r{create_table "movies"(.*)do}) + assert_not_nil(match, "nonstandardpk table not found") + assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved" end - def test_schema_dump_includes_decimal_options - stream = StringIO.new - ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/] - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - output = stream.string - assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output + def test_schema_dump_includes_length_for_mysql_blob_and_text_fields + output = standard_dump + assert_match %r{t.binary\s+"tiny_blob",\s+:limit => 255$}, output + assert_match %r{t.binary\s+"normal_blob"$}, output + assert_match %r{t.binary\s+"medium_blob",\s+:limit => 16777215$}, output + assert_match %r{t.binary\s+"long_blob",\s+:limit => 2147483647$}, output + assert_match %r{t.text\s+"tiny_text",\s+:limit => 255$}, output + assert_match %r{t.text\s+"normal_text"$}, output + assert_match %r{t.text\s+"medium_text",\s+:limit => 16777215$}, output + assert_match %r{t.text\s+"long_text",\s+:limit => 2147483647$}, output end end + def test_schema_dump_includes_decimal_options + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output + end end -- cgit v1.2.3 From 0a21193dc660396fb993b06d1d3c168a9cd900a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarmo=20T=C3=A4nav?= Date: Wed, 7 May 2008 02:08:57 +0300 Subject: create_table :force => true no longer tries to drop a non-existing table Signed-off-by: Michael Koziarski --- .../connection_adapters/abstract/schema_statements.rb | 4 ++-- activerecord/test/cases/migration_test.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'activerecord') 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 e2b8896d42..1594be40e2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -97,8 +97,8 @@ module ActiveRecord yield table_definition - if options[:force] - drop_table(table_name, options) rescue nil + if options[:force] && table_exists?(table_name) + drop_table(table_name, options) end create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index d4e81827aa..6be31b5f86 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -209,6 +209,24 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Base.primary_key_prefix_type = nil end + uses_mocha('test_create_table_with_force_true_does_not_drop_nonexisting_table') do + def test_create_table_with_force_true_does_not_drop_nonexisting_table + if Person.connection.table_exists?(:testings2) + Person.connection.drop_table :testings2 + end + + # using a copy as we need the drop_table method to + # continue to work for the ensure block of the test + temp_conn = Person.connection.dup + temp_conn.expects(:drop_table).never + temp_conn.create_table :testings2, :force => true do |t| + t.column :foo, :string + end + ensure + Person.connection.drop_table :testings2 rescue nil + end + end + # SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL # column to a table without a default value. -- cgit v1.2.3 From bcb090c56b842a76397e0ea32f54c942fd11910e Mon Sep 17 00:00:00 2001 From: Andreas Neuhaus Date: Thu, 8 May 2008 00:04:53 -0500 Subject: Calling ActiveRecord#inspect on an unloaded association won't wipe the collection [#9 state:resolved] Signed-off-by: Joshua Peek --- activerecord/lib/active_record/associations/association_proxy.rb | 4 ++-- activerecord/test/cases/associations_test.rb | 6 ++++++ activerecord/test/models/developer.rb | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index c415ad2df3..68503a3c40 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -118,7 +118,7 @@ module ActiveRecord end def inspect - reload unless loaded? + load_target @target.inspect end @@ -167,7 +167,7 @@ module ActiveRecord def with_scope(*args, &block) @reflection.klass.send :with_scope, *args, &block end - + private def method_missing(method, *args) if load_target diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index ed2fab6d22..d8fe98bf57 100755 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -149,6 +149,12 @@ class AssociationProxyTest < ActiveRecord::TestCase assert !david.projects.loaded? end + def test_inspect_does_not_reload_a_not_yet_loaded_target + andreas = Developer.new :name => 'Andreas', :log => 'new developer added' + assert !andreas.audit_logs.loaded? + assert_match(/message: "new developer added"/, andreas.audit_logs.inspect) + end + def test_save_on_parent_saves_children developer = Developer.create :name => "Bryan", :salary => 50_000 assert_equal 1, developer.reload.audit_logs.size diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 192c2cb5ab..f77fd0e96d 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -49,6 +49,10 @@ class Developer < ActiveRecord::Base before_create do |developer| developer.audit_logs.build :message => "Computer created" end + + def log=(message) + audit_logs.build :message => message + end end class AuditLog < ActiveRecord::Base -- cgit v1.2.3 From eb5b93be74ed3eca925c1ab9bd4739919ae39a41 Mon Sep 17 00:00:00 2001 From: Scott Fleckenstein Date: Wed, 7 May 2008 06:54:07 -0700 Subject: Fix Time.zone.parse from stripping time zone information and make Time aware attribute methods use Time.zone.parse instead of to_time --- .../lib/active_record/attribute_methods.rb | 2 +- activerecord/test/cases/attribute_methods_test.rb | 27 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 46ecfc1969..973b761546 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -180,7 +180,7 @@ module ActiveRecord method_body = <<-EOV def #{attr_name}=(time) if time - time = time.to_time rescue time unless time.acts_like?(:time) + time = Time.zone.parse(time) rescue time unless time.acts_like?(:time) time = time.in_time_zone if time.acts_like?(:time) end write_attribute(:#{attr_name}, time) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 61a049ab36..ab9abf27f9 100755 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -173,6 +173,33 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_setting_time_zone_aware_attribute_with_string + utc_time = Time.utc(2008, 1, 1) + (-11..13).each do |timezone_offset| + time_string = utc_time.in_time_zone(timezone_offset).to_s + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new + record.written_on = time_string + assert_equal Time.zone.parse(time_string), record.written_on + assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone + assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time + end + end + end + + def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone + time_string = 'Tue Jan 01 00:00:00 2008' + (-11..13).each do |timezone_offset| + in_time_zone timezone_offset do + record = @target.new + record.written_on = time_string + assert_equal Time.zone.parse(time_string), record.written_on + assert_equal TimeZone[timezone_offset], record.written_on.time_zone + assert_equal Time.utc(2008, 1, 1), record.written_on.time + end + end + end + def test_setting_time_zone_aware_attribute_in_current_time_zone utc_time = Time.utc(2008, 1, 1) in_time_zone "Pacific Time (US & Canada)" do -- cgit v1.2.3 From 328fada610aa9128386bc4b372d3e0b68aede945 Mon Sep 17 00:00:00 2001 From: gbuesing Date: Thu, 8 May 2008 20:31:54 -0500 Subject: ActiveRecord time zone aware attributes: blank string is treated as nil when assigned to writer --- activerecord/lib/active_record/attribute_methods.rb | 6 +++--- activerecord/test/cases/attribute_methods_test.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 973b761546..fde98bf19f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -179,10 +179,10 @@ module ActiveRecord def define_write_method_for_time_zone_conversion(attr_name) method_body = <<-EOV def #{attr_name}=(time) - if time - time = Time.zone.parse(time) rescue time unless time.acts_like?(:time) - time = time.in_time_zone if time.acts_like?(:time) + unless time.blank? || time.acts_like?(:time) + time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time end + time = time.in_time_zone rescue nil if time write_attribute(:#{attr_name}, time) end EOV diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ab9abf27f9..c336fd9afb 100755 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -186,6 +186,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end end + + def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new + record.written_on = ' ' + assert_nil record.written_on + end + end def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone time_string = 'Tue Jan 01 00:00:00 2008' -- cgit v1.2.3 From 06a7c2948a8dbf31357b552d468fcf42002736e7 Mon Sep 17 00:00:00 2001 From: gbuesing Date: Thu, 8 May 2008 21:30:17 -0500 Subject: Time.zone.parse: return nil for strings with no date information --- activerecord/lib/active_record/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index fde98bf19f..2db27226f2 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -179,7 +179,7 @@ module ActiveRecord def define_write_method_for_time_zone_conversion(attr_name) method_body = <<-EOV def #{attr_name}=(time) - unless time.blank? || time.acts_like?(:time) + unless time.acts_like?(:time) time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time end time = time.in_time_zone rescue nil if time -- cgit v1.2.3 From 618d695f1115c00ce058950af199d5d4dc06385a Mon Sep 17 00:00:00 2001 From: gbuesing Date: Thu, 8 May 2008 21:58:37 -0500 Subject: Updating changelogs --- activerecord/CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 04cf72b38c..f223e1f67f 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. [Scott Fleckenstein, Geoff Buesing] + * Added change_table for migrations (Jeff Dean) [#71]. Example: change_table :videos do |t| -- cgit v1.2.3 From 79e44a5ee484f977c2cad8a4cffe882b816340a1 Mon Sep 17 00:00:00 2001 From: gbuesing Date: Thu, 8 May 2008 22:21:51 -0500 Subject: Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/base.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index f223e1f67f..597b876f22 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true [Geoff Buesing] + * Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. [Scott Fleckenstein, Geoff Buesing] * Added change_table for migrations (Jeff Dean) [#71]. Example: diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 12234184c1..b600e8ecfd 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2571,7 +2571,7 @@ module ActiveRecord #:nodoc: end def instantiate_time_object(name, values) - if Time.zone && self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym) + if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym) Time.zone.local(*values) else Time.time_with_datetime_fallback(@@default_timezone, *values) -- cgit v1.2.3 From dc4eec1129520ce9863c9373d7cb79d8636ab7ca Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 9 May 2008 10:38:02 +0100 Subject: Merge docrails: commit e6afd8b2736364322b673bbdcca3e9b38b6d3da0 Author: Xavier Noria Date: Thu May 8 23:49:36 2008 +0200 Overall documentation improvement and markup corrections. Zillion changes. commit 2fead68b3192332eee27945ed95a94a64ca73f70 Author: Austin Putman Date: Wed May 7 19:35:46 2008 -0700 Documented class methods on ActionController::Routing. These are dangerous, and mostly used for testing. commit f5b84182dbc39bea79c8ee319c688d00fa99f9d1 Author: Teflon Ted Date: Wed May 7 16:08:49 2008 -0400 Added explanation about errant inflections not being patched in the future in order to avoid breaking legacy applications. commit 370f4f51722cec49ace17093d29e9ce9e8f15cfb Author: Sunny Ripert Date: Wed May 7 14:00:59 2008 +0200 Applied list conventions in AR::Base commit 5bd18429f09d44e75191bec42a6db04bd33f3030 Author: Sunny Ripert Date: Wed May 7 13:53:35 2008 +0200 Renamed Options list to Attributes list whenever they weren't option hashes in AR::Base commit d912bd5672316454457ae83f6e9dda5197beeb6f Author: Yaroslav Markin Date: Wed May 7 13:50:28 2008 +0400 Add a filter_parameter_logging usage hint to generated ApplicationController. This may help to remind the developer to filter sensitive information from application logs. Closes #11578 commit b243de0db3c2605121e055079854af5090d06374 Author: Jack Danger Canty Date: Tue May 6 23:39:47 2008 -0700 doc: disambiguating an example ActiveRecord class commit f81d771f0657ae8375b84a77a059812cce5d6fd9 Author: Jack Danger Canty Date: Tue May 6 23:35:05 2008 -0700 doc: ActiveRecord::Reflection::AssociationReflection#through_reflection Added documentation demonstrating the use of #through_reflection for finding intervening reflection objects for HasManyThrough and HasOneThrough. commit ae6b46f00b5b8b2939c6b37ce3329c83de7e71db Author: Cheah Chu Yeow Date: Wed May 7 13:47:41 2008 +0800 Document AttributeAssignmentError and MultiparameterAssignmentErrors. commit 8f463550b597db2156b67733f31aed13487fbc3a Author: John Barnette Date: Tue May 6 22:46:44 2008 -0700 Killing/fixing a bunch of outdated language in the AR README. commit aca44bcd92ef783abdf484b58abdde6786db0f89 Author: Cheah Chu Yeow Date: Wed May 7 13:34:52 2008 +0800 Make a note about ActiveResource::Timeouterror being raised when ARes calls timeout. commit 284a930a93fbee16e25d06392779dbf2f03e9e12 Author: Jonathan Dance Date: Tue May 6 14:58:26 2008 -0400 improvements to the page caching docs commit 9482da621390c874da7c921c8bd6230caae7035a Author: Sunny Ripert Date: Mon May 5 18:13:40 2008 +0200 validates_numericality_of() "integer" option really is "only_integer" commit e9afd6790a8f530528f6597a7f59bb283be754f6 Author: Sunny Ripert Date: Mon May 5 12:11:59 2008 +0200 Harmonized hash notation in AR::Base commit 67ebf14a91ffd970b582be4ff2991d691a9cf3e1 Author: Sunny Ripert Date: Mon May 5 12:06:19 2008 +0200 Turned options into rdoc-lists in AR::Base commit 0ec7c0a41d889d4e5382b9dff72f1aaba89bf297 Author: Marshall Huss Date: Sun May 4 23:21:33 2008 -0400 Added information of how to set element_name in the case the user has a name confliction with an existing model Signed-off-by: Pratik Naik --- activerecord/README | 24 +- activerecord/lib/active_record/associations.rb | 275 +++++++++++---------- activerecord/lib/active_record/base.rb | 155 ++++++------ .../connection_adapters/abstract/query_cache.rb | 6 + .../connection_adapters/abstract_adapter.rb | 4 +- .../connection_adapters/mysql_adapter.rb | 24 +- .../connection_adapters/postgresql_adapter.rb | 18 +- .../connection_adapters/sqlite_adapter.rb | 2 +- activerecord/lib/active_record/fixtures.rb | 2 +- activerecord/lib/active_record/reflection.rb | 14 +- activerecord/lib/active_record/validations.rb | 8 +- 11 files changed, 279 insertions(+), 253 deletions(-) (limited to 'activerecord') diff --git a/activerecord/README b/activerecord/README index ff3f55ee8a..d68eb28a64 100755 --- a/activerecord/README +++ b/activerecord/README @@ -169,10 +169,10 @@ A short rundown of the major features: class AddSystemSettings < ActiveRecord::Migration def self.up create_table :system_settings do |t| - t.string :name - t.string :label - t.text :value - t.string :type + t.string :name + t.string :label + t.text :value + t.string :type t.integer :position end @@ -289,16 +289,6 @@ Bi-directional associations thanks to the "belongs_to" macro thirty_seven_signals.firm.nil? # true -== Examples - -Active Record ships with a couple of examples that should give you a good feel for -operating usage. Be sure to edit the examples/shared_setup.rb file for your -own database before running the examples. Possibly also the table definition SQL in -the examples themselves. - -It's also highly recommended to have a look at the unit tests. Read more in link:files/RUNNING_UNIT_TESTS.html - - == Philosophy Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is @@ -336,7 +326,7 @@ then use: % [sudo] gem install activerecord-1.10.0.gem -You can also install Active Record the old-fashion way with the following command: +You can also install Active Record the old-fashioned way with the following command: % [sudo] ruby install.rb @@ -357,5 +347,5 @@ RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Ra remember to update the corresponding unit tests. If fact, I prefer new feature to be submitted in the form of new unit tests. -For other information, feel free to ask on the ruby-talk mailing list -(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. +For other information, feel free to ask on the rubyonrails-talk +(http://groups.google.com/group/rubyonrails-talk) mailing list. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0809b271d7..fb5f1f8a8c 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -110,7 +110,7 @@ module ActiveRecord # # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association # adds a method with that name to its model, it will override the inherited method and break things. - # For instance, #attributes and #connection would be bad choices for association names. + # For instance, +attributes+ and +connection+ would be bad choices for association names. # # == Auto-generated methods # @@ -258,7 +258,7 @@ module ActiveRecord # order to update their primary keys - except if the parent object is unsaved (new_record? == true). # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment # is cancelled. - # * If you wish to assign an object to a +has_one+ association without saving it, use the #association.build method (documented below). + # * If you wish to assign an object to a +has_one+ association without saving it, use the association.build method (documented below). # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It # does not save the parent either. # @@ -266,8 +266,8 @@ module ActiveRecord # # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object # (the owner of the collection) is not yet stored in the database. - # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns +false+. - # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below). + # * If saving any of the objects being added to a collection (via push or similar) fails, then push returns +false+. + # * You can add an object to a collection without automatically saving it by using the collection.build method (documented below). # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved. # # === Association callbacks @@ -504,8 +504,8 @@ module ActiveRecord # # Address.find(:all, :include => :addressable) # INVALID # - # will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model's type - # is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that early query. + # will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model's type + # is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that early query. # # In versions greater than 2.0.2 eager loading in polymorphic associations is supported # thanks to a change in the overall preloading strategy. @@ -575,7 +575,7 @@ module ActiveRecord # end # end # - # When Firm#clients is called, it will in turn call MyApplication::Business::Company.find(firm.id). If you want to associate + # When Firm#clients is called, it will in turn call MyApplication::Business::Company.find(firm.id). If you want to associate # with a class in another module scope, this can be done by specifying the complete class name. Example: # # module MyApplication @@ -603,28 +603,28 @@ module ActiveRecord # Adds the following methods for retrieval and query of collections of associated objects: # +collection+ is replaced with the symbol passed as the first argument, so # has_many :clients would add among others clients.empty?. - # * collection(force_reload = false) - returns an array of all the associated objects. + # * collection(force_reload = false) - Returns an array of all the associated objects. # An empty array is returned if none are found. - # * collection<<(object, ...) - adds one or more objects to the collection by setting their foreign keys to the collection's primary key. - # * collection.delete(object, ...) - removes one or more objects from the collection by setting their foreign keys to NULL. + # * collection<<(object, ...) - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # * collection.delete(object, ...) - Removes one or more objects from the collection by setting their foreign keys to +NULL+. # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model. - # * collection=objects - replaces the collections content by deleting and adding objects as appropriate. - # * collection_singular_ids - returns an array of the associated objects' ids - # * collection_singular_ids=ids - replace the collection with the objects identified by the primary keys in +ids+ - # * collection.clear - removes every object from the collection. This destroys the associated objects if they + # * collection=objects - Replaces the collections content by deleting and adding objects as appropriate. + # * collection_singular_ids - Returns an array of the associated objects' ids + # * collection_singular_ids=ids - Replace the collection with the objects identified by the primary keys in +ids+ + # * collection.clear - Removes every object from the collection. This destroys the associated objects if they # are associated with :dependent => :destroy, deletes them directly from the database if :dependent => :delete_all, - # otherwise sets their foreign keys to NULL. - # * collection.empty? - returns +true+ if there are no associated objects. - # * collection.size - returns the number of associated objects. - # * collection.find - finds an associated object according to the same rules as Base.find. - # * collection.build(attributes = {}, ...) - returns one or more new objects of the collection type that have been instantiated + # otherwise sets their foreign keys to +NULL+. + # * collection.empty? - Returns +true+ if there are no associated objects. + # * collection.size - Returns the number of associated objects. + # * collection.find - Finds an associated object according to the same rules as Base.find. + # * collection.build(attributes = {}, ...) - Returns one or more new objects of the collection type that have been instantiated # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an # associated object already exists, not if it's +nil+! - # * collection.create(attributes = {}) - returns a new object of the collection type that has been instantiated + # * collection.create(attributes = {}) - Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). # *Note:* This only works if an associated object already exists, not if it's +nil+! # - # Example: A +Firm+ class declares has_many :clients, which will add: + # Example: A Firm class declares has_many :clients, which will add: # * Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}") # * Firm#clients<< # * Firm#clients.delete @@ -640,45 +640,45 @@ module ActiveRecord # The declaration can also include an options hash to specialize the behavior of the association. # # Options are: - # * :class_name - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So has_many :products will by default be linked to the +Product+ class, but - # if the real class name is +SpecialProduct+, you'll have to specify it with this option. - # * :conditions - specify the conditions that the associated objects must meet in order to be included as a +WHERE+ + # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_many :products will by default be linked to the Product class, but + # if the real class name is SpecialProduct, you'll have to specify it with this option. + # * :conditions - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+ # SQL fragment, such as price > 5 AND name LIKE 'B%'. Record creations from the association are scoped if a hash # is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create # or @blog.posts.build. - # * :order - specify the order in which the associated objects are returned as an ORDER BY SQL fragment, - # such as last_name, first_name DESC - # * :foreign_key - specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_many+ association will use +person_id+ - # as the default +foreign_key+. - # * :dependent - if set to :destroy all the associated objects are destroyed - # alongside this object by calling their destroy method. If set to :delete_all all associated - # objects are deleted *without* calling their destroy method. If set to :nullify all associated - # objects' foreign keys are set to +NULL+ *without* calling their save callbacks. *Warning:* This option is ignored when also using - # the through option. - # * :finder_sql - specify a complete SQL statement to fetch the association. This is a good way to go for complex + # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, + # such as last_name, first_name DESC. + # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id" + # as the default :foreign_key. + # * :dependent - If set to :destroy all the associated objects are destroyed + # alongside this object by calling their +destroy+ method. If set to :delete_all all associated + # objects are deleted *without* calling their +destroy+ method. If set to :nullify all associated + # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using + # the :through option. + # * :finder_sql - Specify a complete SQL statement to fetch the association. This is a good way to go for complex # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added. - # * :counter_sql - specify a complete SQL statement to fetch the size of the association. If :finder_sql is + # * :counter_sql - Specify a complete SQL statement to fetch the size of the association. If :finder_sql is # specified but not :counter_sql, :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM. - # * :extend - specify a named module for extending the proxy. See "Association extensions". - # * :include - specify second-order associations that should be eager loaded when the collection is loaded. - # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. - # * :limit: An integer determining the limit on the number of rows that should be returned. - # * :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. - # * :select: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join + # * :extend - Specify a named module for extending the proxy. See "Association extensions". + # * :include - Specify second-order associations that should be eager loaded when the collection is loaded. + # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :limit - An integer determining the limit on the number of rows that should be returned. + # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * :select - By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join # but not include the joined columns. - # * :as: Specifies a polymorphic interface (See #belongs_to). - # * :through: Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key + # * :as - Specifies a polymorphic interface (See belongs_to). + # * :through - Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key # are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to # or has_many association on the join model. - # * :source: Specifies the source association name used by has_many :through queries. Only use it if the name cannot be + # * :source - Specifies the source association name used by has_many :through queries. Only use it if the name cannot be # inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or - # :subscriber on +Subscription+, unless a :source is given. - # * :source_type: Specifies type of the source association used by has_many :through queries where the source + # :subscriber on Subscription, unless a :source is given. + # * :source_type - Specifies type of the source association used by has_many :through queries where the source # association is a polymorphic +belongs_to+. - # * :uniq - if set to +true+, duplicates will be omitted from the collection. Useful in conjunction with :through. - # * :readonly - if set to +true+, all the associated objects are readonly through the association. + # * :uniq - If true, duplicates will be omitted from the collection. Useful in conjunction with :through. + # * :readonly - If true, all the associated objects are readonly through the association. # # Option examples: # has_many :comments, :order => "posted_on" @@ -712,14 +712,14 @@ module ActiveRecord # Adds the following methods for retrieval and query of a single associated object: # +association+ is replaced with the symbol passed as the first argument, so # has_one :manager would add among others manager.nil?. - # * association(force_reload = false) - returns the associated object. +nil+ is returned if none is found. - # * association=(associate) - assigns the associate object, extracts the primary key, sets it as the foreign key, + # * association(force_reload = false) - Returns the associated object. +nil+ is returned if none is found. + # * association=(associate) - Assigns the associate object, extracts the primary key, sets it as the foreign key, # and saves the associate object. - # * association.nil? - returns +true+ if there is no associated object. - # * build_association(attributes = {}) - returns a new object of the associated type that has been instantiated + # * association.nil? - Returns +true+ if there is no associated object. + # * build_association(attributes = {}) - Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if # an association already exists. It will NOT work if the association is +nil+. - # * create_association(attributes = {}) - returns a new object of the associated type that has been instantiated + # * create_association(attributes = {}) - Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). # # Example: An Account class declares has_one :beneficiary, which will add: @@ -732,28 +732,28 @@ module ActiveRecord # The declaration can also include an options hash to specialize the behavior of the association. # # Options are: - # * :class_name - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So has_one :manager will by default be linked to the +Manager+ class, but - # if the real class name is +Person+, you'll have to specify it with this option. - # * :conditions - specify the conditions that the associated object must meet in order to be included as a +WHERE+ + # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_one :manager will by default be linked to the Manager class, but + # if the real class name is Person, you'll have to specify it with this option. + # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as rank = 5. - # * :order - specify the order in which the associated objects are returned as an ORDER BY SQL fragment, - # such as last_name, first_name DESC - # * :dependent - if set to :destroy, the associated object is destroyed when this object is. If set to + # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, + # such as last_name, first_name DESC. + # * :dependent - If set to :destroy, the associated object is destroyed when this object is. If set to # :delete, the associated object is deleted *without* calling its destroy method. If set to :nullify, the associated # object's foreign key is set to +NULL+. Also, association is assigned. - # * :foreign_key - specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_one+ association will use +person_id+ - # as the default +foreign_key+. - # * :include - specify second-order associations that should be eager loaded when this object is loaded. - # * :as: Specifies a polymorphic interface (See #belongs_to). + # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id" + # as the default :foreign_key. + # * :include - Specify second-order associations that should be eager loaded when this object is loaded. + # * :as - Specifies a polymorphic interface (See belongs_to). # * :through: Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key # are ignored, as the association uses the source reflection. You can only use a :through query through a # has_one or belongs_to association on the join model. - # * :source: Specifies the source association name used by has_one :through queries. Only use it if the name cannot be + # * :source - Specifies the source association name used by has_one :through queries. Only use it if the name cannot be # inferred from the association. has_one :favorite, :through => :favorites will look for a - # :favorite on +Favorite+, unless a :source is given. - # * :readonly - if set to +true+, the associated object is readonly through the association. + # :favorite on Favorite, unless a :source is given. + # * :readonly - If true, the associated object is readonly through the association. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -796,12 +796,12 @@ module ActiveRecord # Adds the following methods for retrieval and query for a single associated object for which this object holds an id: # +association+ is replaced with the symbol passed as the first argument, so # belongs_to :author would add among others author.nil?. - # * association(force_reload = false) - returns the associated object. +nil+ is returned if none is found. - # * association=(associate) - assigns the associate object, extracts the primary key, and sets it as the foreign key. - # * association.nil? - returns +true+ if there is no associated object. - # * build_association(attributes = {}) - returns a new object of the associated type that has been instantiated + # * association(force_reload = false) - Returns the associated object. +nil+ is returned if none is found. + # * association=(associate) - Assigns the associate object, extracts the primary key, and sets it as the foreign key. + # * association.nil? - Returns +true+ if there is no associated object. + # * build_association(attributes = {}) - Returns a new object of the associated type that has been instantiated # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. - # * create_association(attributes = {}) - returns a new object of the associated type that has been instantiated + # * create_association(attributes = {}) - Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation). # # Example: A Post class declares belongs_to :author, which will add: @@ -814,34 +814,35 @@ module ActiveRecord # The declaration can also include an options hash to specialize the behavior of the association. # # Options are: - # * :class_name - specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So has_one :author will by default be linked to the +Author+ class, but - # if the real class name is +Person+, you'll have to specify it with this option. - # * :conditions - specify the conditions that the associated object must meet in order to be included as a +WHERE+ + # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_one :author will by default be linked to the Author class, but + # if the real class name is Person, you'll have to specify it with this option. + # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as authorized = 1. - # * :order - specify the order in which the associated objects are returned as an ORDER BY SQL fragment, - # such as last_name, first_name DESC - # * :foreign_key - specify the foreign key used for the association. By default this is guessed to be the name - # of the association with an +_id+ suffix. So a class that defines a +belongs_to :person+ association will use +person_id+ as the default +foreign_key+. - # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+. - # * :dependent - if set to :destroy, the associated object is destroyed when this object is. If set to + # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, + # such as last_name, first_name DESC. + # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name + # of the association with an "_id" suffix. So a class that defines a belongs_to :person association will use + # "person_id" as the default :foreign_key. Similarly, belongs_to :favorite_person, :class_name => "Person" + # will use a foreign key of "favorite_person_id". + # * :dependent - If set to :destroy, the associated object is destroyed when this object is. If set to # :delete, the associated object is deleted *without* calling its destroy method. This option should not be specified when # belongs_to is used in conjunction with a has_many relationship on another class because of the potential to leave # orphaned records behind. - # * :counter_cache - caches the number of belonging objects on the associate class through the use of +increment_counter+ + # * :counter_cache - Caches the number of belonging objects on the associate class through the use of +increment_counter+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's - # destroyed. This requires that a column named #{table_name}_count (such as +comments_count+ for a belonging +Comment+ class) - # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing + # destroyed. This requires that a column named #{table_name}_count (such as +comments_count+ for a belonging Comment class) + # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing # a column name instead of a +true+/+false+ value to this option (e.g., :counter_cache => :my_custom_counter.) # When creating a counter cache column, the database statement or migration must specify a default value of 0, failing to do - # this results in a counter with NULL value, which will never increment. - # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly. - # * :include - specify second-order associations that should be eager loaded when this object is loaded. + # this results in a counter with +NULL+ value, which will never increment. + # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+. + # * :include - Specify second-order associations that should be eager loaded when this object is loaded. # Not allowed if the association is polymorphic. - # * :polymorphic - specify this association is a polymorphic association by passing +true+. + # * :polymorphic - Specify this association is a polymorphic association by passing +true+. # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute - # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). - # * :readonly - if set to +true+, the associated object is readonly through the association. + # to the +attr_readonly+ list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). + # * :readonly - If true, the associated object is readonly through the association. # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -926,14 +927,14 @@ module ActiveRecord end # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as - # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+ - # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence - # is calculated using the < operator for String. This means that if the strings are of different lengths, + # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project + # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence + # is calculated using the < operator for String. This means that if the strings are of different lengths, # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher - # lexical precedence than the shorter one. For example, one would expect the tables paper_boxes and papers - # to generate a join table name of papers_paper_boxes because of the length of the name paper_boxes, - # but it in fact generates a join table name of paper_boxes_papers. Be aware of this caveat, and use the - # custom join_table option if you need to. + # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" + # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", + # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the + # custom :join_table option if you need to. # # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as @@ -943,23 +944,23 @@ module ActiveRecord # Adds the following methods for retrieval and query: # +collection+ is replaced with the symbol passed as the first argument, so # has_and_belongs_to_many :categories would add among others categories.empty?. - # * collection(force_reload = false) - returns an array of all the associated objects. + # * collection(force_reload = false) - Returns an array of all the associated objects. # An empty array is returned if none are found. - # * collection<<(object, ...) - adds one or more objects to the collection by creating associations in the join table + # * collection<<(object, ...) - Adds one or more objects to the collection by creating associations in the join table # (collection.push and collection.concat are aliases to this method). - # * collection.delete(object, ...) - removes one or more objects from the collection by removing their associations from the join table. + # * collection.delete(object, ...) - Removes one or more objects from the collection by removing their associations from the join table. # This does not destroy the objects. - # * collection=objects - replaces the collection's content by deleting and adding objects as appropriate. - # * collection_singular_ids - returns an array of the associated objects' ids - # * collection_singular_ids=ids - replace the collection by the objects identified by the primary keys in +ids+ - # * collection.clear - removes every object from the collection. This does not destroy the objects. - # * collection.empty? - returns +true+ if there are no associated objects. - # * collection.size - returns the number of associated objects. - # * collection.find(id) - finds an associated object responding to the +id+ and that + # * collection=objects - Replaces the collection's content by deleting and adding objects as appropriate. + # * collection_singular_ids - Returns an array of the associated objects' ids. + # * collection_singular_ids=ids - Replace the collection by the objects identified by the primary keys in +ids+. + # * collection.clear - Removes every object from the collection. This does not destroy the objects. + # * collection.empty? - Returns +true+ if there are no associated objects. + # * collection.size - Returns the number of associated objects. + # * collection.find(id) - Finds an associated object responding to the +id+ and that # meets the condition that it has to be associated with this object. - # * collection.build(attributes = {}) - returns a new object of the collection type that has been instantiated + # * collection.build(attributes = {}) - Returns a new object of the collection type that has been instantiated # with +attributes+ and linked to this object through the join table, but has not yet been saved. - # * collection.create(attributes = {}) - returns a new object of the collection type that has been instantiated + # * collection.create(attributes = {}) - Returns a new object of the collection type that has been instantiated # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation). # # Example: A Developer class declares has_and_belongs_to_many :projects, which will add: @@ -978,38 +979,38 @@ module ActiveRecord # The declaration may include an options hash to specialize the behavior of the association. # # Options are: - # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred # from the association name. So has_and_belongs_to_many :projects will by default be linked to the - # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option. - # * :join_table - specify the name of the join table if the default based on lexical order isn't what you want. + # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. + # * :join_table - Specify the name of the join table if the default based on lexical order isn't what you want. # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any # +has_and_belongs_to_many+ declaration in order to work. - # * :foreign_key - specify the foreign key used for the association. By default this is guessed to be the name - # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_and_belongs_to_many+ association - # will use +person_id+ as the default +foreign_key+. - # * :association_foreign_key - specify the association foreign key used for the association. By default this is - # guessed to be the name of the associated class in lower-case and +_id+ suffixed. So if the associated class is +Project+, - # the +has_and_belongs_to_many+ association will use +project_id+ as the default association +foreign_key+. - # * :conditions - specify the conditions that the associated object must meet in order to be included as a +WHERE+ + # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association + # will use "person_id" as the default :foreign_key. + # * :association_foreign_key - Specify the association foreign key used for the association. By default this is + # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project, + # the +has_and_belongs_to_many+ association will use "project_id" as the default :association_foreign_key. + # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+ # SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used. # has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create # or @blog.posts.build. - # * :order - specify the order in which the associated objects are returned as an ORDER BY SQL fragment, + # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment, # such as last_name, first_name DESC - # * :uniq - if set to +true+, duplicate associated objects will be ignored by accessors and query methods - # * :finder_sql - overwrite the default generated SQL statement used to fetch the association with a manual statement - # * :delete_sql - overwrite the default generated SQL statement used to remove links between the associated - # classes with a manual statement - # * :insert_sql - overwrite the default generated SQL statement used to add links between the associated classes - # with a manual statement - # * :extend - anonymous module for extending the proxy, see "Association extensions". - # * :include - specify second-order associations that should be eager loaded when the collection is loaded. - # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. - # * :limit: An integer determining the limit on the number of rows that should be returned. - # * :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. - # * :select: By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join + # * :uniq - If true, duplicate associated objects will be ignored by accessors and query methods. + # * :finder_sql - Overwrite the default generated SQL statement used to fetch the association with a manual statement + # * :delete_sql - Overwrite the default generated SQL statement used to remove links between the associated + # classes with a manual statement. + # * :insert_sql - Overwrite the default generated SQL statement used to add links between the associated classes + # with a manual statement. + # * :extend - Anonymous module for extending the proxy, see "Association extensions". + # * :include - Specify second-order associations that should be eager loaded when the collection is loaded. + # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :limit - An integer determining the limit on the number of rows that should be returned. + # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * :select - By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join # but not include the joined columns. - # * :readonly - if set to +true+, all the associated objects are readonly through the association. + # * :readonly - If true, all the associated objects are readonly through the association. # # Option examples: # has_and_belongs_to_many :projects diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b600e8ecfd..74299bd572 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -92,13 +92,15 @@ module ActiveRecord #:nodoc: class DangerousAttributeError < ActiveRecordError end - # Raised when you've tried to access a column which wasn't - # loaded by your finder. Typically this is because :select - # has been specified. + # Raised when you've tried to access a column which wasn't loaded by your finder. + # Typically this is because :select has been specified. class MissingAttributeError < NoMethodError end - class AttributeAssignmentError < ActiveRecordError #:nodoc: + # Raised when an error occured while doing a mass assignment to an attribute through the + # attributes= method. The exception has an +attribute+ property that is the name of the + # offending attribute. + class AttributeAssignmentError < ActiveRecordError attr_reader :exception, :attribute def initialize(message, exception, attribute) @exception = exception @@ -107,7 +109,10 @@ module ActiveRecord #:nodoc: end end - class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc: + # Raised when there are multiple errors while doing a mass assignment through the +attributes+ + # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError + # objects, each corresponding to the error while assigning to an attribute. + class MultiparameterAssignmentErrors < ActiveRecordError attr_reader :errors def initialize(errors) @errors = errors @@ -230,8 +235,8 @@ module ActiveRecord #:nodoc: # == Accessing attributes before they have been typecasted # # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first. - # That can be done by using the _before_type_cast accessors that all attributes have. For example, if your Account model - # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast. + # That can be done by using the _before_type_cast accessors that all attributes have. For example, if your Account model + # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast. # # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you @@ -332,26 +337,26 @@ module ActiveRecord #:nodoc: # # == Exceptions # - # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record - # * +AdapterNotSpecified+ -- the configuration hash used in establish_connection didn't include an + # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record. + # * AdapterNotSpecified - The configuration hash used in establish_connection didn't include an # :adapter key. - # * +AdapterNotFound+ -- the :adapter key used in establish_connection specified a non-existent adapter + # * AdapterNotFound - The :adapter key used in establish_connection specified a non-existent adapter # (or a bad spelling of an existing one). - # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition. - # * +SerializationTypeMismatch+ -- the serialized object wasn't of the class specified as the second parameter. - # * +ConnectionNotEstablished+ -- no connection has been established. Use establish_connection before querying. - # * +RecordNotFound+ -- no record responded to the find* method. - # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions. - # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message. - # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions. - # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the - # +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+ + # * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition. + # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter. + # * ConnectionNotEstablished+ - No connection has been established. Use establish_connection before querying. + # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist + # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal + # nothing was found, please check its documentation for further details. + # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message. + # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the + # attributes= method. The +errors+ property of this exception contains an array of AttributeAssignmentError # objects that should be inspected to determine which attributes triggered the errors. - # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method. + # * AttributeAssignmentError - An error occurred while doing a mass assignment through the attributes= method. # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error. # # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level). - # So it's possible to assign a logger to the class through Base.logger= which will then be used by all + # So it's possible to assign a logger to the class through Base.logger= which will then be used by all # instances in the current object space. class Base # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed @@ -601,7 +606,7 @@ module ActiveRecord #:nodoc: # User.create(:first_name => 'Jamie') # # # Create an Array of new objects - # User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}]) + # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) # # # Create a single object and pass it into a block to set other attributes. # User.create(:first_name => 'Jamie') do |u| @@ -609,7 +614,7 @@ module ActiveRecord #:nodoc: # end # # # Creating an Array of new objects using a block, where the block is executed for each object: - # User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}]) do |u| + # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u| # u.is_admin = false # end def create(attributes = nil, &block) @@ -626,18 +631,18 @@ module ActiveRecord #:nodoc: # Updates an object (or multiple objects) and saves it to the database, if validations pass. # The resulting object is returned whether the object was saved successfully to the database or not. # - # ==== Options + # ==== Attributes # - # +id+ This should be the id or an array of ids to be updated - # +attributes+ This should be a Hash of attributes to be set on the object, or an array of Hashes. + # * +id+ - This should be the id or an array of ids to be updated. + # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes. # # ==== Examples # # # Updating one record: - # Person.update(15, {:user_name => 'Samuel', :group => 'expert'}) + # Person.update(15, { :user_name => 'Samuel', :group => 'expert' }) # # # Updating multiple records: - # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} } + # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } # Person.update(people.keys, people.values) def update(id, attributes) if id.is_a?(Array) @@ -656,9 +661,9 @@ module ActiveRecord #:nodoc: # # Objects are _not_ instantiated with this method. # - # ==== Options + # ==== Attributes # - # +id+ Can be either an Integer or an Array of Integers + # * +id+ - Can be either an Integer or an Array of Integers. # # ==== Examples # @@ -679,9 +684,9 @@ module ActiveRecord #:nodoc: # This essentially finds the object (or multiple objects) with the given id, creates a new object # from the attributes, and then calls destroy on it. # - # ==== Options + # ==== Attributes # - # +id+ Can be either an Integer or an Array of Integers + # * +id+ - Can be either an Integer or an Array of Integers. # # ==== Examples # @@ -702,12 +707,12 @@ module ActiveRecord #:nodoc: # Updates all records with details given if they match a set of conditions supplied, limits and order can # also be supplied. # - # ==== Options + # ==== Attributes # - # +updates+ A String of column and value pairs that will be set on any records that match conditions - # +conditions+ An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. - # See conditions in the intro for more info. - # +options+ Additional options are :limit and/or :order, see the examples for usage. + # * +updates+ - A String of column and value pairs that will be set on any records that match conditions. + # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. + # See conditions in the intro for more info. + # * +options+ - Additional options are :limit and/or :order, see the examples for usage. # # ==== Examples # @@ -734,9 +739,9 @@ module ActiveRecord #:nodoc: # many records. If you want to simply delete records without worrying about dependent associations or # callbacks, use the much faster +delete_all+ method instead. # - # ==== Options + # ==== Attributes # - # +conditions+ Conditions are specified the same way as with +find+ method. + # * +conditions+ - Conditions are specified the same way as with +find+ method. # # ==== Example # @@ -752,9 +757,9 @@ module ActiveRecord #:nodoc: # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient # than destroy_all. # - # ==== Options + # ==== Attributes # - # +conditions+ Conditions are specified the same way as with +find+ method. + # * +conditions+ - Conditions are specified the same way as with +find+ method. # # ==== Example # @@ -772,9 +777,9 @@ module ActiveRecord #:nodoc: # The use of this method should be restricted to complicated SQL queries that can't be executed # using the ActiveRecord::Calculations class methods. Look into those before using this. # - # ==== Options + # ==== Attributes # - # +sql+: An SQL statement which should return a count query from the database, see the example below + # * +sql+ - An SQL statement which should return a count query from the database, see the example below. # # ==== Examples # @@ -790,12 +795,11 @@ module ActiveRecord #:nodoc: # with the given ID, altering the given hash of counters by the amount # given by the corresponding value: # - # ==== Options + # ==== Attributes # - # +id+ The id of the object you wish to update a counter on - # +counters+ An Array of Hashes containing the names of the fields - # to update as keys and the amount to update the field by as - # values + # * +id+ - The id of the object you wish to update a counter on. + # * +counters+ - An Array of Hashes containing the names of the fields + # to update as keys and the amount to update the field by as values. # # ==== Examples # @@ -821,10 +825,10 @@ module ActiveRecord #:nodoc: # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is # shown it would have to run an SQL query to find how many posts and comments there are. # - # ==== Options + # ==== Attributes # - # +counter_name+ The name of the field that should be incremented - # +id+ The id of the object that should be incremented + # * +counter_name+ - The name of the field that should be incremented. + # * +id+ - The id of the object that should be incremented. # # ==== Examples # @@ -838,10 +842,10 @@ module ActiveRecord #:nodoc: # # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. # - # ==== Options + # ==== Attributes # - # +counter_name+ The name of the field that should be decremented - # +id+ The id of the object that should be decremented + # * +counter_name+ - The name of the field that should be decremented. + # * +id+ - The id of the object that should be decremented. # # ==== Examples # @@ -886,9 +890,9 @@ module ActiveRecord #:nodoc: # overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict # attributes as needed, have a look at attr_protected. # - # ==== Options + # ==== Attributes # - # *attributes A comma separated list of symbols that represent columns _not_ to be protected + # * *attributes A comma separated list of symbols that represent columns _not_ to be protected # # ==== Examples # @@ -927,10 +931,10 @@ module ActiveRecord #:nodoc: # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that # class on retrieval or +SerializationTypeMismatch+ will be raised. # - # ==== Options + # ==== Attributes # - # +attr_name+ The field name that should be serialized - # +class_name+ Optional, class name that the object type should be equal to + # * +attr_name+ - The field name that should be serialized. + # * +class_name+ - Optional, class name that the object type should be equal to. # # ==== Example # # Serialize a preferences attribute @@ -1757,7 +1761,7 @@ module ActiveRecord #:nodoc: # class Article < ActiveRecord::Base # def self.find_with_scope # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do - # with_scope(:find => { :limit => 10}) + # with_scope(:find => { :limit => 10 }) # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10 # end # with_scope(:find => { :conditions => "author_id = 3" }) @@ -2238,40 +2242,53 @@ module ActiveRecord #:nodoc: save! end - # Initializes the +attribute+ to zero if nil and adds the value passed as +by+ (default is one). Only makes sense for number-based attributes. Returns self. + # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). + # The increment is performed directly on the underlying attribute, no setter is invoked. + # Only makes sense for number-based attributes. Returns +self+. def increment(attribute, by = 1) self[attribute] ||= 0 self[attribute] += by self end - # Increments the +attribute+ and saves the record. - # Note: Updates made with this method aren't subjected to validation checks + # Wrapper around +increment+ that saves the record. This method differs from + # its non-bang version in that it passes through the attribute setter. + # Saving is not subjected to validation checks. Returns +true+ if the + # record could be saved. def increment!(attribute, by = 1) increment(attribute, by).update_attribute(attribute, self[attribute]) end - # Initializes the +attribute+ to zero if nil and subtracts the value passed as +by+ (default is one). Only makes sense for number-based attributes. Returns self. + # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1). + # The decrement is performed directly on the underlying attribute, no setter is invoked. + # Only makes sense for number-based attributes. Returns +self+. def decrement(attribute, by = 1) self[attribute] ||= 0 self[attribute] -= by self end - # Decrements the +attribute+ and saves the record. - # Note: Updates made with this method aren't subjected to validation checks + # Wrapper around +decrement+ that saves the record. This method differs from + # its non-bang version in that it passes through the attribute setter. + # Saving is not subjected to validation checks. Returns +true+ if the + # record could be saved. def decrement!(attribute, by = 1) decrement(attribute, by).update_attribute(attribute, self[attribute]) end - # Turns an +attribute+ that's currently true into false and vice versa. Returns self. + # Assigns to +attribute+ the boolean opposite of attribute?. So + # if the predicate returns +true+ the attribute will become +false+. This + # method toggles directly the underlying value without calling any setter. + # Returns +self+. def toggle(attribute) self[attribute] = !send("#{attribute}?") self end - # Toggles the +attribute+ and saves the record. - # Note: Updates made with this method aren't subjected to validation checks + # Wrapper around +toggle+ that saves the record. This method differs from + # its non-bang version in that it passes through the attribute setter. + # Saving is not subjected to validation checks. Returns +true+ if the + # record could be saved. def toggle!(attribute) toggle(attribute).update_attribute(attribute, self[attribute]) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index e6b8e3ae90..2afd6064ad 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -44,6 +44,12 @@ module ActiveRecord @query_cache_enabled = old end + # Clears the query cache. + # + # One reason you may wish to call this method explicitly is between queries + # that ask the database to randomize results. Otherwise the cache would see + # the same SQL query and repeatedly return the same result each time, silently + # undermining the randomness you were expecting. def clear_query_cache @query_cache.clear if @query_cache end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 5c7e9f27a5..8c286f64db 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -73,7 +73,7 @@ module ActiveRecord # REFERENTIAL INTEGRITY ==================================== - # Override to turn off referential integrity while executing +&block+ + # Override to turn off referential integrity while executing &block. def disable_referential_integrity(&block) yield end @@ -101,7 +101,7 @@ module ActiveRecord false end - # Lazily verify this connection, calling +active?+ only if it hasn't + # Lazily verify this connection, calling active? only if it hasn't # been called for +timeout+ seconds. def verify!(timeout) now = Time.now.to_i diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e742d60c5f..f00a2c8950 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -146,19 +146,19 @@ module ActiveRecord # # Options: # - # * :host -- Defaults to localhost - # * :port -- Defaults to 3306 - # * :socket -- Defaults to /tmp/mysql.sock - # * :username -- Defaults to root - # * :password -- Defaults to nothing - # * :database -- The name of the database. No default, must be provided. - # * :encoding -- (Optional) Sets the client encoding by executing "SET NAMES " after connection - # * :sslkey -- Necessary to use MySQL with an SSL connection - # * :sslcert -- Necessary to use MySQL with an SSL connection - # * :sslcapath -- Necessary to use MySQL with an SSL connection - # * :sslcipher -- Necessary to use MySQL with an SSL connection + # * :host - Defaults to "localhost". + # * :port - Defaults to 3306. + # * :socket - Defaults to "/tmp/mysql.sock". + # * :username - Defaults to "root" + # * :password - Defaults to nothing. + # * :database - The name of the database. No default, must be provided. + # * :encoding - (Optional) Sets the client encoding by executing "SET NAMES " after connection. + # * :sslkey - Necessary to use MySQL with an SSL connection. + # * :sslcert - Necessary to use MySQL with an SSL connection. + # * :sslcapath - Necessary to use MySQL with an SSL connection. + # * :sslcipher - Necessary to use MySQL with an SSL connection. # - # By default, the MysqlAdapter will consider all columns of type tinyint(1) + # By default, the MysqlAdapter will consider all columns of type tinyint(1) # as boolean. If you wish to disable this emulation (which was the default # behavior in versions 0.13.1 and earlier) you can add the following line # to your environment.rb file: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e3f7969cdf..2ec2d80af4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -228,15 +228,15 @@ module ActiveRecord # # Options: # - # * :host -- Defaults to localhost - # * :port -- Defaults to 5432 - # * :username -- Defaults to nothing - # * :password -- Defaults to nothing - # * :database -- The name of the database. No default, must be provided. - # * :schema_search_path -- An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option. - # * :encoding -- An optional client encoding that is used in a SET client_encoding TO call on the connection. - # * :min_messages -- An optional client min messages that is used in a SET client_min_messages TO call on the connection. - # * :allow_concurrency -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. + # * :host - Defaults to "localhost". + # * :port - Defaults to 5432. + # * :username - Defaults to nothing. + # * :password - Defaults to nothing. + # * :database - The name of the database. No default, must be provided. + # * :schema_search_path - An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option. + # * :encoding - An optional client encoding that is used in a SET client_encoding TO call on the connection. + # * :min_messages - An optional client min messages that is used in a SET client_min_messages TO call on the connection. + # * :allow_concurrency - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. class PostgreSQLAdapter < AbstractAdapter # Returns 'PostgreSQL' as adapter name for identification purposes. def adapter_name diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 8fa62c1845..8abbc6d0a4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -70,7 +70,7 @@ module ActiveRecord # # Options: # - # * :database -- Path to the database file. + # * :database - Path to the database file. class SQLiteAdapter < AbstractAdapter def adapter_name #:nodoc: 'SQLite' diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 7d5fd35dae..9367ea523d 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -426,7 +426,7 @@ end # == Support for YAML defaults # # You probably already know how to use YAML to set and reuse defaults in -# your +database.yml+ file,. You can use the same technique in your fixtures: +# your database.yml file. You can use the same technique in your fixtures: # # DEFAULTS: &DEFAULTS # created_on: <%= 3.weeks.ago.to_s(:db) %> diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 61005af83f..8614ef8751 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -153,6 +153,17 @@ module ActiveRecord end end + # Returns the AssociationReflection object specified in the :through option + # of a HasManyThrough or HasOneThrough association. Example: + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, :through => :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # taggings_reflection = tags_reflection.through_reflection + # def through_reflection @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false end @@ -168,7 +179,8 @@ module ActiveRecord # Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many. # (The :tags association on Tagging below.) # - # class Post + # class Post < ActiveRecord::Base + # has_many :taggings # has_many :tags, :through => :taggings # end # diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 50db32725d..d25e8cd0da 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -678,7 +678,7 @@ module ActiveRecord # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+). # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+). # * :with - The regular expression used to validate the format with (note: must be supplied!). - # * :on Specifies when this validation is active (default is :save, other options :create, :update). + # * :on - Specifies when this validation is active (default is :save, other options :create, :update). # * :if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The # method, proc or string should return or evaluate to a true or false value. @@ -784,7 +784,7 @@ module ActiveRecord # # Configuration options: # * :message - A custom error message (default is: "is invalid") - # * :on Specifies when this validation is active (default is :save, other options :create, :update) + # * :on - Specifies when this validation is active (default is :save, other options :create, :update). # * :if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The # method, proc or string should return or evaluate to a true or false value. @@ -802,8 +802,8 @@ module ActiveRecord end # Validates whether the value of the specified attribute is numeric by trying to convert it to - # a float with Kernel.Float (if integer is false) or applying it to the regular expression - # /\A[\+\-]?\d+\Z/ (if integer is set to true). + # a float with Kernel.Float (if only_integer is false) or applying it to the regular expression + # /\A[\+\-]?\d+\Z/ (if only_integer is set to true). # # class Person < ActiveRecord::Base # validates_numericality_of :value, :on => :create -- cgit v1.2.3