diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2009-02-06 12:13:44 +0100 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2009-02-06 12:13:44 +0100 |
commit | 7a5da7ce785616d79843dfebbb5107be8b46f8c5 (patch) | |
tree | 355145e59fbf166b8f2cde23dc8a13b188384c7e /activerecord | |
parent | 81c7a5d48d972c9e3973b4c018fafae1dfd9c0de (diff) | |
parent | be098f840614bbb71fe26f0e2b4c064b6866c076 (diff) | |
download | rails-7a5da7ce785616d79843dfebbb5107be8b46f8c5.tar.gz rails-7a5da7ce785616d79843dfebbb5107be8b46f8c5.tar.bz2 rails-7a5da7ce785616d79843dfebbb5107be8b46f8c5.zip |
Merge branch 'master' of git@github.com:rails/rails
Diffstat (limited to 'activerecord')
13 files changed, 104 insertions, 11 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3f2b5d726e..05ce8ff0b5 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1090,6 +1090,22 @@ module ActiveRecord # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the # custom <tt>:join_table</tt> option if you need to. # + # The join table should not have a primary key or a model associated with it. You must manually generate the + # join table with a migration such as this: + # + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration + # def self.up + # create_table :developers_projects, :id => false do |t| + # t.integer :developer_id + # t.integer :project_id + # end + # end + # + # def self.down + # drop_table :developers_projects + # end + # end + # # 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 # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 177d156834..3ffc48941c 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -324,6 +324,7 @@ module ActiveRecord if Numeric === value || value !~ /[^0-9]/ !value.to_i.zero? else + return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) !value.blank? end elsif column.number? diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 07660ebd03..680b41518c 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -129,6 +129,7 @@ module ActiveRecord base.class_eval do alias_method_chain :reload, :autosave_associations alias_method_chain :save, :autosave_associations + alias_method_chain :save!, :autosave_associations alias_method_chain :valid?, :autosave_associations %w{ has_one belongs_to has_many has_and_belongs_to_many }.each do |type| @@ -161,6 +162,17 @@ module ActiveRecord end end + # Attempts to save the record just like save_with_autosave_associations but + # will raise a RecordInvalid exception instead of returning false if the + # record is not valid. + def save_with_autosave_associations! + if valid_with_autosave_associations? + save_with_autosave_associations(false) || raise(RecordNotSaved) + else + raise RecordInvalid.new(self) + end + end + # Returns whether or not the parent, <tt>self</tt>, and any loaded autosave associations are valid. def valid_with_autosave_associations? if valid_without_autosave_associations? diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9f9fbd8b37..78c6ac2ba8 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1992,12 +1992,16 @@ module ActiveRecord #:nodoc: attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) } end - def attribute_condition(argument) + def attribute_condition(quoted_column_name, argument) case argument - when nil then "IS ?" - when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "IN (?)" - when Range then "BETWEEN ? AND ?" - else "= ?" + when nil then "#{quoted_column_name} IS ?" + when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope then "#{quoted_column_name} IN (?)" + when Range then if argument.exclude_end? + "#{quoted_column_name} >= ? AND #{quoted_column_name} < ?" + else + "#{quoted_column_name} BETWEEN ? AND ?" + end + else "#{quoted_column_name} = ?" end end @@ -2307,7 +2311,7 @@ module ActiveRecord #:nodoc: table_name = connection.quote_table_name(table_name) end - "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}" + attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value) else sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s)) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 273f823e7f..24c734cddb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -8,6 +8,7 @@ module ActiveRecord # An abstract definition of a column in a table. class Column TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set + FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set module Format ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 8bfdadd0e3..5778223c74 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -86,7 +86,8 @@ module ActiveRecord # For each key in the hash that starts with the string 'new' a new model # will be instantiated. When the proc given with the <tt>:reject_if</tt> # option evaluates to +false+ for a certain attribute hash no record will - # be built for that hash. + # be built for that hash. (Rejecting new records can alternatively be done + # by utilizing the <tt>'_delete'</tt> key. Scroll down for more info.) # # params = { 'member' => { # 'name' => 'joe', 'posts_attributes' => { @@ -258,11 +259,14 @@ module ActiveRecord # If a <tt>:reject_if</tt> proc exists for this association, it will be # called with the attributes as its argument. If the proc returns a truthy # value, the record is _not_ build. + # + # Alternatively, you can specify the <tt>'_delete'</tt> key to _not_ build + # a record. See should_destroy_nested_attributes_record? for more info. def build_new_nested_attributes_record(association_name, attributes) if reject_proc = self.class.reject_new_nested_attributes_procs[association_name] return if reject_proc.call(attributes) end - send(association_name).build(attributes) + send(association_name).build(attributes) unless should_destroy_nested_attributes_record?(true, attributes) end # Assigns the attributes to the record specified by +id+. Or marks it for diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 5e45cf65ab..de199d30bf 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -99,7 +99,7 @@ module ActiveRecord define_method(:session_id=) { |session_id| self.sessid = session_id } else def self.find_by_session_id(session_id) - find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id] + find :first, :conditions => {:session_id=>session_id} end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index ce93b0f270..8f3c80565e 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -744,7 +744,7 @@ module ActiveRecord if scope = configuration[:scope] Array(scope).map do |scope_item| scope_value = record.send(scope_item) - condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}" + condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value) condition_params << scope_value end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 77ee8d8fc4..759f9f3872 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -56,6 +56,18 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal myobj, topic.content end + def test_typecast_attribute_from_select_to_false + topic = Topic.create(:title => 'Budget') + topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + assert !topic.is_test? + end + + def test_typecast_attribute_from_select_to_true + topic = Topic.create(:title => 'Budget') + topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + assert topic.is_test? + end + def test_kernel_methods_not_implemented_in_activerecord %w(test name display y).each do |method| assert !ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 3c656b2430..381249c0c2 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -169,6 +169,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase assert_equal 'The Vile Insanity', @pirate.reload.ship.name end + def test_should_automatically_save_bang_the_associated_model + @pirate.ship.name = 'The Vile Insanity' + @pirate.save! + assert_equal 'The Vile Insanity', @pirate.reload.ship.name + end + def test_should_automatically_validate_the_associated_model @pirate.ship.name = '' assert !@pirate.valid? @@ -245,6 +251,12 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase assert_equal 'Arr', @ship.reload.pirate.catchphrase end + def test_should_automatically_save_bang_the_associated_model + @ship.pirate.catchphrase = 'Arr' + @ship.save! + assert_equal 'Arr', @ship.reload.pirate.catchphrase + end + def test_should_automatically_validate_the_associated_model @ship.pirate.catchphrase = '' assert !@ship.valid? @@ -298,6 +310,14 @@ module AutosaveAssociationOnACollectionAssociationTests assert_equal new_names, @pirate.reload.send(@association_name).map(&:name) end + def test_should_automatically_save_bang_the_associated_models + new_names = ['Grace OMalley', 'Privateers Greed'] + @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } + + @pirate.save! + assert_equal new_names, @pirate.reload.send(@association_name).map(&:name) + end + def test_should_automatically_validate_the_associated_models @pirate.send(@association_name).each { |child| child.name = '' } @@ -347,7 +367,9 @@ module AutosaveAssociationOnACollectionAssociationTests def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! } - assert_queries(2) do + @pirate.send(@association_name).class # hack to load the target + + assert_queries(3) do @pirate.catchphrase = 'Yarr' new_names = ['Grace OMalley', 'Privateers Greed'] @pirate.send(@association_name).each_with_index { |child, i| child.name = new_names[i] } diff --git a/activerecord/test/cases/datatype_test_postgresql.rb b/activerecord/test/cases/datatype_test_postgresql.rb index bff092b5d7..88fb6f7384 100644 --- a/activerecord/test/cases/datatype_test_postgresql.rb +++ b/activerecord/test/cases/datatype_test_postgresql.rb @@ -26,6 +26,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection + @connection.execute("set lc_monetary = 'C'") @connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") @first_array = PostgresqlArray.find(1) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index aac5e6a96b..ee8f4901f9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -307,6 +307,12 @@ class FinderTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) } end + def test_find_on_hash_conditions_with_end_exclusive_range + assert_equal [1,2,3], Topic.find(:all, :conditions => { :id => 1..3 }).map(&:id).sort + assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1...3 }).map(&:id).sort + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) } + end + def test_find_on_hash_conditions_with_multiple_ranges assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 1605684677..2e531a284e 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -233,6 +233,20 @@ module NestedAttributesOnACollectionAssociationTests assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name end + def test_should_remove_delete_key_from_arguments_hash_of_new_records + assert_nothing_raised ActiveRecord::UnknownAttributeError do + @pirate.send(association_setter, { 'new_1' => { '_delete' => '0' }}) + end + end + + def test_should_ignore_new_associated_records_with_truthy_delete_attribute + @pirate.send(@association_name).destroy_all + @pirate.reload.attributes = { association_getter => { 'new_1' => { :name => 'Grace OMalley' }, 'new_2' => { :name => 'Privateers Greed', '_delete' => '1' }}} + + assert_equal 1, @pirate.send(@association_name).length + assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name + end + def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models attributes = ActiveSupport::OrderedHash.new attributes['new_123726353'] = { :name => 'Grace OMalley' } |