From 5a8f764661bcdf9c6ce503c0ff343a1970deb1bb Mon Sep 17 00:00:00 2001 From: Scott Taylor Date: Sat, 31 Jan 2009 21:32:49 -0500 Subject: Add ActiveRecord::Base.exists? with no args [#1817 state:committed] Signed-off-by: David Heinemeier Hansson --- activerecord/CHANGELOG | 5 +++++ activerecord/lib/active_record/base.rb | 6 ++++-- activerecord/test/cases/finder_test.rb | 11 ++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 027533aa2d..a63c09bbaa 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,3 +1,8 @@ +*Edge* + +* Added that ActiveRecord::Base.exists? can be called with no arguments #1817 [Scott Taylor] + + *2.3.0 [RC1] (February 1st, 2009)* * Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran] diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index f9168c8dc2..9f9fbd8b37 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -663,7 +663,7 @@ module ActiveRecord #:nodoc: # Returns true if a record exists in the table that matches the +id+ or - # conditions given, or false otherwise. The argument can take four forms: + # conditions given, or false otherwise. The argument can take five forms: # # * Integer - Finds the record with this primary key. # * String - Finds the record with a primary key corresponding to this @@ -672,6 +672,7 @@ module ActiveRecord #:nodoc: # (such as ['color = ?', 'red']). # * Hash - Finds the record that matches these +find+-style conditions # (such as {:color => 'red'}). + # * No args - Returns false if the table is empty, true otherwise. # # For more information about specifying conditions as a Hash or Array, # see the Conditions section in the introduction to ActiveRecord::Base. @@ -685,7 +686,8 @@ module ActiveRecord #:nodoc: # Person.exists?('5') # Person.exists?(:name => "David") # Person.exists?(['name LIKE ?', "%#{query}%"]) - def exists?(id_or_conditions) + # Person.exists? + def exists?(id_or_conditions = {}) connection.select_all( construct_finder_sql( :select => "#{quoted_table_name}.#{primary_key}", diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7fd7fd7a5b..aac5e6a96b 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -94,7 +94,16 @@ class FinderTest < ActiveRecord::TestCase assert_raise(NoMethodError) { Topic.exists?([1,2]) } end - + + def test_exists_returns_true_with_one_record_and_no_args + assert Topic.exists? + end + + def test_does_not_exist_with_empty_table_and_no_args_given + Topic.delete_all + assert !Topic.exists? + end + def test_exists_with_aggregate_having_three_mappings existing_address = customers(:david).address assert Customer.exists?(:address => existing_address) -- cgit v1.2.3 From 455a7633dbdb295de828eb2657433d47d85eb0bc Mon Sep 17 00:00:00 2001 From: Pascal Ehlert Date: Wed, 4 Feb 2009 09:24:02 +0100 Subject: Nested attribute accessors should ignore new records with truthy _delete key. Signed-off-by: Michael Koziarski [#1861 state:committed] --- activerecord/lib/active_record/nested_attributes.rb | 8 ++++++-- activerecord/test/cases/nested_attributes_test.rb | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'activerecord') 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 :reject_if # 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 '_delete' key. Scroll down for more info.) # # params = { 'member' => { # 'name' => 'joe', 'posts_attributes' => { @@ -258,11 +259,14 @@ module ActiveRecord # If a :reject_if 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 '_delete' 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/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' } -- cgit v1.2.3 From db5d6950169f8f10b6aec85faa2c38e0c57315c7 Mon Sep 17 00:00:00 2001 From: Eloy Duran Date: Wed, 4 Feb 2009 21:40:53 +0100 Subject: Also save :autosave enabled associations when #save! is used. Signed-off-by: Michael Koziarski [#1877 state:committed] --- .../lib/active_record/autosave_association.rb | 12 +++++++++++ .../test/cases/autosave_association_test.rb | 24 +++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'activerecord') 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, self, and any loaded autosave associations are valid. def valid_with_autosave_associations? if valid_without_autosave_associations? 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] } -- cgit v1.2.3 From 9c6bde58c52f63d9e40979b89bc5db0c786159da Mon Sep 17 00:00:00 2001 From: Max Lapshin Date: Wed, 14 Jan 2009 16:08:57 +0300 Subject: Fixed tests of postgresql money insertion when non-C locale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tarmo Tänav Signed-off-by: Michael Koziarski --- activerecord/test/cases/datatype_test_postgresql.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord') 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) -- cgit v1.2.3 From 1fe9d6cc88dbad26e865c7c2bfd83da25796e2f1 Mon Sep 17 00:00:00 2001 From: Max Lapshin Date: Wed, 14 Jan 2009 16:09:23 +0300 Subject: Support true/false in query_attribute for calculated columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tarmo Tänav Signed-off-by: Michael Koziarski --- activerecord/lib/active_record/attribute_methods.rb | 1 + .../connection_adapters/abstract/schema_definitions.rb | 1 + activerecord/test/cases/attribute_methods_test.rb | 12 ++++++++++++ 3 files changed, 14 insertions(+) (limited to 'activerecord') 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/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/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" -- cgit v1.2.3 From 9991868d85b25da672bf119bfcbff22a4bb6e8f1 Mon Sep 17 00:00:00 2001 From: Will Bryant Date: Wed, 4 Feb 2009 13:01:03 +1300 Subject: support end-exclusive ... Ranges in SQL hash condition sanitization properly Signed-off-by: Michael Koziarski [#1865 state:committed] --- activerecord/lib/active_record/base.rb | 16 ++++++++++------ activerecord/lib/active_record/validations.rb | 2 +- activerecord/test/cases/finder_test.rb | 6 ++++++ 3 files changed, 17 insertions(+), 7 deletions(-) (limited to 'activerecord') 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/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/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 -- cgit v1.2.3 From 011ea46569a5df1412aecf25fc4ea2e287b0e136 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Fri, 6 Feb 2009 14:32:03 +1300 Subject: Don't use weird attribute_condition methods in AR Session Store Use the hash condition for find instead. --- activerecord/lib/active_record/session_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') 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 -- cgit v1.2.3 From 96d610553e5fdaabc923835ab1f194070ddb4477 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Fri, 6 Feb 2009 01:57:02 +0000 Subject: Merge docrails along with the new guides and guides generation code --- activerecord/lib/active_record/associations.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'activerecord') 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 :join_table 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 -- cgit v1.2.3