Permalink
Browse files

Merge branch 'master' into sprockets

  • Loading branch information...
2 parents ac9443e + 9772de8 commit 5df076ad0965dc684afff8a019fd9f92a53ada76 @josh josh committed Mar 31, 2011
Showing with 1,729 additions and 608 deletions.
  1. +3 −0 Gemfile
  2. +1 −1 actionpack/lib/abstract_controller/callbacks.rb
  3. +8 −1 actionpack/lib/action_dispatch/routing/mapper.rb
  4. +7 −0 actionpack/test/action_dispatch/routing/mapper_test.rb
  5. +27 −0 actionpack/test/controller/filters_test.rb
  6. +1 −0 actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb
  7. +1 −0 actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb
  8. +8 −2 activemodel/lib/active_model/attribute_methods.rb
  9. +0 −11 activemodel/test/cases/attribute_methods_test.rb
  10. +6 −0 activerecord/CHANGELOG
  11. +61 −17 activerecord/lib/active_record/associations.rb
  12. +85 −0 activerecord/lib/active_record/associations/alias_tracker.rb
  13. +6 −38 activerecord/lib/active_record/associations/association.rb
  14. +120 −0 activerecord/lib/active_record/associations/association_scope.rb
  15. +12 −12 activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
  16. +0 −13 activerecord/lib/active_record/associations/collection_association.rb
  17. +0 −22 activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
  18. +0 −2 activerecord/lib/active_record/associations/has_many_association.rb
  19. +6 −0 activerecord/lib/active_record/associations/has_many_through_association.rb
  20. +0 −6 activerecord/lib/active_record/associations/has_one_association.rb
  21. +2 −0 activerecord/lib/active_record/associations/has_one_through_association.rb
  22. +8 −24 activerecord/lib/active_record/associations/join_dependency.rb
  23. +66 −201 activerecord/lib/active_record/associations/join_dependency/join_association.rb
  24. +56 −0 activerecord/lib/active_record/associations/join_helper.rb
  25. +3 −2 activerecord/lib/active_record/associations/preloader/through_association.rb
  26. +16 −94 activerecord/lib/active_record/associations/through_association.rb
  27. +8 −1 activerecord/lib/active_record/attribute_methods/read.rb
  28. +5 −4 activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
  29. +7 −1 activerecord/lib/active_record/attribute_methods/write.rb
  30. +3 −3 activerecord/lib/active_record/base.rb
  31. +4 −1 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
  32. +2 −3 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
  33. +4 −3 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
  34. +4 −0 activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
  35. +1 −9 activerecord/lib/active_record/persistence.rb
  36. +101 −17 activerecord/lib/active_record/reflection.rb
  37. +14 −3 activerecord/lib/active_record/relation.rb
  38. +1 −1 activerecord/lib/active_record/relation/query_methods.rb
  39. +15 −15 activerecord/test/cases/associations/cascaded_eager_loading_test.rb
  40. +6 −6 activerecord/test/cases/associations/eager_test.rb
  41. +3 −3 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
  42. +25 −5 activerecord/test/cases/associations/has_many_through_associations_test.rb
  43. +23 −1 activerecord/test/cases/associations/has_one_through_associations_test.rb
  44. +3 −10 activerecord/test/cases/associations/join_model_test.rb
  45. +546 −0 activerecord/test/cases/associations/nested_through_associations_test.rb
  46. +11 −15 activerecord/test/cases/attribute_methods_test.rb
  47. +12 −0 activerecord/test/cases/base_test.rb
  48. +1 −1 activerecord/test/cases/batches_test.rb
  49. +4 −2 activerecord/test/cases/finder_test.rb
  50. +34 −0 activerecord/test/cases/habtm_destroy_order_test.rb
  51. +9 −9 activerecord/test/cases/identity_map_test.rb
  52. +6 −2 activerecord/test/cases/json_serialization_test.rb
  53. +54 −3 activerecord/test/cases/reflection_test.rb
  54. +1 −1 activerecord/test/cases/relation_scoping_test.rb
  55. +20 −20 activerecord/test/cases/relations_test.rb
  56. +6 −0 activerecord/test/fixtures/authors.yml
  57. +2 −0 activerecord/test/fixtures/books.yml
  58. +5 −0 activerecord/test/fixtures/categories.yml
  59. +8 −0 activerecord/test/fixtures/categories_posts.yml
  60. +6 −0 activerecord/test/fixtures/categorizations.yml
  61. +3 −1 activerecord/test/fixtures/clubs.yml
  62. +6 −0 activerecord/test/fixtures/essays.yml
  63. +8 −0 activerecord/test/fixtures/member_details.yml
  64. +3 −0 activerecord/test/fixtures/members.yml
  65. +7 −0 activerecord/test/fixtures/memberships.yml
  66. +1 −0 activerecord/test/fixtures/owners.yml
  67. +28 −0 activerecord/test/fixtures/posts.yml
  68. +14 −0 activerecord/test/fixtures/ratings.yml
  69. +50 −0 activerecord/test/fixtures/taggings.yml
  70. +5 −1 activerecord/test/fixtures/tags.yml
  71. +38 −4 activerecord/test/models/author.rb
  72. +2 −0 activerecord/test/models/book.rb
  73. +2 −0 activerecord/test/models/categorization.rb
  74. +2 −0 activerecord/test/models/category.rb
  75. +1 −0 activerecord/test/models/club.rb
  76. +1 −0 activerecord/test/models/comment.rb
  77. +2 −0 activerecord/test/models/essay.rb
  78. +2 −0 activerecord/test/models/job.rb
  79. +11 −0 activerecord/test/models/member.rb
  80. +2 −0 activerecord/test/models/member_detail.rb
  81. +7 −1 activerecord/test/models/organization.rb
  82. +3 −0 activerecord/test/models/person.rb
  83. +10 −1 activerecord/test/models/post.rb
  84. +4 −0 activerecord/test/models/rating.rb
  85. +2 −0 activerecord/test/models/reference.rb
  86. +2 −0 activerecord/test/models/tagging.rb
  87. +16 −0 activerecord/test/schema/schema.rb
  88. +7 −5 activesupport/lib/active_support/json/backends/yaml.rb
  89. +4 −4 activesupport/test/json/decoding_test.rb
  90. +16 −0 railties/guides/source/active_record_querying.textile
  91. +1 −1 railties/guides/source/caching_with_rails.textile
  92. +1 −1 railties/guides/source/contributing_to_ruby_on_rails.textile
  93. +1 −1 railties/guides/source/getting_started.textile
  94. +1 −1 railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
  95. +8 −0 railties/lib/rails/railtie/configuration.rb
  96. +1 −2 railties/test/generators/shared_generator_tests.rb
