diff options
-rw-r--r-- | activerecord/CHANGELOG | 2 | ||||
-rwxr-xr-x | activerecord/lib/active_record/associations.rb | 18 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 30 | ||||
-rw-r--r-- | activerecord/lib/active_record/calculations.rb | 4 | ||||
-rw-r--r-- | activerecord/test/adapter_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/method_scoping_test.rb | 65 |
6 files changed, 101 insertions, 20 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 81b674864d..39095ac1f5 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Add support for :include to with_scope [andrew@redlinesoftware.com] + * Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 [schoenm@earthlink.net] * Change periods (.) in table aliases to _'s. Closes #4251 [jeff@ministrycentered.com] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index ca5ae33b5a..dcb0f31684 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -929,12 +929,12 @@ module ActiveRecord end def count_with_associations(options = {}) - join_dependency = JoinDependency.new(self, options[:include], options[:joins]) + join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency)) end def find_with_associations(options = {}) - join_dependency = JoinDependency.new(self, options[:include], options[:joins]) + join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) rows = select_all_rows(options, join_dependency) return join_dependency.instantiate(rows) end @@ -1084,10 +1084,10 @@ module ActiveRecord sql << " FROM #{table_name} " sql << join_dependency.join_associations.collect{|join| join.association_join }.join - sql << "#{options[:joins]} " if options[:joins] - + + add_joins!(sql, options) add_conditions!(sql, options[:conditions]) - add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit] + add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && (scope(:find, :limit) || options[:limit]) add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections) @@ -1099,10 +1099,10 @@ module ActiveRecord end def construct_finder_sql_with_included_associations(options, join_dependency) - sql = "SELECT #{column_aliases(join_dependency)} FROM #{options[:from] || table_name} " + sql = "SELECT #{column_aliases(join_dependency)} FROM #{scope(:find, :from) || options[:from] || table_name} " sql << join_dependency.join_associations.collect{|join| join.association_join }.join - sql << "#{options[:joins]} " if options[:joins] + add_joins!(sql, options) add_conditions!(sql, options[:conditions]) add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit] @@ -1134,7 +1134,7 @@ module ActiveRecord if include_eager_conditions?(options) || include_eager_order?(options) sql << join_dependency.join_associations.collect{|join| join.association_join }.join - sql << "#{options[:joins]} " if options[:joins] + add_joins!(sql, options) end add_conditions!(sql, options[:conditions]) @@ -1144,7 +1144,7 @@ module ActiveRecord end def include_eager_conditions?(options) - conditions = options[:conditions] + conditions = scope(:find, :conditions) || options[:conditions] return false unless conditions conditions = conditions.first if conditions.is_a?(Array) conditions.scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name| diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c58a7bf5c2..2cdffba49f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -388,7 +388,7 @@ module ActiveRecord #:nodoc: when :first find(:all, options.merge(options[:include] ? { } : { :limit => 1 })).first when :all - records = options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options)) + records = (scoped?(:find, :include) || options[:include]) ? find_with_associations(options) : find_by_sql(construct_finder_sql(options)) records.each { |record| record.readonly! } if options[:readonly] records else @@ -834,7 +834,7 @@ module ActiveRecord #:nodoc: # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash. # method_name may be :find or :create. :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>, - # <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash. + # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash. # # Article.with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do # Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1 @@ -873,7 +873,7 @@ module ActiveRecord #:nodoc: method_scoping.assert_valid_keys([ :find, :create ]) if f = method_scoping[:find] - f.assert_valid_keys([ :conditions, :joins, :select, :from, :offset, :limit, :readonly ]) + f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :readonly ]) f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly) end @@ -882,10 +882,13 @@ module ActiveRecord #:nodoc: method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)| case hash[method] when Hash - if method == :find && hash[method][:conditions] && params[:conditions] + if method == :find (hash[method].keys + params.keys).uniq.each do |key| - if key == :conditions + merge = hash[method][key] && params[key] # merge if both scopes have the same key + if key == :conditions && merge hash[method][key] = [params[key], hash[method][key]].collect{|sql| "( %s )" % sanitize_sql(sql)}.join(" AND ") + elsif key == :include && merge + hash[method][key] = merge_includes(hash[method][key], params[key]).uniq else hash[method][key] = hash[method][key] || params[key] end @@ -994,6 +997,23 @@ module ActiveRecord #:nodoc: sql end + # Merges includes so that the result is a valid +include+ + def merge_includes(first, second) + safe_to_array(first) + safe_to_array(second) + end + + # Object#to_a is deprecated, though it does have the desired behaviour + def safe_to_array(o) + case o + when NilClass + [] + when Array + o + else + [o] + end + end + def add_limit!(sql, options) options[:limit] ||= scope(:find, :limit) options[:offset] ||= scope(:find, :offset) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index af976f532f..774926f409 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -61,7 +61,7 @@ module ActiveRecord raise(ArgumentError, "Unexpected parameters passed to count(*args): expected either count(conditions=nil, joins=nil) or count(options={})") end - options[:include] ? count_with_associations(options) : calculate(:count, :all, options) + (scope(:find, :include) || options[:include]) ? count_with_associations(options) : calculate(:count, :all, options) end # Calculates average value on a given column. The value is returned as a float. See #calculate for examples with options. @@ -221,4 +221,4 @@ module ActiveRecord end end end -end
\ No newline at end of file +end diff --git a/activerecord/test/adapter_test.rb b/activerecord/test/adapter_test.rb index 3da1bf3e54..772dd0e5a8 100644 --- a/activerecord/test/adapter_test.rb +++ b/activerecord/test/adapter_test.rb @@ -43,8 +43,6 @@ class AdapterTest < Test::Unit::TestCase def test_current_database if @connection.respond_to?(:current_database) assert_equal "activerecord_unittest", @connection.current_database - else - warn "#{@connection.class} does not respond to #current_database" end end diff --git a/activerecord/test/method_scoping_test.rb b/activerecord/test/method_scoping_test.rb index c01aab6a36..bceb3869a6 100644 --- a/activerecord/test/method_scoping_test.rb +++ b/activerecord/test/method_scoping_test.rb @@ -1,11 +1,12 @@ require 'abstract_unit' require 'fixtures/developer' +require 'fixtures/project' require 'fixtures/comment' require 'fixtures/post' require 'fixtures/category' class MethodScopingTest < Test::Unit::TestCase - fixtures :developers, :comments, :posts + fixtures :developers, :projects, :comments, :posts def test_set_conditions Developer.with_scope(:find => { :conditions => 'just a test...' }) do @@ -60,6 +61,23 @@ class MethodScopingTest < Test::Unit::TestCase end end + def test_scoped_find_include + # with the include, will retrieve only developers for the given project + scoped_developers = Developer.with_scope(:find => { :include => :projects }) do + Developer.find(:all, :conditions => 'projects.id = 2') + end + assert scoped_developers.include?(developers(:david)) + assert !scoped_developers.include?(developers(:jamis)) + assert_equal 1, scoped_developers.size + end + + def test_scoped_count_include + # with the include, will retrieve only developers for the given project + Developer.with_scope(:find => { :include => :projects }) do + assert_equal 1, Developer.count('projects.id = 2') + end + end + def test_scoped_create new_comment = nil @@ -108,7 +126,7 @@ class MethodScopingTest < Test::Unit::TestCase end class NestedScopingTest < Test::Unit::TestCase - fixtures :developers, :comments, :posts + fixtures :developers, :projects, :comments, :posts def test_merge_options Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do @@ -160,6 +178,49 @@ class NestedScopingTest < Test::Unit::TestCase end end + def test_nested_scoped_find_include + Developer.with_scope(:find => { :include => :projects }) do + Developer.with_scope(:find => { :conditions => "projects.id = 2" }) do + assert_nothing_raised { Developer.find(1) } + assert_equal('David', Developer.find(:first).name) + end + end + end + + def test_nested_scoped_find_merged_include + # :include's remain unique and don't "double up" when merging + Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do + Developer.with_scope(:find => { :include => :projects }) do + assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length + assert_equal('David', Developer.find(:first).name) + end + end + + # the nested scope doesn't remove the first :include + Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do + Developer.with_scope(:find => { :include => [] }) do + assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length + assert_equal('David', Developer.find(:first).name) + end + end + + # mixing array and symbol include's will merge correctly + Developer.with_scope(:find => { :include => [:projects], :conditions => "projects.id = 2" }) do + Developer.with_scope(:find => { :include => :projects }) do + assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length + assert_equal('David', Developer.find(:first).name) + end + end + end + + def test_nested_scoped_find_replace_include + Developer.with_scope(:find => { :include => :projects }) do + Developer.with_exclusive_scope(:find => { :include => [] }) do + assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length + end + end + end + def test_three_level_nested_exclusive_scoped_find Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do assert_equal('Jamis', Developer.find(:first).name) |