diff options
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 |