View
@@ -38,6 +38,9 @@ platforms :mri_19 do
end
platforms :ruby do
+ if ENV["RB_FSEVENT"]
+ gem 'rb-fsevent'
+ end
gem 'json'
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.4"
@@ -14,7 +14,7 @@ module Callbacks
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
def process_action(method_name, *args)
- run_callbacks(:process_action, method_name) do
+ run_callbacks(:process_action, action_name) do
super
end
end
@@ -107,7 +107,7 @@ def normalize_path(path)
if @options[:format] == false
@options.delete(:format)
path
- elsif path.include?(":format") || path.end_with?('/')
+ elsif path.include?(":format") || path.end_with?('/') || path.match(/^\/?\*/)
path
else
"#{path}(.:format)"
@@ -364,6 +364,13 @@ def root(options = {})
# match 'path' => 'c#a', :defaults => { :format => 'jpg' }
#
# See <tt>Scoping#defaults</tt> for its scope equivalent.
+ #
+ # [:anchor]
+ # Boolean to anchor a #match pattern. Default is true. When set to
+ # false, the pattern matches any request prefixed with the given path.
+ #
+ # # Matches any request starting with 'path'
+ # match 'path' => 'c#a', :anchor => false
def match(path, options=nil)
mapping = Mapping.new(@set, @scope, path, options || {})
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@@ -46,6 +46,13 @@ def test_map_more_slashes
mapper.match '/one/two/', :to => 'posts#index', :as => :main
assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info]
end
+
+ def test_map_wildcard
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path', :to => 'pages#show', :as => :page
+ assert_equal '/*path', fakeset.conditions.first[:path_info]
+ end
end
end
end
@@ -505,6 +505,21 @@ def show
end
end
+ class ImplicitActionsController < ActionController::Base
+ before_filter :find_only, :only => :edit
+ before_filter :find_except, :except => :edit
+
+ private
+
+ def find_only
+ @only = 'Only'
+ end
+
+ def find_except
+ @except = 'Except'
+ end
+ end
+
def test_sweeper_should_not_block_rendering
response = test_process(SweeperTestController)
assert_equal 'hello world', response.body
@@ -783,6 +798,18 @@ def test_a_rescuing_around_filter
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end
+ def test_filters_obey_only_and_except_for_implicit_actions
+ test_process(ImplicitActionsController, 'show')
+ assert_equal 'Except', assigns(:except)
+ assert_nil assigns(:only)
+ assert_equal 'show', response.body
+
+ test_process(ImplicitActionsController, 'edit')
+ assert_equal 'Only', assigns(:only)
+ assert_nil assigns(:except)
+ assert_equal 'edit', response.body
+ end
+
private
def test_process(controller, action = "show")
@controller = controller.is_a?(Class) ? controller.new : controller
@@ -106,8 +106,14 @@ def define_attr_method(name, value=nil, &block)
if block_given?
sing.send :define_method, name, &block
else
- value = value.to_s if value
- sing.send(:define_method, name) { value && value.dup }
+ if name =~ /^[a-zA-Z_]\w*[!?=]?$/
+ sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
+ def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
+ eorb
+ else
+ value = value.to_s if value
+ sing.send(:define_method, name) { value }
+ end
end
end
@@ -9,10 +9,6 @@ class << self
define_method(:bar) do
'original bar'
end
-
- define_method(:zomg) do
- 'original zomg'
- end
end
def attributes
@@ -102,13 +98,6 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar')
end
- def test_defined_methods_always_return_duped_string
- ModelWithAttributes.define_attr_method(:zomg, 'lol')
- assert_equal 'lol', ModelWithAttributes.zomg
- ModelWithAttributes.zomg << 'bbq'
- assert_equal 'lol', ModelWithAttributes.zomg
- end
-
test '#define_attr_method generates attribute method' do
ModelWithAttributes.define_attr_method(:bar, 'bar')
@@ -1,5 +1,11 @@
*Rails 3.1.0 (unreleased)*
+* Associations with a :through option can now use *any* association as the
+ through or source association, including other associations which have a
+ :through option and has_and_belongs_to_many associations
+
+ [Jon Leighton]
+
* The configuration for the current database connection is now accessible via
ActiveRecord::Base.connection_config. [fxn]
@@ -52,14 +52,6 @@ def initialize(reflection)
end
end
- class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
- def initialize(reflection)
- through_reflection = reflection.through_reflection
- source_reflection = reflection.source_reflection
- super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
- end
- end
-
class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
@@ -78,6 +70,12 @@ def initialize(owner, reflection)
end
end
+ class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc
+ def initialize(owner, reflection)
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
+ end
+ end
+
class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc:
def initialize(reflection)
super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).")
@@ -142,8 +140,11 @@ module Builder #:nodoc:
autoload :HasAndBelongsToMany, 'active_record/associations/builder/has_and_belongs_to_many'
end
- autoload :Preloader, 'active_record/associations/preloader'
- autoload :JoinDependency, 'active_record/associations/join_dependency'
+ autoload :Preloader, 'active_record/associations/preloader'
+ autoload :JoinDependency, 'active_record/associations/join_dependency'
+ autoload :AssociationScope, 'active_record/associations/association_scope'
+ autoload :AliasTracker, 'active_record/associations/alias_tracker'
+ autoload :JoinHelper, 'active_record/associations/join_helper'
# Clears out the association cache.
def clear_association_cache #:nodoc:
@@ -548,6 +549,49 @@ def association_instance_set(name, association)
# belongs_to :tag, :inverse_of => :taggings
# end
#
+ # === Nested Associations
+ #
+ # You can actually specify *any* association with the <tt>:through</tt> option, including an
+ # association which has a <tt>:through</tt> option itself. For example:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # has_many :comments, :through => :posts
+ # has_many :commenters, :through => :comments
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # end
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commenter
+ # end
+ #
+ # @author = Author.first
+ # @author.commenters # => People who commented on posts written by the author
+ #
+ # An equivalent way of setting up this association this would be:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # has_many :commenters, :through => :posts
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :comments
+ # has_many :commenters, :through => :comments
+ # end
+ #
+ # class Comment < ActiveRecord::Base
+ # belongs_to :commenter
+ # end
+ #
+ # When using nested association, you will not be able to modify the association because there
+ # is not enough information to know what modification to make. For example, if you tried to
+ # add a <tt>Commenter</tt> in the example above, there would be no way to tell how to set up the
+ # intermediate <tt>Post</tt> and <tt>Comment</tt> objects.
+ #
# === Polymorphic Associations
#
# Polymorphic associations on models are not restricted on what types of models they
@@ -1068,10 +1112,10 @@ module ClassMethods
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
- # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt>,
+ # Specifies an association through which to perform the query. This can be any other type
+ # of association, including other <tt>:through</tt> associations. Options for <tt>:class_name</tt>,
# <tt>:primary_key</tt> and <tt>:foreign_key</tt> are ignored, as the association uses the
- # source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>,
- # <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
+ # source reflection.
#
# If the association on the join model is a +belongs_to+, the collection can be modified
# and the records on the <tt>:through</tt> model will be automatically created and removed
@@ -1198,10 +1242,10 @@ def has_many(name, options = {}, &extension)
# you want to do a join but not include the joined columns. Do not forget to include the
# primary and foreign keys, otherwise it will raise an error.
# [:through]
- # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>
- # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
- # can only use a <tt>:through</tt> query through a <tt>has_one</tt> or <tt>belongs_to</tt>
- # association on the join model.
+ # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
+ # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
+ # source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
+ # or <tt>belongs_to</tt> association on the join model.
# [:source]
# Specifies the source association name used by <tt>has_one :through</tt> queries.
# Only use it if the name cannot be inferred from the association.
@@ -0,0 +1,85 @@
+require 'active_support/core_ext/string/conversions'
+
+module ActiveRecord
+ module Associations
+ # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
+ # ActiveRecord::Associations::ThroughAssociationScope
+ class AliasTracker # :nodoc:
+ attr_reader :aliases, :table_joins
+
+ # table_joins is an array of arel joins which might conflict with the aliases we assign here
+ def initialize(table_joins = [])
+ @aliases = Hash.new
+ @table_joins = table_joins
+ end
+
+ def aliased_table_for(table_name, aliased_name = nil)
+ table_alias = aliased_name_for(table_name, aliased_name)
+
+ if table_alias == table_name
+ Arel::Table.new(table_name)
+ else
+ Arel::Table.new(table_name).alias(table_alias)
+ end
+ end
+
+ def aliased_name_for(table_name, aliased_name = nil)
+ aliased_name ||= table_name
+
+ initialize_count_for(table_name) if aliases[table_name].nil?
+
+ if aliases[table_name].zero?
+ # If it's zero, we can have our table_name
+ aliases[table_name] = 1
+ table_name
+ else
+ # Otherwise, we need to use an alias
+ aliased_name = connection.table_alias_for(aliased_name)
+
+ initialize_count_for(aliased_name) if aliases[aliased_name].nil?
+
+ # Update the count
+ aliases[aliased_name] += 1
+
+ if aliases[aliased_name] > 1
+ "#{truncate(aliased_name)}_#{aliases[aliased_name]}"
+ else
+ aliased_name
+ end
+ end
+ end
+
+ def pluralize(table_name)
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
+ end
+
+ private
+
+ def initialize_count_for(name)
+ aliases[name] = 0
+
+ unless Arel::Table === table_joins
+ # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name = connection.quote_table_name(name).downcase
+
+ aliases[name] += table_joins.map { |join|
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ }.sum
+ end
+
+ aliases[name]
+ end
+
+ def truncate(name)
+ name[0..connection.table_alias_length-3]
+ end
+
+ def connection
+ ActiveRecord::Base.connection
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 5df076a

Please sign in to comment.