aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb7
-rw-r--r--actionpack/test/dispatch/cookies_test.rb21
-rw-r--r--activemodel/lib/active_model/validations/validates.rb8
-rw-r--r--activemodel/test/cases/validations/validates_test.rb8
-rw-r--r--activemodel/test/validators/namespace/email_validator.rb6
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency.rb24
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb13
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb71
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb63
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb10
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb8
-rw-r--r--activerecord/test/cases/autosave_association_test.rb11
-rw-r--r--activerecord/test/cases/transactions_test.rb16
-rw-r--r--activerecord/test/models/author.rb8
-rw-r--r--activerecord/test/models/categorization.rb11
-rw-r--r--activerecord/test/models/contract.rb2
-rw-r--r--activerecord/test/models/person.rb1
-rw-r--r--activerecord/test/models/post.rb7
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--railties/guides/source/routing.textile126
-rw-r--r--railties/lib/rails/engine.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb6
-rw-r--r--railties/lib/rails/generators/migration.rb2
-rw-r--r--railties/lib/rails/generators/named_base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb20
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/rails/generator/generator_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb41
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb3
-rw-r--r--railties/test/generators/generator_generator_test.rb39
-rw-r--r--railties/test/generators/model_generator_test.rb18
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb30
-rw-r--r--railties/test/railties/engine_test.rb64
43 files changed, 523 insertions, 188 deletions
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
index 699c44c62c..dce3c2fe88 100644
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ b/actionpack/lib/action_controller/railties/paths.rb
@@ -16,6 +16,14 @@ module ActionController
if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
klass.helper :all
end
+
+ if app.config.serve_static_assets && namespace
+ paths = namespace._railtie.config.paths
+
+ klass.config.assets_dir = paths["public"].first
+ klass.config.javascripts_dir = paths["public/javascripts"].first
+ klass.config.stylesheets_dir = paths["public/stylesheets"].first
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index b0a4e3d949..f369d2d3c2 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -131,8 +131,11 @@ module ActionDispatch
options[:path] ||= "/"
if options[:domain] == :all
- @host =~ DOMAIN_REGEXP
- options[:domain] = ".#{$1}.#{$2}"
+ # if host is not ip and matches domain regexp
+ # (ip confirms to domain regexp so we explicitly check for ip)
+ options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ DOMAIN_REGEXP)
+ ".#{$1}.#{$2}"
+ end
end
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 5ec7f12cc1..e2040401c7 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -295,6 +295,27 @@ class CookiesTest < ActionController::TestCase
assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
end
+ def test_cookie_with_all_domain_option_using_localhost
+ @request.host = "localhost"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_ipv4_address
+ @request.host = "192.168.1.1"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_ipv6_address
+ @request.host = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
def test_deleting_cookie_with_all_domain_option
get :delete_cookie_with_domain
assert_response :success
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 77c5073c6e..0132f68282 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -55,6 +55,10 @@ module ActiveModel
# validates :name, :title => true
# end
#
+ # Additionally validator classes may be in another namespace and still used within any class.
+ #
+ # validates :name, :'file/title' => true
+ #
# The validators hash can also handle regular expressions, ranges,
# arrays and strings in shortcut form, e.g.
#
@@ -86,8 +90,10 @@ module ActiveModel
defaults.merge!(:attributes => attributes)
validations.each do |key, options|
+ key = "#{key.to_s.camelize}Validator"
+
begin
- validator = const_get("#{key.to_s.camelize}Validator")
+ validator = key.include?('::') ? key.constantize : const_get(key)
rescue NameError
raise ArgumentError, "Unknown validator: '#{key}'"
end
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 666c48c8a0..3a9900939e 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -3,6 +3,7 @@ require 'cases/helper'
require 'models/person'
require 'models/person_with_validator'
require 'validators/email_validator'
+require 'validators/namespace/email_validator'
class ValidatesTest < ActiveModel::TestCase
setup :reset_callbacks
@@ -34,6 +35,13 @@ class ValidatesTest < ActiveModel::TestCase
assert_equal ['is not an email'], person.errors[:karma]
end
+ def test_validates_with_namespaced_validator_class
+ Person.validates :karma, :'namespace/email' => true
+ person = Person.new
+ person.valid?
+ assert_equal ['is not an email'], person.errors[:karma]
+ end
+
def test_validates_with_if_as_local_conditions
Person.validates :karma, :presence => true, :email => { :unless => :condition_is_true }
person = Person.new
diff --git a/activemodel/test/validators/namespace/email_validator.rb b/activemodel/test/validators/namespace/email_validator.rb
new file mode 100644
index 0000000000..57e2793ce2
--- /dev/null
+++ b/activemodel/test/validators/namespace/email_validator.rb
@@ -0,0 +1,6 @@
+require 'validators/email_validator'
+
+module Namespace
+ class EmailValidator < ::EmailValidator
+ end
+end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
index b6d85a7c7d..a74d0a4ca8 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
@@ -47,31 +47,19 @@ module ActiveRecord
end
def count_aliases_from_table_joins(name)
- return 0 if !@table_joins || Arel::Table === @table_joins
+ return 0 if Arel::Table === @table_joins
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
quoted_name = active_record.connection.quote_table_name(name).downcase
- @table_joins.grep(Arel::Nodes::Join).map { |join|
- right = join.right
- case right
- when Arel::Table
- right.name.downcase == name ? 1 : 0
- when String
- count_aliases_from_string(right.downcase, quoted_name)
- else
- 0
- end
+ @table_joins.map { |join|
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
}.sum
end
- def count_aliases_from_string(join_sql, name)
- # Table names
- join_sql.scan(/join(?:\s+\w+)?\s+#{name}\son/).size +
- # Table aliases
- join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{name}\son/).size
- end
-
def instantiate(rows)
primary_key = join_base.aliased_primary_key
parents = {}
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
index 98536d23bc..694778008b 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
attr_reader :aliased_prefix, :aliased_table_name
delegate :options, :through_reflection, :source_reflection, :to => :reflection
- delegate :table, :table_name, :to => :parent, :prefix => true
+ delegate :table, :table_name, :to => :parent, :prefix => :parent
def initialize(reflection, join_dependency, parent = nil)
reflection.check_validity!
@@ -37,6 +37,7 @@ module ActiveRecord
@join_dependency = join_dependency
@parent = parent
@join_type = Arel::InnerJoin
+ @aliased_prefix = "t#{ join_dependency.join_parts.size }"
# This must be done eagerly upon initialisation because the alias which is produced
# depends on the state of the join dependency, but we want it to work the same way
@@ -97,7 +98,6 @@ module ActiveRecord
private
def allocate_aliases
- @aliased_prefix = "t#{ join_dependency.join_parts.size }"
@aliased_table_name = aliased_table_name_for(table_name)
if reflection.macro == :has_and_belongs_to_many
@@ -135,9 +135,14 @@ module ActiveRecord
conditions << process_conditions(options[:conditions], aliased_table_name)
end
- join = relation.join(target_table, join_type)
+ ands = relation.create_and(conditions)
- join.on(*conditions)
+ join = relation.create_join(
+ target_table,
+ relation.create_on(ands),
+ join_type)
+
+ relation.from join
end
def join_has_and_belongs_to_many_to(relation)
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index d0c8af1801..781aa7ef62 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -54,7 +54,7 @@ module ActiveRecord
end
def construct_find_options!(options)
- options[:joins] = construct_joins(options[:joins])
+ options[:joins] = [construct_joins] + Array.wrap(options[:joins])
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
end
@@ -81,7 +81,7 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- with_scope(@scope) { @reflection.klass.find(:all) }
+ scoped.all
end
def has_cached_counter?
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 7f28abf464..e8cf73976b 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -33,7 +33,7 @@ module ActiveRecord
private
def find_target
- with_scope(@scope) { @reflection.klass.find(:first) }
+ scoped.first
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index acddfda924..2ecd0f054a 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -3,6 +3,13 @@ module ActiveRecord
module Associations
module ThroughAssociationScope
+ def scoped
+ with_scope(@scope) do
+ @reflection.klass.scoped &
+ @reflection.through_reflection.klass.scoped
+ end
+ end
+
protected
def construct_find_scope
@@ -21,27 +28,33 @@ module ActiveRecord
construct_owner_attributes(@reflection)
end
+ def aliased_through_table
+ name = @reflection.through_reflection.table_name
+
+ @reflection.table_name == name ?
+ @reflection.through_reflection.klass.arel_table.alias(name + "_join") :
+ @reflection.through_reflection.klass.arel_table
+ end
+
# Build SQL conditions from attributes, qualified by table name.
def construct_conditions
- table_name = @reflection.through_reflection.quoted_table_name
+ table = aliased_through_table
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
- "#{table_name}.#{attr} = #{value}"
+ table[attr].eq(value)
end
- conditions << sql_conditions if sql_conditions
- "(" + conditions.join(') AND (') + ")"
+ conditions << Arel.sql(sql_conditions) if sql_conditions
+ table.create_and(conditions)
end
# Associate attributes pointing to owner, quoted.
def construct_quoted_owner_attributes(reflection)
if as = reflection.options[:as]
- { "#{as}_id" => owner_quoted_id,
- "#{as}_type" => reflection.klass.quote_value(
- @owner.class.base_class.name.to_s,
- reflection.klass.columns_hash["#{as}_type"]) }
+ { "#{as}_id" => @owner.id,
+ "#{as}_type" => @owner.class.base_class.name }
elsif reflection.macro == :belongs_to
- { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
+ { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
else
- { reflection.primary_key_name => owner_quoted_id }
+ { reflection.primary_key_name => @owner.id }
end
end
@@ -54,41 +67,43 @@ module ActiveRecord
custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
end
- def construct_joins(custom_joins = nil)
- polymorphic_join = nil
+ def construct_joins
+ right = aliased_through_table
+ left = @reflection.klass.arel_table
+
+ conditions = []
+
if @reflection.source_reflection.macro == :belongs_to
reflection_primary_key = @reflection.klass.primary_key
source_primary_key = @reflection.source_reflection.primary_key_name
if @reflection.options[:source_type]
- polymorphic_join = "AND %s.%s = %s" % [
- @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
- @owner.class.quote_value(@reflection.options[:source_type])
- ]
+ column = @reflection.source_reflection.options[:foreign_type]
+ conditions <<
+ right[column].eq(@reflection.options[:source_type])
end
else
reflection_primary_key = @reflection.source_reflection.primary_key_name
source_primary_key = @reflection.through_reflection.klass.primary_key
if @reflection.source_reflection.options[:as]
- polymorphic_join = "AND %s.%s = %s" % [
- @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
- @owner.class.quote_value(@reflection.through_reflection.klass.name)
- ]
+ column = "#{@reflection.source_reflection.options[:as]}_type"
+ conditions <<
+ left[column].eq(@reflection.through_reflection.klass.name)
end
end
- "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
- @reflection.through_reflection.quoted_table_name,
- @reflection.quoted_table_name, reflection_primary_key,
- @reflection.through_reflection.quoted_table_name, source_primary_key,
- polymorphic_join
- ]
+ conditions <<
+ left[reflection_primary_key].eq(right[source_primary_key])
+
+ right.create_join(
+ right,
+ right.create_on(right.create_and(conditions)))
end
# Construct attributes for associate pointing to owner.
def construct_owner_attributes(reflection)
if as = reflection.options[:as]
{ "#{as}_id" => @owner.id,
- "#{as}_type" => @owner.class.base_class.name.to_s }
+ "#{as}_type" => @owner.class.base_class.name }
else
{ reflection.primary_key_name => @owner.id }
end
@@ -102,7 +117,7 @@ module ActiveRecord
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
if @reflection.options[:source_type]
- join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name)
end
if @reflection.through_reflection.options[:conditions].is_a?(Hash)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index c3dda29d03..4a18719551 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -363,7 +363,7 @@ module ActiveRecord
if autosave && association.marked_for_destruction?
association.destroy
elsif autosave != false
- saved = association.save(:validate => !autosave) if association.new_record? || autosave
+ saved = association.save(:validate => !autosave) if association.new_record? || (autosave && association.changed_for_autosave?)
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index d4aefada22..5b9c48bafa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -12,7 +12,7 @@ module ActiveRecord
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
- table_name[0..table_alias_length-1].gsub(/\./, '_')
+ table_name[0...table_alias_length].gsub(/\./, '_')
end
# def tables(name = nil) end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a07c321960..fe4b518826 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -248,7 +248,7 @@ module ActiveRecord
end
def has_inverse?
- !@options[:inverse_of].nil?
+ @options[:inverse_of]
end
def inverse_of
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 3009bb70c1..7ecba1c43a 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -11,7 +11,6 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
- delegate :insert, :to => :arel
attr_reader :table, :klass, :loaded
attr_accessor :extensions
@@ -28,6 +27,19 @@ module ActiveRecord
@extensions = []
end
+ def insert(values)
+ im = arel.compile_insert values
+ im.into @table
+ primary_key_name = @klass.primary_key
+ primary_key_value = Hash === values ? values[primary_key_name] : nil
+
+ @klass.connection.insert(
+ im.to_sql,
+ 'SQL',
+ primary_key_name,
+ primary_key_value)
+ end
+
def new(*args, &block)
scoping { @klass.new(*args, &block) }
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 906ad7699c..8bc28c06cb 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -187,7 +187,7 @@ module ActiveRecord
def find_with_associations
including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, [])
rows = construct_relation_for_association_find(join_dependency).to_a
join_dependency.instantiate(rows)
rescue ThrowResult
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 08b61c9752..6660df302b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -191,27 +191,19 @@ module ActiveRecord
def custom_join_ast(table, joins)
joins = joins.reject { |join| join.blank? }
- return if joins.empty?
+ return [] if joins.empty?
@implicit_readonly = true
- joins.map! do |join|
+ joins.map do |join|
case join
when Array
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
when String
join = Arel.sql(join)
end
- join
+ table.create_string_join(join)
end
-
- head = table.create_string_join(table, joins.shift)
-
- joins.inject(head) do |ast, join|
- ast.right = table.create_string_join(ast.right, join)
- end
-
- head
end
def collapse_wheres(arel, wheres)
@@ -225,14 +217,13 @@ module ActiveRecord
test = eqls.inject(eqls.shift) do |memo, expr|
memo.or(expr)
end
- arel = arel.where(test)
+ arel.where(test)
end
(wheres - equalities).each do |where|
where = Arel.sql(where) if String === where
- arel = arel.where(Arel::Nodes::Grouping.new(where))
+ arel.where(Arel::Nodes::Grouping.new(where))
end
- arel
end
def build_where(opts, other = [])
@@ -248,18 +239,39 @@ module ActiveRecord
end
def build_joins(manager, joins)
- joins = joins.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
-
- association_joins = joins.find_all do |join|
- [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
+ buckets = joins.group_by do |join|
+ case join
+ when String
+ 'string_join'
+ when Hash, Symbol, Array
+ 'association_join'
+ when ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
+ 'stashed_join'
+ when Arel::Nodes::Join
+ 'join_node'
+ else
+ raise 'unknown class: %s' % join.class.name
+ end
end
- stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
+ association_joins = buckets['association_join'] || []
+ stashed_association_joins = buckets['stashed_join'] || []
+ join_nodes = buckets['join_node'] || []
+ string_joins = (buckets['string_join'] || []).map { |x|
+ x.strip
+ }.uniq
- non_association_joins = (joins - association_joins - stashed_association_joins)
- join_ast = custom_join_ast(manager.froms.first, non_association_joins)
+ join_list = custom_join_ast(manager, string_joins)
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, join_ast)
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(
+ @klass,
+ association_joins,
+ join_list
+ )
+
+ join_nodes.each do |join|
+ join_dependency.table_aliases[join.left.name.downcase] = 1
+ end
join_dependency.graft(*stashed_association_joins)
@@ -267,13 +279,12 @@ module ActiveRecord
# FIXME: refactor this to build an AST
join_dependency.join_associations.each do |association|
- manager = association.join_to(manager)
+ association.join_to(manager)
end
- return manager unless join_ast
+ manager.join_sources.concat join_nodes.uniq
+ manager.join_sources.concat join_list
- join_ast.left = manager.froms.first
- manager.from join_ast
manager
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 52432b0428..44ff01ddc0 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -17,6 +17,8 @@ require 'models/developer'
require 'models/subscriber'
require 'models/book'
require 'models/subscription'
+require 'models/categorization'
+require 'models/category'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors,
@@ -388,6 +390,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
+ def test_has_many_association_through_a_has_many_association_to_self
+ sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1)
+ john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1)
+ assert_equal sarah.agents, [john]
+ assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents
+ end
+
def test_collection_singular_ids_getter_with_string_primary_keys
book = books(:awdr)
assert_equal 2, book.subscriber_ids.size
@@ -455,4 +464,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post.people << people(:michael)
assert_equal readers + 1, post.readers.size
end
+
+ def test_has_many_through_with_default_scope_on_join_model
+ assert_equal posts(:welcome).comments, authors(:david).comments_on_first_posts
+ end
+
+ def test_create_has_many_through_with_default_scope_on_join_model
+ category = authors(:david).special_categories.create(:name => "Foo")
+ assert_equal 1, category.categorizations.where(:special => true).count
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index ac43e571cb..93a4f498d0 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -9,9 +9,13 @@ require 'models/member_detail'
require 'models/minivan'
require 'models/dashboard'
require 'models/speedometer'
+require 'models/author'
+require 'models/post'
+require 'models/comment'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans,
+ :dashboards, :speedometers, :authors, :posts, :comments
def setup
@member = members(:groucho)
@@ -229,4 +233,8 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
minivan.dashboard
end
end
+
+ def test_has_one_through_with_default_scope_on_join_model
+ assert_equal posts(:welcome).comments.first, authors(:david).comment_on_first_posts
+ end
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 780eabc443..da2a81e98a 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -65,21 +65,21 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'")
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
-
+
def test_find_with_sti_join
scope = Post.joins(:special_comments).where(:id => posts(:sti_comments).id)
-
+
# The join should match SpecialComment and its subclasses only
assert scope.where("comments.type" => "Comment").empty?
assert !scope.where("comments.type" => "SpecialComment").empty?
assert !scope.where("comments.type" => "SubSpecialComment").empty?
end
-
+
def test_find_with_conditions_on_reflection
assert !posts(:welcome).comments.empty?
assert Post.joins(:nonexistant_comments).where(:id => posts(:welcome).id).empty? # [sic!]
end
-
+
def test_find_with_conditions_on_through_reflection
assert !posts(:welcome).tags.empty?
assert Post.joins(:misc_tags).where(:id => posts(:welcome).id).empty?
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index fbf7121468..27aee400f9 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -667,10 +667,21 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
end
+ @ship.pirate.catchphrase = "Changed Catchphrase"
+
assert_raise(RuntimeError) { assert !@ship.save }
assert_not_nil @ship.reload.pirate
end
+ def test_should_save_changed_child_objects_if_parent_is_saved
+ @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
+ @parrot = @pirate.parrots.create!(:name => 'Posideons Killer')
+ @parrot.name = "NewName"
+ @ship.save
+
+ assert_equal 'NewName', @parrot.reload.name
+ end
+
# has_many & has_and_belongs_to
%w{ parrots birds }.each do |association_name|
define_method("test_should_destroy_#{association_name}_as_part_of_the_save_transaction_if_they_were_marked_for_destroyal") do
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index b0ccd71836..110a18772f 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -370,23 +370,23 @@ class TransactionTest < ActiveRecord::TestCase
assert topic_2.save
@first.save
@second.destroy
- assert_equal true, topic_1.persisted?
+ assert topic_1.persisted?, 'persisted'
assert_not_nil topic_1.id
- assert_equal true, topic_2.persisted?
+ assert topic_2.persisted?, 'persisted'
assert_not_nil topic_2.id
- assert_equal true, @first.persisted?
+ assert @first.persisted?, 'persisted'
assert_not_nil @first.id
- assert_equal true, @second.destroyed?
+ assert @second.destroyed?, 'destroyed'
raise ActiveRecord::Rollback
end
- assert_equal false, topic_1.persisted?
+ assert !topic_1.persisted?, 'not persisted'
assert_nil topic_1.id
- assert_equal false, topic_2.persisted?
+ assert !topic_2.persisted?, 'not persisted'
assert_nil topic_2.id
- assert_equal true, @first.persisted?
+ assert @first.persisted?, 'persisted'
assert_not_nil @first.id
- assert_equal false, @second.destroyed?
+ assert !@second.destroyed?, 'not destroyed'
end
if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE)
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 34bfd2d881..fd6d2b384a 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -26,6 +26,10 @@ class Author < ActiveRecord::Base
has_many :comments_with_order_and_conditions, :through => :posts, :source => :comments, :order => 'comments.body', :conditions => "comments.body like 'Thank%'"
has_many :comments_with_include, :through => :posts, :source => :comments, :include => :post
+ has_many :first_posts
+ has_many :comments_on_first_posts, :through => :first_posts, :source => :comments, :order => 'posts.id desc, comments.id asc'
+ has_one :comment_on_first_posts, :through => :first_posts, :source => :comments, :order => 'posts.id desc, comments.id asc'
+
has_many :thinking_posts, :class_name => 'Post', :conditions => { :title => 'So I was thinking' }, :dependent => :delete_all
has_many :welcome_posts, :class_name => 'Post', :conditions => { :title => 'Welcome to the weblog' }
@@ -73,6 +77,10 @@ class Author < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
+ has_many :special_categorizations
+ has_many :special_categories, :through => :special_categorizations, :source => :category
+ has_one :special_category, :through => :special_categorizations, :source => :category
+
has_many :categories_like_general, :through => :categorizations, :source => :category, :class_name => 'Category', :conditions => { :name => 'General' }
has_many :categorized_posts, :through => :categorizations, :source => :post
diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb
index 10594323ff..b3fc29fa15 100644
--- a/activerecord/test/models/categorization.rb
+++ b/activerecord/test/models/categorization.rb
@@ -2,4 +2,13 @@ class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
belongs_to :author
-end \ No newline at end of file
+end
+
+class SpecialCategorization < ActiveRecord::Base
+ self.table_name = 'categorizations'
+
+ default_scope where(:special => true)
+
+ belongs_to :author
+ belongs_to :category
+end
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
index 606c99cd4e..94fd48e12a 100644
--- a/activerecord/test/models/contract.rb
+++ b/activerecord/test/models/contract.rb
@@ -1,4 +1,4 @@
class Contract < ActiveRecord::Base
belongs_to :company
belongs_to :developer
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 951ec93c53..bee89de042 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -12,6 +12,7 @@ class Person < ActiveRecord::Base
belongs_to :primary_contact, :class_name => 'Person'
has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id'
+ has_many :agents_of_agents, :through => :agents, :source => :agents
belongs_to :number1_fan, :class_name => 'Person'
scope :males, :conditions => { :gender => 'M' }
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 61e782ff14..164b499bf0 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -119,3 +119,10 @@ class PostForAuthor < ActiveRecord::Base
cattr_accessor :selected_author
default_scope lambda { where(:author_id => PostForAuthor.selected_author) }
end
+
+class FirstPost < ActiveRecord::Base
+ self.table_name = 'posts'
+ default_scope where(:id => 1)
+ has_many :comments, :foreign_key => :post_id
+ has_one :comment, :foreign_key => :post_id
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index d4eb56c4d7..177045ac51 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -112,6 +112,7 @@ ActiveRecord::Schema.define do
t.column :category_id, :integer
t.column :post_id, :integer
t.column :author_id, :integer
+ t.column :special, :boolean
end
create_table :citations, :force => true do |t|
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 74dee60c32..32346e8f2b 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -76,14 +76,14 @@ resources :photos
creates seven different routes in your application, all mapping to the +Photos+ controller:
-|_. Verb |_.Path |_.action |_.used for|
-|GET |/photos |index |display a list of all photos|
-|GET |/photos/new |new |return an HTML form for creating a new photo|
-|POST |/photos |create |create a new photo|
-|GET |/photos/:id |show |display a specific photo|
-|GET |/photos/:id/edit |edit |return an HTML form for editing a photo|
-|PUT |/photos/:id |update |update a specific photo|
-|DELETE |/photos/:id |destroy |delete a specific photo|
+|_. HTTP Verb |_.Path |_.action |_.used for |
+|GET |/photos |index |display a list of all photos |
+|GET |/photos/new |new |return an HTML form for creating a new photo |
+|POST |/photos |create |create a new photo |
+|GET |/photos/:id |show |display a specific photo |
+|GET |/photos/:id/edit |edit |return an HTML form for editing a photo |
+|PUT |/photos/:id |update |update a specific photo |
+|DELETE |/photos/:id |destroy |delete a specific photo |
h4. Paths and URLs
@@ -130,13 +130,13 @@ resource :geocoder
creates six different routes in your application, all mapping to the +Geocoders+ controller:
-|_. Verb |_.Path |_.action |_.used for|
-|GET |/geocoder/new |new |return an HTML form for creating the geocoder|
-|POST |/geocoder |create |create the new geocoder|
-|GET |/geocoder |show |display the one and only geocoder resource|
-|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder|
-|PUT |/geocoder |update |update the one and only geocoder resource|
-|DELETE |/geocoder |destroy |delete the geocoder resource|
+|_.HTTP Verb |_.Path |_.action |_.used for |
+|GET |/geocoder/new |new |return an HTML form for creating the geocoder |
+|POST |/geocoder |create |create the new geocoder |
+|GET |/geocoder |show |display the one and only geocoder resource |
+|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder |
+|PUT |/geocoder |update |update the one and only geocoder resource |
+|DELETE |/geocoder |destroy |delete the geocoder resource |
NOTE: Because you might want to use the same controller for a singular route (+/account+) and a plural route (+/accounts/45+), singular resources map to plural controllers.
@@ -160,14 +160,14 @@ end
This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create:
-|_. Verb |_.Path |_.action |_. helper |
-|GET |/admin/posts |index | admin_posts_path |
-|GET |/admin/posts/new |new | new_admin_posts_path |
-|POST |/admin/posts |create | admin_posts_path |
-|GET |/admin/posts/1 |show | admin_post_path(id) |
-|GET |/admin/posts/1/edit |edit | edit_admin_post_path(id) |
-|PUT |/admin/posts/1 |update | admin_post_path(id) |
-|DELETE |/admin/posts/1 |destroy | admin_post_path(id) |
+|_.HTTP Verb |_.Path |_.action |_.named helper |
+|GET |/admin/posts |index | admin_posts_path |
+|GET |/admin/posts/new |new | new_admin_posts_path |
+|POST |/admin/posts |create | admin_posts_path |
+|GET |/admin/posts/1 |show | admin_post_path(id) |
+|GET |/admin/posts/1/edit |edit | edit_admin_post_path(id) |
+|PUT |/admin/posts/1 |update | admin_post_path(id) |
+|DELETE |/admin/posts/1 |destroy | admin_post_path(id) |
If you want to route +/posts+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
@@ -199,14 +199,14 @@ resources :posts, :path => "/admin/posts"
In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following paths map to +PostsController+:
-|_. Verb |_.Path |_.action |_. helper |
-|GET |/admin/posts |index | posts_path |
-|GET |/admin/posts/new |new | posts_path |
-|POST |/admin/posts |create | posts_path |
-|GET |/admin/posts/1 |show | post_path(id) |
-|GET |/admin/posts/1/edit |edit | edit_post_path(id) |
-|PUT |/admin/posts/1 |update | post_path(id) |
-|DELETE |/admin/posts/1 |destroy | post_path(id) |
+|_.HTTP Verb |_.Path |_.action |_.named helper |
+|GET |/admin/posts |index | posts_path |
+|GET |/admin/posts/new |new | posts_path |
+|POST |/admin/posts |create | posts_path |
+|GET |/admin/posts/1 |show | post_path(id) |
+|GET |/admin/posts/1/edit |edit | edit_post_path(id) |
+|PUT |/admin/posts/1 |update | post_path(id) |
+|DELETE |/admin/posts/1 |destroy | post_path(id) |
h4. Nested Resources
@@ -232,14 +232,14 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.Verb |_.Path |_.action |_.used for|
-|GET |/magazines/1/ads |index |display a list of all ads for a specific magazine|
-|GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine|
-|POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine|
-|GET |/magazines/1/ads/1 |show |display a specific ad belonging to a specific magazine|
-|GET |/magazines/1/ads/1/edit |edit |return an HTML form for editing an ad belonging to a specific magazine|
-|PUT |/magazines/1/ads/1 |update |update a specific ad belonging to a specific magazine|
-|DELETE |/magazines/1/ads/1 |destroy |delete a specific ad belonging to a specific magazine|
+|_.HTTP Verb |_.Path |_.action |_.used for |
+|GET |/magazines/1/ads |index |display a list of all ads for a specific magazine |
+|GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
+|POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine |
+|GET |/magazines/1/ads/1 |show |display a specific ad belonging to a specific magazine |
+|GET |/magazines/1/ads/1/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
+|PUT |/magazines/1/ads/1 |update |update a specific ad belonging to a specific magazine |
+|DELETE |/magazines/1/ads/1 |destroy |delete a specific ad belonging to a specific magazine |
This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+).
@@ -611,14 +611,14 @@ resources :photos, :controller => "images"
will recognize incoming paths beginning with +/photos+ but route to the +Images+ controller:
-|_. Verb |_.Path |_.action |
-|GET |/photos |index |
-|GET |/photos/new |new |
-|POST |/photos |create |
-|GET |/photos/1 |show |
-|GET |/photos/1/edit |edit |
-|PUT |/photos/1 |update |
-|DELETE |/photos/1 |destroy |
+|_.HTTP Verb |_.Path |_.action |_.named helper |
+|GET |/photos |index | photos_path |
+|GET |/photos/new |new | new_photo_path |
+|POST |/photos |create | photos_path |
+|GET |/photos/1 |show | photo_path(id) |
+|GET |/photos/1/edit |edit | edit_photo_path(id) |
+|PUT |/photos/1 |update | photo_path(id) |
+|DELETE |/photos/1 |destroy | photo_path(id) |
NOTE: Use +photos_path+, +new_photos_path+, etc. to generate paths for this resource.
@@ -653,14 +653,14 @@ resources :photos, :as => "images"
will recognize incoming paths beginning with +/photos+ and route the requests to +PhotosController+, but use the value of the :as option to name the helpers.
-|_.HTTP verb|_.Path |_.action |_.named helper |
-|GET |/photos |index | images_path |
-|GET |/photos/new |new | new_image_path |
-|POST |/photos |create | images_path |
-|GET |/photos/1 |show | image_path |
-|GET |/photos/1/edit |edit | edit_image_path |
-|PUT |/photos/1 |update | image_path |
-|DELETE |/photos/1 |destroy | image_path |
+|_.HTTP verb|_.Path |_.action |_.named helper |
+|GET |/photos |index | images_path |
+|GET |/photos/new |new | new_image_path |
+|POST |/photos |create | images_path |
+|GET |/photos/1 |show | image_path(id) |
+|GET |/photos/1/edit |edit | edit_image_path(id) |
+|PUT |/photos/1 |update | image_path(id) |
+|DELETE |/photos/1 |destroy | image_path(id) |
h4. Overriding the +new+ and +edit+ Segments
@@ -745,14 +745,14 @@ end
Rails now creates routes to the +CategoriesController+.
-|_.HTTP verb|_.Path |_.action |
-|GET |/kategorien |index |
-|GET |/kategorien/neu |new |
-|POST |/kategorien |create |
-|GET |/kategorien/1 |show |
-|GET |/kategorien/:id/bearbeiten |edit |
-|PUT |/kategorien/1 |update |
-|DELETE |/kategorien/1 |destroy |
+|_.HTTP verb|_.Path |_.action |_.named helper |
+|GET |/kategorien |index | categories_path |
+|GET |/kategorien/neu |new | new_category_path |
+|POST |/kategorien |create | categories_path |
+|GET |/kategorien/1 |show | category_path(id) |
+|GET |/kategorien/1/bearbeiten |edit | edit_category_path(id) |
+|PUT |/kategorien/1 |update | category_path(id) |
+|DELETE |/kategorien/1 |destroy | category_path(id) |
h4. Overriding the Singular Form
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index cda0e0a135..1d81e08cd3 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -207,7 +207,7 @@ module Rails
# end
# end
#
- # == Namespaced Engine
+ # == Isolated Engine
#
# Normally when you create controllers, helpers and models inside engine, they are treated
# as they were created inside the application. This means all applications helpers and named routes
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 2d0c10efca..7766050632 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -171,6 +171,12 @@ gem 'rails', '#{Rails::VERSION::STRING}'
def dev_or_edge?
options.dev? || options.edge?
end
+
+ def empty_directory_with_gitkeep(destination, config = {})
+ empty_directory(destination, config)
+ create_file("#{destination}/.gitkeep") unless options[:skip_git]
+ end
+
end
end
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 8d98909055..0c5c4f6e09 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -52,7 +52,7 @@ module Rails
destination = self.class.migration_exists?(migration_dir, @migration_file_name)
- if behavior == :invoke
+ if !(destination && options[:skip]) && behavior == :invoke
if destination && options.force?
remove_file(destination)
elsif destination
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index e0dde4360f..44a2639488 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -42,7 +42,7 @@ module Rails
end
def wrap_with_namespace(content)
- content = indent(content)
+ content = indent(content).chomp
"module #{namespace.name}\n#{content}\nend\n"
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index ef1eb8d237..3f6ff35a86 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -63,7 +63,7 @@ module Rails
end
def database_yml
- template "config/databases/#{@options[:database]}.yml", "config/database.yml"
+ template "config/databases/#{options[:database]}.yml", "config/database.yml"
end
def db
@@ -104,18 +104,18 @@ module Rails
def javascripts
empty_directory "public/javascripts"
-
+
unless options[:skip_javascript]
- copy_file "public/javascripts/#{@options[:javascript]}.js"
- copy_file "public/javascripts/#{@options[:javascript]}_ujs.js", "public/javascripts/rails.js"
-
- if options[:prototype]
+ copy_file "public/javascripts/#{options[:javascript]}.js"
+ copy_file "public/javascripts/#{options[:javascript]}_ujs.js", "public/javascripts/rails.js"
+
+ if options[:javascript] == "prototype"
copy_file "public/javascripts/controls.js"
copy_file "public/javascripts/dragdrop.js"
copy_file "public/javascripts/effects.js"
end
end
-
+
copy_file "public/javascripts/application.js"
end
@@ -284,6 +284,7 @@ module Rails
def app_const_base
@app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize
end
+ alias :camelized :app_const_base
def app_const
@app_const ||= "#{app_const_base}::Application"
@@ -317,11 +318,6 @@ module Rails
].find { |f| File.exist?(f) } unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
end
- def empty_directory_with_gitkeep(destination, config = {})
- empty_directory(destination, config)
- create_file("#{destination}/.gitkeep") unless options[:skip_git]
- end
-
def get_builder_class
defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder
end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
index 1de78eecae..6d56c331c1 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
- <title><%= app_const_base %></title>
+ <title><%= camelized %></title>
<%%= stylesheet_link_tag :all %>
<%%= javascript_include_tag :defaults %>
<%%= csrf_meta_tags %>
diff --git a/railties/lib/rails/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb
index 5b5d1884bc..3e0a442bda 100644
--- a/railties/lib/rails/generators/rails/generator/generator_generator.rb
+++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb
@@ -14,9 +14,9 @@ module Rails
def generator_dir
if options[:namespace]
- File.join("lib", "generators", file_name)
+ File.join("lib", "generators", regular_class_path, file_name)
else
- File.join("lib", "generators")
+ File.join("lib", "generators", regular_class_path)
end
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 9c54b98238..9461589ff5 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -8,7 +8,11 @@ module Rails
end
def app
- directory "app" if options[:mountable]
+ if options[:mountable]
+ directory "app"
+ template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt",
+ "app/views/layouts/#{name}/application.html.erb"
+ end
end
def readme
@@ -88,6 +92,29 @@ task :default => :test
end
end
+ def stylesheets
+ empty_directory_with_gitkeep "public/stylesheets" if options[:mountable]
+ end
+
+ def javascripts
+ return unless options[:mountable]
+
+ empty_directory "#{app_templates_dir}/public/javascripts"
+
+ unless options[:skip_javascript]
+ copy_file "#{app_templates_dir}/public/javascripts/#{options[:javascript]}.js", "public/javascripts/#{options[:javascript]}.js"
+ copy_file "#{app_templates_dir}/public/javascripts/#{options[:javascript]}_ujs.js", "public/javascripts/rails.js"
+
+ if options[:javascript] == "prototype"
+ copy_file "#{app_templates_dir}/public/javascripts/controls.js", "public/javascripts/controls.js"
+ copy_file "#{app_templates_dir}/public/javascripts/dragdrop.js", "public/javascripts/dragdrop.js"
+ copy_file "#{app_templates_dir}/public/javascripts/effects.js", "public/javascripts/effects.js"
+ end
+ end
+
+ copy_file "#{app_templates_dir}/public/javascripts/application.js", "public/javascripts/application.js"
+ end
+
def script(force = false)
directory "script", :force => force do |content|
"#{shebang}\n" + content
@@ -143,6 +170,14 @@ task :default => :test
build(:lib)
end
+ def create_public_stylesheets_files
+ build(:stylesheets)
+ end
+
+ def create_javascript_files
+ build(:javascripts)
+ end
+
def create_script_files
build(:script)
end
@@ -163,6 +198,10 @@ task :default => :test
public_task :apply_rails_template, :bundle_if_dev_or_edge
protected
+ def app_templates_dir
+ "../../app/templates"
+ end
+
def create_dummy_app(path = nil)
dummy_path(path) if path
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb
index 9600ee0c3f..aa8ea77bae 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb
@@ -1,7 +1,7 @@
module <%= camelized %>
class Engine < Rails::Engine
<% if mountable? -%>
- isolate_namespace <%= camelized %>
+ isolate_namespace <%= camelized %>
<% end -%>
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 7faa674a81..02c49ab241 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -135,6 +135,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "public/javascripts/application.js"
assert_file "public/javascripts/prototype.js"
assert_file "public/javascripts/rails.js"
+ assert_file "public/javascripts/controls.js"
+ assert_file "public/javascripts/dragdrop.js"
+ assert_file "public/javascripts/dragdrop.js"
assert_file "test"
end
diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb
index 26f975a191..f4c975fc18 100644
--- a/railties/test/generators/generator_generator_test.rb
+++ b/railties/test/generators/generator_generator_test.rb
@@ -17,4 +17,43 @@ class GeneratorGeneratorTest < Rails::Generators::TestCase
assert_file "lib/generators/awesome/awesome_generator.rb",
/class AwesomeGenerator < Rails::Generators::NamedBase/
end
+
+ def test_namespaced_generator_skeleton
+ run_generator ["rails/awesome"]
+
+ %w(
+ lib/generators/rails/awesome
+ lib/generators/rails/awesome/USAGE
+ lib/generators/rails/awesome/templates
+ ).each{ |path| assert_file path }
+
+ assert_file "lib/generators/rails/awesome/awesome_generator.rb",
+ /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/
+ end
+
+ def test_generator_skeleton_is_created_without_file_name_namespace
+ run_generator ["awesome", "--namespace", "false"]
+
+ %w(
+ lib/generators/
+ lib/generators/USAGE
+ lib/generators/templates
+ ).each{ |path| assert_file path }
+
+ assert_file "lib/generators/awesome_generator.rb",
+ /class AwesomeGenerator < Rails::Generators::NamedBase/
+ end
+
+ def test_namespaced_generator_skeleton_without_file_name_namespace
+ run_generator ["rails/awesome", "--namespace", "false"]
+
+ %w(
+ lib/generators/rails
+ lib/generators/rails/USAGE
+ lib/generators/rails/templates
+ ).each{ |path| assert_file path }
+
+ assert_file "lib/generators/rails/awesome_generator.rb",
+ /class Rails::AwesomeGenerator < Rails::Generators::NamedBase/
+ end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 8a0f560bc8..552b7eb30a 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -147,10 +147,22 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
end
- def test_migration_already_exists_error_message
+ def test_migration_is_skipped_with_skip_option
run_generator
- error = capture(:stderr){ run_generator ["Account"], :behavior => :skip }
- assert_match /Another migration is already named create_accounts/, error
+ output = run_generator ["Account", "--skip"]
+ assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output
+ end
+
+ def test_migration_is_ignored_as_identical_with_skip_option
+ run_generator ["Account"]
+ output = run_generator ["Account", "--skip"]
+ assert_match %r{identical\s+db/migrate/\d+_create_accounts.rb}, output
+ end
+
+ def test_migration_is_skipped_on_skip_behavior
+ run_generator
+ output = run_generator ["Account"], :behavior => :skip
+ assert_match %r{skip\s+db/migrate/\d+_create_accounts.rb}, output
end
def test_migration_error_is_not_shown_on_revoke
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 0d24821ff6..2a9e8046b8 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -91,6 +91,35 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "test/dummy/config/database.yml", /postgres/
end
+ def test_skipping_javascripts_without_mountable_option
+ run_generator
+ assert_no_file "public/javascripts/prototype.js"
+ assert_no_file "public/javascripts/rails.js"
+ assert_no_file "public/javascripts/controls.js"
+ assert_no_file "public/javascripts/dragdrop.js"
+ assert_no_file "public/javascripts/dragdrop.js"
+ assert_no_file "public/javascripts/application.js"
+ end
+
+ def test_javascripts_generation
+ run_generator [destination_root, "--mountable"]
+ assert_file "public/javascripts/rails.js"
+ assert_file "public/javascripts/prototype.js"
+ assert_file "public/javascripts/controls.js"
+ assert_file "public/javascripts/dragdrop.js"
+ assert_file "public/javascripts/dragdrop.js"
+ assert_file "public/javascripts/application.js"
+ end
+
+ def test_skip_javascripts
+ run_generator [destination_root, "--skip-javascript", "--mountable"]
+ assert_no_file "public/javascripts/prototype.js"
+ assert_no_file "public/javascripts/rails.js"
+ assert_no_file "public/javascripts/controls.js"
+ assert_no_file "public/javascripts/dragdrop.js"
+ assert_no_file "public/javascripts/dragdrop.js"
+ end
+
def test_ensure_that_javascript_option_is_passed_to_app_generator
run_generator [destination_root, "--javascript", "jquery"]
assert_file "test/dummy/public/javascripts/jquery.js"
@@ -137,6 +166,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/
assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/
assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/
+ assert_file "app/views/layouts/bukkits/application.html.erb", /<title>Bukkits<\/title>/
end
def test_passing_dummy_path_as_a_parameter
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 05bd0c36cd..6b64a19741 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -721,5 +721,69 @@ module RailtiesTest
engine_path = File.join(@plugin.path, '..', engine_dir)
assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path)
end
+
+ test "ensure that engine properly sets assets directories" do
+ add_to_config("config.action_dispatch.show_exceptions = false")
+ add_to_config("config.serve_static_assets = true")
+
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ @plugin.write "public/stylesheets/foo.css", ""
+ @plugin.write "public/javascripts/foo.js", ""
+
+ @plugin.write "app/views/layouts/bukkits/application.html.erb", <<-RUBY
+ <%= stylesheet_link_tag :all %>
+ <%= javascript_include_tag :all %>
+ <%= yield %>
+ RUBY
+
+ @plugin.write "app/controllers/bukkits/home_controller.rb", <<-RUBY
+ module Bukkits
+ class HomeController < ActionController::Base
+ def index
+ render :text => "Good morning!", :layout => "bukkits/application"
+ end
+ end
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw do
+ match "/home" => "home#index"
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ mount Bukkits::Engine => "/bukkits"
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ boot_rails
+
+ require "#{rails_root}/config/environment"
+
+ assert_equal File.join(@plugin.path, "public"), Bukkits::HomeController.assets_dir
+ assert_equal File.join(@plugin.path, "public/stylesheets"), Bukkits::HomeController.stylesheets_dir
+ assert_equal File.join(@plugin.path, "public/javascripts"), Bukkits::HomeController.javascripts_dir
+
+ assert_equal File.join(app_path, "public"), ActionController::Base.assets_dir
+ assert_equal File.join(app_path, "public/stylesheets"), ActionController::Base.stylesheets_dir
+ assert_equal File.join(app_path, "public/javascripts"), ActionController::Base.javascripts_dir
+
+ get "/bukkits/home"
+
+ assert_match %r{bukkits/stylesheets/foo.css}, last_response.body
+ assert_match %r{bukkits/javascripts/foo.js}, last_response.body
+ end
end
end