Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge commit 'mainstream/master'

  • Loading branch information...
commit babd1e57e697dca47c2ab8cd8c63486f2f1ea2f9 2 parents 02674a8 + 73c5963
@lifo lifo authored
View
2  activerecord/CHANGELOG
@@ -1,3 +1,5 @@
+* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
+
*2.1.0 RC1 (May 11th, 2008)*
* Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung]
View
25 activerecord/lib/active_record/associations/association_collection.rb
@@ -48,6 +48,26 @@ def find(*args)
end
end
+ # fetch first using SQL if possible
+ def first(*args)
+ if fetch_first_or_last_using_find? args
+ find(:first, *args)
+ else
+ load_target unless loaded?
+ @target.first(*args)
+ end
+ end
+
+ # fetch last using SQL if possible
+ def last(*args)
+ if fetch_first_or_last_using_find? args
+ find(:last, *args)
+ else
+ load_target unless loaded?
+ @target.last(*args)
+ end
+ end
+
def to_ary
load_target
@target.to_ary
@@ -330,7 +350,10 @@ def ensure_owner_is_not_new
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
end
-
+
+ def fetch_first_or_last_using_find?(args)
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
+ end
end
end
end
View
4 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -214,6 +214,10 @@ def rename_table(name, new_name)
end
def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
+ raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
+ end
+
super(table_name, column_name, type, options)
# See last paragraph on http://www.sqlite.org/lang_altertable.html
execute "VACUUM"
View
24 activerecord/lib/active_record/named_scope.rb
@@ -102,7 +102,13 @@ def named_scope(name, options = {}, &block)
class Scope
attr_reader :proxy_scope, :proxy_options
- [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
+
+ [].methods.each do |m|
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last)/
+ delegate m, :to => :proxy_found
+ end
+ end
+
delegate :scopes, :with_scope, :to => :proxy_scope
def initialize(proxy_scope, options, &block)
@@ -115,6 +121,22 @@ def reload
load_found; self
end
+ def first(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.first(*args)
+ else
+ find(:first, *args)
+ end
+ end
+
+ def last(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.last(*args)
+ else
+ find(:last, *args)
+ end
+ end
+
protected
def proxy_found
@found || load_found
View
2  activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -401,6 +401,8 @@ def test_find_in_association
def test_include_uses_array_include_after_loaded
project = projects(:active_record)
+ project.developers.class # force load target
+
developer = project.developers.first
assert_no_queries do
View
66 activerecord/test/cases/associations/has_many_associations_test.rb
@@ -818,6 +818,8 @@ def test_has_many_through_respects_hash_conditions
def test_include_uses_array_include_after_loaded
firm = companies(:first_firm)
+ firm.clients.class # force load target
+
client = firm.clients.first
assert_no_queries do
@@ -857,4 +859,68 @@ def test_include_returns_false_for_non_matching_record_to_verify_scoping
assert ! firm.clients.include?(client)
end
+ def test_calling_first_or_last_on_association_should_not_load_association
+ firm = companies(:first_firm)
+ firm.clients.first
+ firm.clients.last
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+ assert firm.clients.loaded?
+
+ assert_no_queries do
+ firm.clients.first
+ assert_equal 2, firm.clients.first(2).size
+ firm.clients.last
+ assert_equal 2, firm.clients.last(2).size
+ end
+ end
+
+ def test_calling_first_or_last_on_existing_record_with_build_should_load_association
+ firm = companies(:first_firm)
+ firm.clients.build(:name => 'Foo')
+ assert !firm.clients.loaded?
+
+ assert_queries 1 do
+ firm.clients.first
+ firm.clients.last
+ end
+
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_first_or_last_on_new_record_should_not_run_queries
+ firm = Firm.new
+
+ assert_no_queries do
+ firm.clients.first
+ firm.clients.last
+ end
+ end
+
+ def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+
+ assert_queries 2 do
+ assert firm.clients.loaded?
+ firm.clients.first(:order => 'name')
+ firm.clients.last(:order => 'name')
+ end
+ end
+
+ def test_calling_first_or_last_with_integer_on_association_should_load_association
+ firm = companies(:first_firm)
+
+ assert_queries 1 do
+ firm.clients.first(2)
+ firm.clients.last(2)
+ end
+
+ assert firm.clients.loaded?
+ end
+
end
View
2  activerecord/test/cases/associations/join_model_test.rb
@@ -664,6 +664,8 @@ def test_belongs_to_shared_parent
def test_has_many_through_include_uses_array_include_after_loaded
david = authors(:david)
+ david.categories.class # force load target
+
category = david.categories.first
assert_no_queries do
View
4 activerecord/test/cases/associations_test.rb
@@ -99,12 +99,12 @@ def test_proxy_accessors
david = authors(:david)
assert_equal david, david.posts.proxy_owner
assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection
- david.posts.first # force load target
+ david.posts.class # force load target
assert_equal david.posts, david.posts.proxy_target
assert_equal david, david.posts_with_extension.testing_proxy_owner
assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection
- david.posts_with_extension.first # force load target
+ david.posts_with_extension.class # force load target
assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target
end
View
28 activerecord/test/cases/named_scope_test.rb
@@ -118,4 +118,32 @@ def test_proxy_options
assert_equal expected_proxy_options, Topic.approved.proxy_options
end
+ def test_first_and_last_should_support_find_options
+ assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
+ assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
+ end
+
+ def test_first_and_last_should_allow_integers_for_limit
+ assert_equal Topic.base.first(2), Topic.base.to_a.first(2)
+ assert_equal Topic.base.last(2), Topic.base.to_a.last(2)
+ end
+
+ def test_first_and_last_should_not_use_query_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_no_queries do
+ topics.first
+ topics.last
+ end
+ end
+
+ def test_first_and_last_find_options_should_use_query_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_queries(2) do
+ topics.first(:order => 'title')
+ topics.last(:order => 'title')
+ end
+ end
+
end
View
26 activerecord/test/cases/transactions_test.rb
@@ -179,6 +179,32 @@ def test_rollback_when_commit_raises
end
end
+ def test_sqlite_add_column_in_transaction_raises_statement_invalid
+ return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter)
+
+ # Test first if column creation/deletion works correctly when no
+ # transaction is in place.
+ #
+ # We go back to the connection for the column queries because
+ # Topic.columns is cached and won't report changes to the DB
+
+ assert_nothing_raised do
+ Topic.reset_column_information
+ Topic.connection.add_column('topics', 'stuff', :string)
+ assert Topic.column_names.include?('stuff')
+
+ Topic.reset_column_information
+ Topic.connection.remove_column('topics', 'stuff')
+ assert !Topic.column_names.include?('stuff')
+ end
+
+ # Test now inside a transaction: add_column should raise a StatementInvalid
+ Topic.transaction do
+ assert_raises(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) }
+ raise ActiveRecord::Rollback
+ end
+ end
+
private
def add_exception_raising_after_save_callback_to_topic
Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
View
2  activesupport/lib/active_support/dependencies.rb
@@ -384,7 +384,7 @@ def new_constants_in(*descs)
return new_constants
ensure
# Remove the stack frames that we added.
- if defined?(watch_frames) && ! watch_frames.empty?
+ if defined?(watch_frames) && ! watch_frames.blank?
frame_ids = watch_frames.collect(&:object_id)
constant_watch_stack.delete_if do |watch_frame|
frame_ids.include? watch_frame.object_id
View
6 activesupport/test/dependencies_test.rb
@@ -584,6 +584,12 @@ def test_new_constants_in_with_inherited_constants
assert_equal [], m
end
+ def test_new_constants_in_with_illegal_module_name_raises_correct_error
+ assert_raises(NameError) do
+ Dependencies.new_constants_in("Illegal-Name") {}
+ end
+ end
+
def test_file_with_multiple_constants_and_require_dependency
with_loading 'autoloading_fixtures' do
assert ! defined?(MultipleConstantFile)
Please sign in to comment.
Something went wrong with that request. Please try again.