diff options
author | eparreno <emili@eparreno.com> | 2010-11-15 22:16:17 +0100 |
---|---|---|
committer | eparreno <emili@eparreno.com> | 2010-11-15 22:16:17 +0100 |
commit | ca4b4c0baa240baea0eaf372283dbd33ec671b6a (patch) | |
tree | 0ca0ee53b1d03d76f82c3ba481da53c1e5499084 /railties | |
parent | 04775a41cbbddceeffa9cf8590566b9990fd4070 (diff) | |
parent | 91a6db90cf8b2c07af4cf64a9c587268106aadd5 (diff) | |
download | rails-ca4b4c0baa240baea0eaf372283dbd33ec671b6a.tar.gz rails-ca4b4c0baa240baea0eaf372283dbd33ec671b6a.tar.bz2 rails-ca4b4c0baa240baea0eaf372283dbd33ec671b6a.zip |
Merge branch 'master' of github.com:lifo/docrails
Diffstat (limited to 'railties')
70 files changed, 1998 insertions, 726 deletions
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index c02e9f1912..b39075f101 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -239,7 +239,7 @@ class LoginsController < ApplicationController # "Delete" a login, aka "log the user out" def destroy # Remove the user id from the session - session[:current_user_id] = nil + @_current_user = session[:current_user_id] = nil redirect_to root_url end end @@ -261,6 +261,13 @@ class LoginsController < ApplicationController end </ruby> +Note it is also possible to assign a flash message as part of the redirection. + +<ruby> +redirect_to root_url, :notice => "You have successfully logged out" +</ruby> + + The +destroy+ action redirects to the application's +root_url+, where the message will be displayed. Note that it's entirely up to the next action to decide what, if anything, it will do with what the previous action put in the flash. It's conventional to display eventual errors or notices from the flash in the application's layout: <ruby> diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 41738827b2..8d2ce44e93 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -446,7 +446,7 @@ The following configuration options are best made in one of the environment file |sendmail_settings|Allows you to override options for the :sendmail delivery method.<ul><li>:location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail.</li><li>:arguments - The command line arguments to be passed to sendmail. Defaults to -i -t.</li></ul>| |raise_delivery_errors|Whether or not errors should be raised if the email fails to be delivered.| |delivery_method|Defines a delivery method. Possible values are :smtp (default), :sendmail, :file and :test.| -|perform_deliveries|Determines whether deliver_* methods are actually carried out. By default they are, but this can be turned off to help functional testing.| +|perform_deliveries|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| |deliveries|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| h4. Example Action Mailer Configuration diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 9b1d264d2c..dc1200812e 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -167,6 +167,12 @@ def log_info(sql, name, ms) end </ruby> ++try+ can also be called without arguments but a block, which will only be executed if the object is not nil: + +<ruby> +@person.try { |p| "#{p.first_name} #{p.last_name}" } +</ruby> + NOTE: Defined in +active_support/core_ext/object/try.rb+. h4. +singleton_class+ @@ -395,39 +401,6 @@ C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. -h5. +copy_instance_variables_from(object, exclude = [])+ - -Copies the instance variables of +object+ into +self+. - -Instance variable names in the +exclude+ array are ignored. If +object+ -responds to +protected_instance_variables+ the ones returned are -also ignored. For example, Rails controllers implement that method. - -In both arrays strings and symbols are understood, and they have to include -the at sign. - -<ruby> -class C - def initialize(x, y, z) - @x, @y, @z = x, y, z - end - - def protected_instance_variables - %w(@z) - end -end - -a = C.new(0, 1, 2) -b = C.new(3, 4, 5) - -a.copy_instance_variables_from(b, [:@y]) -# a is now: @x = 3, @y = 1, @z = 2 -</ruby> - -In the example +object+ and +self+ are of the same type, but they don't need to. - -NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. - h4. Silencing Warnings, Streams, and Exceptions The methods +silence_warnings+ and +enable_warnings+ change the value of +$VERBOSE+ accordingly for the duration of their block, and reset it afterwards: diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index 900b3fb5d5..e3ccd6396c 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -116,14 +116,12 @@ Use fixed-width fonts for: * file names <ruby> -# Copies the instance variables of +object+ into +self+. -# -# Instance variable names in the +exclude+ array are ignored. If +object+ -# responds to <tt>protected_instance_variables</tt> the ones returned are -# also ignored. For example, Rails controllers implement that method. -# ... -def copy_instance_variables_from(object, exclude = []) - ... +class Array + # Calls <tt>to_param</tt> on all its elements and joins the result with + # slashes. This is used by <tt>url_for</tt> in Action Pack. + def to_param + collect { |e| e.to_param }.join '/' + end end </ruby> diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index bb38c64307..28fff5c11e 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -32,7 +32,7 @@ config.filter_parameters << :password This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same +config+ object: <ruby> -config.active_record.colorize_logging = false +config.active_record.timestamped_migrations = false </ruby> Rails will use that particular setting to configure Active Record. @@ -45,6 +45,8 @@ h4. Rails General Configuration * +config.cache_store+ configures which cache store to use for Rails caching. Options include +:memory_store+, +:file_store+, +:mem_cache_store+ or the name of your own custom class. +* +config.colorize_logging+ (true by default) specifies whether or not to use ANSI color codes when logging information. + * +config.controller_paths+ accepts an array of paths that will be searched for controllers. Defaults to +app/controllers+. * +config.database_configuration_file+ overrides the default path for the database configuration file. Default to +config/database.yml+. @@ -105,8 +107,6 @@ h4. Configuring Active Record * +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. -* +config.active_record.colorize_logging+ (true by default) specifies whether or not to use ANSI color codes when logging information from ActiveRecord. - * +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:local+. * +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 514be33a40..ded82512d3 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -232,13 +232,13 @@ The corresponding view +app/views/articles/new.html.erb+ using +form_for+ looks <%= form_for @article, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %> <%= f.text_field :title %> <%= f.text_area :body, :size => "60x12" %> - <%= submit_tag "Create" %> + <%= f.submit "Create" %> <% end %> </erb> There are a few things to note here: -# +:article+ is the name of the model and +@article+ is the actual object being edited. +# +@article+ is the actual object being edited. # There is a single hash of options. Routing options are passed in the +:url+ hash, HTML options are passed in the +:html+ hash. # The +form_for+ method yields a *form builder* object (the +f+ variable). # Methods to create form controls are called *on* the form builder object +f+ @@ -294,13 +294,13 @@ When dealing with RESTful resources, calls to +form_for+ can get significantly e <ruby> ## Creating a new article # long-style: -form_for(:article, @article, :url => articles_path) +form_for(@article, :url => articles_path) # same thing, short-style (record identification gets used): form_for(@article) ## Editing an existing article # long-style: -form_for(:article, @article, :url => article_path(@article), :html => { :method => "put" }) +form_for(@article, :url => article_path(@article), :html => { :method => "put" }) # short-style: form_for(@article) </ruby> diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index e592417dcb..f3420e37d1 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -919,8 +919,6 @@ So first, we'll wire up the Post show template (+/app/views/posts/show.html.erb+ <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> - <%= f.error_messages %> - <div class="field"> <%= f.label :commenter %><br /> <%= f.text_field :commenter %> @@ -989,8 +987,6 @@ Once we have made the new comment, we send the user back to the original post us <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> - <%= f.error_messages %> - <div class="field"> <%= f.label :commenter %><br /> <%= f.text_field :commenter %> @@ -1057,8 +1053,6 @@ Then in the +app/views/posts/show.html.erb+ you can change it to look like the f <h2>Add a comment:</h2> <%= form_for([@post, @post.comments.build]) do |f| %> - <%= f.error_messages %> - <div class="field"> <%= f.label :commenter %><br /> <%= f.text_field :commenter %> @@ -1086,8 +1080,6 @@ Lets also move that new comment section out to it's own partial, again, you crea <erb> <%= form_for([@post, @post.comments.build]) do |f| %> - <%= f.error_messages %> - <div class="field"> <%= f.label :commenter %><br /> <%= f.text_field :commenter %> diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index c65ea5c797..4e26d152bf 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -970,7 +970,7 @@ Partial templates - usually just called "partials" - are another device for brea h5. Naming Partials -To render a partial as part of a view, you use the +render+ method within the view, and include the +:partial+ option: +To render a partial as part of a view, you use the +render+ method within the view: <ruby> <%= render "menu" %> diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index f48ae9c7f7..cc0c3316c8 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -436,6 +436,26 @@ match 'exit' => 'sessions#destroy', :as => :logout This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+ +h4. HTTP Verb Constraints + +You can use the +:via+ option to constrain the request to one or more HTTP methods: + +<ruby> +match 'photos/show' => 'photos#show', :via => :get +</ruby> + +There is a shorthand version of this as well: + +<ruby> +get 'photos/show' +</ruby> + +You can also permit more than one verb to a single route: + +<ruby> +match 'photos/show' => 'photos#show', :via => [:get, :post] +</ruby> + h4. Segment Constraints You can use the +:constraints+ option to enforce a format for a dynamic segment: diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d84373cddf..182068071d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -35,7 +35,6 @@ module Rails # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' - autoload :Configurable, 'rails/application/configurable' autoload :Configuration, 'rails/application/configuration' autoload :Finisher, 'rails/application/finisher' autoload :Railties, 'rails/application/railties' diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 1260772605..2b32f7edf1 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -5,10 +5,12 @@ require 'rails/script_rails_loader' # the rest of this script is not run. Rails::ScriptRailsLoader.exec_script_rails! -railties_path = File.expand_path('../../lib', __FILE__) -$:.unshift(railties_path) if File.directory?(railties_path) && !$:.include?(railties_path) - require 'rails/ruby_version_check' Signal.trap("INT") { puts; exit } -require 'rails/commands/application' +if ARGV.first == 'plugin' + ARGV.shift + require 'rails/commands/plugin_new' +else + require 'rails/commands/application' +end diff --git a/railties/lib/rails/commands/plugin_new.rb b/railties/lib/rails/commands/plugin_new.rb new file mode 100644 index 0000000000..00a7e30902 --- /dev/null +++ b/railties/lib/rails/commands/plugin_new.rb @@ -0,0 +1,16 @@ +require 'rails/version' +if %w(--version -v).include? ARGV.first + puts "Rails #{Rails::VERSION::STRING}" + exit(0) +end + +if ARGV.first != "new" + ARGV[0] = "--help" +else + ARGV.shift +end + +require 'rails/generators' +require 'rails/generators/rails/plugin_new/plugin_new_generator' + +Rails::Generators::PluginNewGenerator.start diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 8369795e71..66fab0a760 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,5 +1,6 @@ require 'active_support/deprecation' require 'active_support/ordered_options' +require 'active_support/core_ext/hash/deep_dup' require 'rails/paths' require 'rails/rack' @@ -51,6 +52,13 @@ module Rails @colorize_logging = true end + def initialize_copy(source) + @aliases = @aliases.deep_dup + @options = @options.deep_dup + @fallbacks = @fallbacks.deep_dup + @templates = @templates.dup + end + def method_missing(method, *args) method = method.to_s.sub(/=$/, '').to_sym diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 72bfe207d7..466a31c2a0 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -324,8 +324,8 @@ module Rails # MyEngine::Engine.load_seed # class Engine < Railtie - autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" + autoload :Railties, "rails/engine/railties" class << self attr_accessor :called_from, :isolated diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 7a07dcad7d..4f458b0aee 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -10,6 +10,7 @@ module Rails def initialize(root=nil) super() @root = root + @generators = app_generators.dup end # Returns the middleware stack for the engine. diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb new file mode 100644 index 0000000000..f5c626553c --- /dev/null +++ b/railties/lib/rails/generators/app_base.rb @@ -0,0 +1,176 @@ +require 'digest/md5' +require 'active_support/secure_random' +require 'rails/version' unless defined?(Rails::VERSION) +require 'rbconfig' +require 'open-uri' +require 'uri' + +module Rails + module Generators + class AppBase < Base + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + JAVASCRIPTS = %w( prototype jquery ) + + attr_accessor :rails_template + add_shebang_option! + + argument :app_path, :type => :string + + def self.add_shared_options_for(name) + class_option :builder, :type => :string, :aliases => "-b", + :desc => "Path to a #{name} builder (can be a filesystem path or URL)" + + class_option :template, :type => :string, :aliases => "-m", + :desc => "Path to an #{name} template (can be a filesystem path or URL)" + + class_option :skip_gemfile, :type => :boolean, :default => false, + :desc => "Don't create a Gemfile" + + class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, + :desc => "Skip Git ignores and keeps" + + class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, + :desc => "Skip Active Record files" + + class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", + :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" + + class_option :javascript, :type => :string, :aliases => "-j", :default => "prototype", + :desc => "Preconfigure for selected javascript library (options: #{JAVASCRIPTS.join('/')})" + + class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false, + :desc => "Skip javascript files" + + class_option :dev, :type => :boolean, :default => false, + :desc => "Setup the #{name} with Gemfile pointing to your Rails checkout" + + class_option :edge, :type => :boolean, :default => false, + :desc => "Setup the #{name} with Gemfile pointing to Rails repository" + + class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false, + :desc => "Skip Test::Unit files" + + class_option :help, :type => :boolean, :aliases => "-h", :group => :rails, + :desc => "Show this help message and quit" + end + + def initialize(*args) + @original_wd = Dir.pwd + + super + end + + protected + + def builder + @builder ||= begin + if path = options[:builder] + if URI(path).is_a?(URI::HTTP) + contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } + else + contents = open(File.expand_path(path, @original_wd)) {|io| io.read } + end + + prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1) + instance_eval(&prok) + end + + builder_class = get_builder_class + builder_class.send(:include, ActionMethods) + builder_class.new(self) + end + end + + def build(meth, *args) + builder.send(meth, *args) if builder.respond_to?(meth) + end + + def create_root + self.destination_root = File.expand_path(app_path, destination_root) + valid_const? + + empty_directory '.' + set_default_accessors! + FileUtils.cd(destination_root) unless options[:pretend] + end + + def apply_rails_template + apply rails_template if rails_template + rescue Thor::Error, LoadError, Errno::ENOENT => e + raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" + end + + def set_default_accessors! + self.rails_template = case options[:template] + when /^http:\/\// + options[:template] + when String + File.expand_path(options[:template], Dir.pwd) + else + options[:template] + end + end + + def database_gemfile_entry + entry = "" + unless options[:skip_active_record] + entry = "gem '#{gem_for_database}'" + entry << ", :require => '#{require_for_database}'" if require_for_database + end + entry + end + + def rails_gemfile_entry + if options.dev? + <<-GEMFILE +gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' +gem 'arel', :git => 'git://github.com/rails/arel.git' +gem "rack", :git => "git://github.com/rack/rack.git" + GEMFILE + elsif options.edge? + <<-GEMFILE +gem 'rails', :git => 'git://github.com/rails/rails.git' +gem 'arel', :git => 'git://github.com/rails/arel.git' +gem "rack", :git => "git://github.com/rack/rack.git" + GEMFILE + else + <<-GEMFILE +gem 'rails', '#{Rails::VERSION::STRING}' + +# Bundle edge Rails instead: +# gem 'rails', :git => 'git://github.com/rails/rails.git' +# gem 'arel', :git => 'git://github.com/rails/arel.git' +# gem "rack", :git => "git://github.com/rack/rack.git" + GEMFILE + end + end + + def gem_for_database + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + case options[:database] + when "oracle" then "ruby-oci8" + when "postgresql" then "pg" + when "sqlite3" then "sqlite3-ruby" + when "frontbase" then "ruby-frontbase" + when "mysql" then "mysql2" + else options[:database] + end + end + + def require_for_database + case options[:database] + when "sqlite3" then "sqlite3" + end + end + + def bundle_if_dev_or_edge + bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + run "#{bundle_command} install" if dev_or_edge? + end + + def dev_or_edge? + options.dev? || options.edge? + end + end + end +end diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index d12b2ff0e5..c8ee939ad7 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -1,3 +1,4 @@ +<% without_namespacing do -%> <%%= form_for(@<%= singular_table_name %>) do |f| %> <%% if @<%= singular_table_name %>.errors.any? %> <div id="error_explanation"> @@ -21,3 +22,4 @@ <%%= f.submit %> </div> <%% end %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb index e58b9fbd08..d1bfcbc429 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb @@ -1,6 +1,8 @@ +<% without_namespacing do -%> <h1>Editing <%= singular_table_name %></h1> <%%= render 'form' %> <%%= link_to 'Show', @<%= singular_table_name %> %> | <%%= link_to 'Back', <%= index_helper %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index 4c46db4d67..435d126ee4 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -1,3 +1,4 @@ +<% without_namespacing do -%> <h1>Listing <%= plural_table_name %></h1> <table> @@ -25,3 +26,4 @@ <br /> <%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb index 02ae4d015e..fe4d0971c4 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb @@ -1,5 +1,7 @@ +<% without_namespacing do -%> <h1>New <%= singular_table_name %></h1> <%%= render 'form' %> <%%= link_to 'Back', <%= index_helper %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb index c0e5ccff1e..bc3a87b99f 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb @@ -1,3 +1,4 @@ +<% without_namespacing do -%> <p id="notice"><%%= notice %></p> <% for attribute in attributes -%> @@ -10,3 +11,4 @@ <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> | <%%= link_to 'Back', <%= index_helper %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 9131a19043..badf981d05 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -30,9 +30,15 @@ module Rails end end + def without_namespacing(&block) + inside_namespace do + concat(capture(&block)) + end + end + def indent(content, multiplier = 2) spaces = " " * multiplier - content.each_line.map {|line| "#{spaces}#{line}" }.join("\n") + content = content.each_line.map {|line| "#{spaces}#{line}" }.join end def wrap_with_namespace(content) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 44f9fde0a6..ef1eb8d237 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -1,9 +1,4 @@ -require 'digest/md5' -require 'active_support/secure_random' -require 'rails/version' unless defined?(Rails::VERSION) -require 'rbconfig' -require 'open-uri' -require 'uri' +require 'rails/generators/app_base' module Rails module ActionMethods @@ -158,60 +153,16 @@ module Rails RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test] - class AppGenerator < Base - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - JAVASCRIPTS = %w( prototype jquery ) - - attr_accessor :rails_template - add_shebang_option! - - argument :app_path, :type => :string - - class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", - :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" - - class_option :javascript, :type => :string, :aliases => "-j", :default => "prototype", - :desc => "Preconfigure for selected javascript library (options: #{JAVASCRIPTS.join('/')})" - - class_option :builder, :type => :string, :aliases => "-b", - :desc => "Path to an application builder (can be a filesystem path or URL)" - - class_option :template, :type => :string, :aliases => "-m", - :desc => "Path to an application template (can be a filesystem path or URL)" - - class_option :dev, :type => :boolean, :default => false, - :desc => "Setup the application with Gemfile pointing to your Rails checkout" - - class_option :edge, :type => :boolean, :default => false, - :desc => "Setup the application with Gemfile pointing to Rails repository" - - class_option :skip_gemfile, :type => :boolean, :default => false, - :desc => "Don't create a Gemfile" - - class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, - :desc => "Skip Active Record files" - - class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false, - :desc => "Skip Test::Unit files" - - class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false, - :desc => "Skip javascript files" - - class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, - :desc => "Skip Git ignores and keeps" + class AppGenerator < AppBase + add_shared_options_for "application" # Add bin/rails options class_option :version, :type => :boolean, :aliases => "-v", :group => :rails, :desc => "Show Rails version number and quit" - class_option :help, :type => :boolean, :aliases => "-h", :group => :rails, - :desc => "Show this help message and quit" - def initialize(*args) raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank? - @original_wd = Dir.pwd - super if !options[:skip_active_record] && !DATABASES.include?(options[:database]) @@ -223,14 +174,7 @@ module Rails end end - def create_root - self.destination_root = File.expand_path(app_path, destination_root) - valid_app_const? - - empty_directory '.' - set_default_accessors! - FileUtils.cd(destination_root) unless options[:pretend] - end + public_task :create_root def create_root_files build(:readme) @@ -309,16 +253,7 @@ module Rails build(:leftovers) end - def apply_rails_template - apply rails_template if rails_template - rescue Thor::Error, LoadError, Errno::ENOENT => e - raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" - end - - def bundle_if_dev_or_edge - bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - run "#{bundle_command} install" if dev_or_edge? - end + public_task :apply_rails_template, :bundle_if_dev_or_edge protected @@ -326,40 +261,6 @@ module Rails "rails new #{self.arguments.map(&:usage).join(' ')} [options]" end - def builder - @builder ||= begin - if path = options[:builder] - if URI(path).is_a?(URI::HTTP) - contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } - else - contents = open(File.expand_path(path, @original_wd)) {|io| io.read } - end - - prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1) - instance_eval(&prok) - end - - builder_class = defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder - builder_class.send(:include, ActionMethods) - builder_class.new(self) - end - end - - def build(meth, *args) - builder.send(meth, *args) if builder.respond_to?(meth) - end - - def set_default_accessors! - self.rails_template = case options[:template] - when /^http:\/\// - options[:template] - when String - File.expand_path(options[:template], Dir.pwd) - else - options[:template] - end - end - # Define file as an alias to create_file for backwards compatibility. def file(*args, &block) create_file(*args, &block) @@ -388,7 +289,7 @@ module Rails @app_const ||= "#{app_const_base}::Application" end - def valid_app_const? + def valid_const? if app_const =~ /^\d/ raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." elsif RESERVED_NAMES.include?(app_name) @@ -402,28 +303,6 @@ module Rails ActiveSupport::SecureRandom.hex(64) end - def dev_or_edge? - options.dev? || options.edge? - end - - def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - case options[:database] - when "oracle" then "ruby-oci8" - when "postgresql" then "pg" - when "sqlite3" then "sqlite3-ruby" - when "frontbase" then "ruby-frontbase" - when "mysql" then "mysql2" - else options[:database] - end - end - - def require_for_database - case options[:database] - when "sqlite3" then "sqlite3" - end - end - def mysql_socket @mysql_socket ||= [ "/tmp/mysql.sock", # default @@ -442,6 +321,10 @@ module Rails empty_directory(destination, config) create_file("#{destination}/.gitkeep") unless options[:skip_git] end + + def get_builder_class + defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder + end end end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 40213b1261..86b9e8f40c 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -1,25 +1,8 @@ source 'http://rubygems.org' -<%- if options.dev? -%> -gem 'rails', :path => '<%= Rails::Generators::RAILS_DEV_PATH %>' -gem 'arel', :git => 'git://github.com/rails/arel.git' -gem "rack", :git => "git://github.com/rack/rack.git" -<%- elsif options.edge? -%> -gem 'rails', :git => 'git://github.com/rails/rails.git' -gem 'arel', :git => 'git://github.com/rails/arel.git' -gem "rack", :git => "git://github.com/rack/rack.git" -<%- else -%> -gem 'rails', '<%= Rails::VERSION::STRING %>' +<%= rails_gemfile_entry -%> -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' -# gem 'arel', :git => 'git://github.com/rails/arel.git' -# gem "rack", :git => "git://github.com/rack/rack.git" -<%- end -%> - -<% unless options[:skip_active_record] -%> -gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= require_for_database %>'<% end %> -<% end -%> +<%= database_gemfile_entry -%> # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 00a23a7b89..6e515756fe 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -48,6 +48,10 @@ module <%= app_const_base %> # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) <% end -%> +<% if options[:skip_test_unit] -%> + config.generators.test_framework = false +<% end -%> + # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js index ad9a79c433..a4f114586c 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v1.4.3 + * jQuery JavaScript Library v1.4.4 * http://jquery.com/ * * Copyright 2010, John Resig @@ -11,7 +11,7 @@ * Copyright 2010, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Thu Oct 14 23:10:06 2010 -0400 + * Date: Thu Nov 11 19:04:53 2010 -0500 */ (function( window, undefined ) { @@ -211,7 +211,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.4.3", + jquery: "1.4.4", // The default length of a jQuery object is 0 length: 0, @@ -330,8 +330,11 @@ jQuery.fn = jQuery.prototype = { jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy, copyIsArray; + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { @@ -433,18 +436,21 @@ jQuery.extend({ // If there are functions bound, to execute if ( readyList ) { // Execute all of them - var fn, i = 0; - while ( (fn = readyList[ i++ ]) ) { - fn.call( document, jQuery ); - } + var fn, + i = 0, + ready = readyList; // Reset the list of functions readyList = null; - } - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); + while ( (fn = ready[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } } } }, @@ -697,7 +703,8 @@ jQuery.extend({ }, merge: function( first, second ) { - var i = first.length, j = 0; + var i = first.length, + j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { @@ -964,6 +971,7 @@ return (window.jQuery = window.$ = jQuery); optSelected: opt.selected, // Will be defined later + deleteExpando: true, optDisabled: false, checkClone: false, scriptEval: false, @@ -994,6 +1002,15 @@ return (window.jQuery = window.$ = jQuery); delete window[ id ]; } + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + root.removeChild( script ); if ( div.attachEvent && div.fireEvent ) { @@ -1087,20 +1104,6 @@ return (window.jQuery = window.$ = jQuery); root = script = div = all = a = null; })(); -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - - var windowData = {}, @@ -1237,8 +1240,24 @@ jQuery.extend({ jQuery.fn.extend({ data: function( key, value ) { + var data = null; + if ( typeof key === "undefined" ) { - return this.length ? jQuery.data( this[0] ) : null; + if ( this.length ) { + var attr = this[0].attributes, name; + data = jQuery.data( this[0] ); + + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + + return data; } else if ( typeof key === "object" ) { return this.each(function() { @@ -1250,31 +1269,12 @@ jQuery.fn.extend({ parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && this[0].nodeType === 1 ) { - data = this[0].getAttribute( "data-" + key ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - } else { - data = undefined; - } - } + data = dataAttr( this[0], key, data ); } return data === undefined && parts[1] ? @@ -1283,7 +1283,8 @@ jQuery.fn.extend({ } else { return this.each(function() { - var $this = jQuery( this ), args = [ parts[0], value ]; + var $this = jQuery( this ), + args = [ parts[0], value ]; $this.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); @@ -1299,6 +1300,33 @@ jQuery.fn.extend({ } }); +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + @@ -1329,7 +1357,8 @@ jQuery.extend({ dequeue: function( elem, type ) { type = type || "fx"; - var queue = jQuery.queue( elem, type ), fn = queue.shift(); + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -1405,6 +1434,19 @@ var rclass = /[\n\t]/g, rclickable = /^a(?:rea)?$/i, rradiocheck = /^(?:radio|checkbox)$/i; +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); @@ -1438,7 +1480,9 @@ jQuery.fn.extend({ elem.className = value; } else { - var className = " " + elem.className + " ", setClass = elem.className; + var className = " " + elem.className + " ", + setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { setClass += " " + classNames[c]; @@ -1486,7 +1530,8 @@ jQuery.fn.extend({ }, toggleClass: function( value, stateVal ) { - var type = typeof value, isBool = typeof stateVal === "boolean"; + var type = typeof value, + isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function(i) { @@ -1498,7 +1543,9 @@ jQuery.fn.extend({ return this.each(function() { if ( type === "string" ) { // toggle individual class names - var className, i = 0, self = jQuery(this), + var className, + i = 0, + self = jQuery( this ), state = stateVal, classNames = value.split( rspaces ); @@ -1667,91 +1714,88 @@ jQuery.extend({ // Try to normalize/fix the name name = notxml && jQuery.props[ name ] || name; - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } + // These attributes require special treatment + var special = rspecialurl.test( name ); - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; } + } + } - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); } - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; + } else { + elem[ name ] = value; } + } - return elem[ name ]; + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; } - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); - return elem.style.cssText; + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; } + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); + elem.style.cssText = "" + value; } - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } + return elem.style.cssText; + } - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; } }); @@ -1790,6 +1834,9 @@ jQuery.event = { if ( handler === false ) { handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; } var handleObjIn, handleObj; @@ -2133,8 +2180,10 @@ jQuery.event = { jQuery.event.trigger( event, data, parent, true ); } else if ( !event.isDefaultPrevented() ) { - var target = event.target, old, targetType = type.replace(rnamespaces, ""), - isClick = jQuery.nodeName(target, "a") && targetType === "click", + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", special = jQuery.event.special[ targetType ] || {}; if ( (!special._default || special._default.call( elem, event ) === false) && @@ -2166,7 +2215,9 @@ jQuery.event = { }, handle: function( event ) { - var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments ); + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); event = args[0] = jQuery.event.fix( event || window.event ); event.currentTarget = this; @@ -2245,7 +2296,8 @@ jQuery.event = { // Fix target property, if necessary if ( !event.target ) { - event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; } // check if target is a textnode (safari) @@ -2260,7 +2312,9 @@ jQuery.event = { // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, body = document.body; + var doc = document.documentElement, + body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } @@ -2466,7 +2520,8 @@ if ( !jQuery.support.submitBubbles ) { setup: function( data, namespaces ) { if ( this.nodeName.toLowerCase() !== "form" ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; + var elem = e.target, + type = elem.type; if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { e.liveFired = undefined; @@ -2475,7 +2530,8 @@ if ( !jQuery.support.submitBubbles ) { }); jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; + var elem = e.target, + type = elem.type; if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { e.liveFired = undefined; @@ -2716,7 +2772,8 @@ jQuery.fn.extend({ toggle: function( fn ) { // Save reference to arguments for access in closure - var args = arguments, i = 1; + var args = arguments, + i = 1; // link all the functions, so any of them can unbind this click handler while ( i < args.length ) { @@ -2811,8 +2868,9 @@ jQuery.each(["live", "die"], function( i, name ) { }); function liveHandler( event ) { - var stop, maxLevel, elems = [], selectors = [], - related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); if ( typeof events === "function" ) { @@ -2823,7 +2881,7 @@ function liveHandler( event ) { if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { return; } - + if ( event.namespace ) { namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); } @@ -2887,6 +2945,9 @@ function liveHandler( event ) { if ( ret === false ) { stop = false; } + if ( event.isImmediatePropagationStopped() ) { + break; + } } } @@ -2954,12 +3015,12 @@ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[ // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. -[0, 0].sort(function(){ +[0, 0].sort(function() { baseHasDuplicate = false; return 0; }); -var Sizzle = function(selector, context, results, seed) { +var Sizzle = function( selector, context, results, seed ) { results = results || []; context = context || document; @@ -2973,13 +3034,16 @@ var Sizzle = function(selector, context, results, seed) { return results; } - var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), - soFar = selector, ret, cur, pop, i; + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; // Reset the position of the chunker regexp (start from head) do { - chunker.exec(""); - m = chunker.exec(soFar); + chunker.exec( "" ); + m = chunker.exec( soFar ); if ( m ) { soFar = m[3]; @@ -2994,8 +3058,10 @@ var Sizzle = function(selector, context, results, seed) { } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); + } else { set = Expr.relative[ parts[0] ] ? [ context ] : @@ -3011,23 +3077,31 @@ var Sizzle = function(selector, context, results, seed) { set = posProcess( selector, set ); } } + } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; if ( parts.length > 0 ) { - checkSet = makeArray(set); + checkSet = makeArray( set ); + } else { prune = false; } @@ -3048,6 +3122,7 @@ var Sizzle = function(selector, context, results, seed) { Expr.relative[ cur ]( checkSet, pop, contextXML ); } + } else { checkSet = parts = []; } @@ -3064,12 +3139,14 @@ var Sizzle = function(selector, context, results, seed) { if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } + } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { @@ -3077,6 +3154,7 @@ var Sizzle = function(selector, context, results, seed) { } } } + } else { makeArray( checkSet, results ); } @@ -3089,15 +3167,15 @@ var Sizzle = function(selector, context, results, seed) { return results; }; -Sizzle.uniqueSort = function(results){ +Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); + results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); } } } @@ -3106,15 +3184,15 @@ Sizzle.uniqueSort = function(results){ return results; }; -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); }; -Sizzle.matchesSelector = function(node, expr){ - return Sizzle(expr, null, null, [node]).length > 0; +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; }; -Sizzle.find = function(expr, context, isXML){ +Sizzle.find = function( expr, context, isXML ) { var set; if ( !expr ) { @@ -3122,15 +3200,17 @@ Sizzle.find = function(expr, context, isXML){ } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; + var match, + type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; - match.splice(1,1); + match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; @@ -3140,20 +3220,26 @@ Sizzle.find = function(expr, context, isXML){ } if ( !set ) { - set = context.getElementsByTagName("*"); + set = context.getElementsByTagName( "*" ); } - return {set: set, expr: expr}; + return { set: set, expr: expr }; }; -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( var type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var filter = Expr.filter[ type ], found, item, left = match[1]; + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + anyFound = false; match.splice(1,1); @@ -3171,6 +3257,7 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( !match ) { anyFound = found = true; + } else if ( match === true ) { continue; } @@ -3185,9 +3272,11 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( inplace && found != null ) { if ( pass ) { anyFound = true; + } else { curLoop[i] = false; } + } else if ( pass ) { result.push( item ); anyFound = true; @@ -3216,6 +3305,7 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); + } else { break; } @@ -3233,6 +3323,7 @@ Sizzle.error = function( msg ) { var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], + match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, @@ -3243,20 +3334,24 @@ var Expr = Sizzle.selectors = { POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, + leftMatch: {}, + attrMap: { "class": "className", "for": "htmlFor" }, + attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); + href: function( elem ) { + return elem.getAttribute( "href" ); } }, + relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), + isTag = isPartStr && !/\W/.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { @@ -3277,23 +3372,29 @@ var Expr = Sizzle.selectors = { Sizzle.filter( part, checkSet, true ); } }, - ">": function(checkSet, part){ - var isPartStr = typeof part === "string", - elem, i = 0, l = checkSet.length; - if ( isPartStr && !/\W/.test(part) ) { + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !/\W/.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; + if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } + } else { for ( ; i < l; i++ ) { elem = checkSet[i]; + if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : @@ -3306,8 +3407,11 @@ var Expr = Sizzle.selectors = { } } }, + "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; + var nodeCheck, + doneName = done++, + checkFn = dirCheck; if ( typeof part === "string" && !/\W/.test(part) ) { part = part.toLowerCase(); @@ -3315,22 +3419,26 @@ var Expr = Sizzle.selectors = { checkFn = dirNodeCheck; } - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; - if ( typeof part === "string" && !/\W/.test(part) ) { + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, + find: { - ID: function(match, context, isXML){ + ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); // Check parentNode to catch when Blackberry 4.6 returns @@ -3338,9 +3446,11 @@ var Expr = Sizzle.selectors = { return m && m.parentNode ? [m] : []; } }, - NAME: function(match, context){ + + NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); + var ret = [], + results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { @@ -3351,12 +3461,13 @@ var Expr = Sizzle.selectors = { return ret.length === 0 ? null : ret; } }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); + + TAG: function( match, context ) { + return context.getElementsByTagName( match[1] ); } }, preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ + CLASS: function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace(/\\/g, "") + " "; if ( isXML ) { @@ -3369,6 +3480,7 @@ var Expr = Sizzle.selectors = { if ( !inplace ) { result.push( elem ); } + } else if ( inplace ) { curLoop[i] = false; } @@ -3377,13 +3489,16 @@ var Expr = Sizzle.selectors = { return false; }, - ID: function(match){ + + ID: function( match ) { return match[1].replace(/\\/g, ""); }, - TAG: function(match, curLoop){ + + TAG: function( match, curLoop ) { return match[1].toLowerCase(); }, - CHILD: function(match){ + + CHILD: function( match ) { if ( match[1] === "nth" ) { // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( @@ -3400,7 +3515,8 @@ var Expr = Sizzle.selectors = { return match; }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { var name = match[1].replace(/\\/g, ""); if ( !isXML && Expr.attrMap[name] ) { @@ -3413,122 +3529,156 @@ var Expr = Sizzle.selectors = { return match; }, - PSEUDO: function(match, curLoop, inplace, result, not){ + + PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); + } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { result.push.apply( result, ret ); } + return false; } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, - POS: function(match){ + + POS: function( match ) { match.unshift( true ); + return match; } }, + filters: { - enabled: function(elem){ + enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, - disabled: function(elem){ + + disabled: function( elem ) { return elem.disabled === true; }, - checked: function(elem){ + + checked: function( elem ) { return elem.checked === true; }, - selected: function(elem){ + + selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly elem.parentNode.selectedIndex; + return elem.selected === true; }, - parent: function(elem){ + + parent: function( elem ) { return !!elem.firstChild; }, - empty: function(elem){ + + empty: function( elem ) { return !elem.firstChild; }, - has: function(elem, i, match){ + + has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, - header: function(elem){ + + header: function( elem ) { return (/h\d/i).test( elem.nodeName ); }, - text: function(elem){ + + text: function( elem ) { return "text" === elem.type; }, - radio: function(elem){ + radio: function( elem ) { return "radio" === elem.type; }, - checkbox: function(elem){ + + checkbox: function( elem ) { return "checkbox" === elem.type; }, - file: function(elem){ + + file: function( elem ) { return "file" === elem.type; }, - password: function(elem){ + password: function( elem ) { return "password" === elem.type; }, - submit: function(elem){ + + submit: function( elem ) { return "submit" === elem.type; }, - image: function(elem){ + + image: function( elem ) { return "image" === elem.type; }, - reset: function(elem){ + + reset: function( elem ) { return "reset" === elem.type; }, - button: function(elem){ + + button: function( elem ) { return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; }, - input: function(elem){ - return (/input|select|textarea|button/i).test(elem.nodeName); + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); } }, setFilters: { - first: function(elem, i){ + first: function( elem, i ) { return i === 0; }, - last: function(elem, i, match, array){ + + last: function( elem, i, match, array ) { return i === array.length - 1; }, - even: function(elem, i){ + + even: function( elem, i ) { return i % 2 === 0; }, - odd: function(elem, i){ + + odd: function( elem, i ) { return i % 2 === 1; }, - lt: function(elem, i, match){ + + lt: function( elem, i, match ) { return i < match[3] - 0; }, - gt: function(elem, i, match){ + + gt: function( elem, i, match ) { return i > match[3] - 0; }, - nth: function(elem, i, match){ + + nth: function( elem, i, match ) { return match[3] - 0 === i; }, - eq: function(elem, i, match){ + + eq: function( elem, i, match ) { return match[3] - 0 === i; } }, filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); + } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { var not = match[3]; @@ -3539,33 +3689,43 @@ var Expr = Sizzle.selectors = { } return true; + } else { Sizzle.error( "Syntax error, unrecognized expression: " + name ); } }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } + if ( type === "first" ) { return true; } + node = elem; - case 'last': + + case "last": while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } + return true; - case 'nth': - var first = match[2], last = match[3]; + + case "nth": + var first = match[2], + last = match[3]; if ( first === 1 && last === 0 ) { return true; @@ -3576,33 +3736,41 @@ var Expr = Sizzle.selectors = { if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } + parent.sizcache = doneName; } var diff = elem.nodeIndex - last; + if ( first === 0 ) { return diff === 0; + } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, - ID: function(elem, match){ + + ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, - TAG: function(elem, match){ + + TAG: function( elem, match ) { return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, - CLASS: function(elem, match){ + + CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, - ATTR: function(elem, match){ + + ATTR: function( elem, match ) { var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : @@ -3633,8 +3801,10 @@ var Expr = Sizzle.selectors = { value === check || value.substr(0, check.length + 1) === check + "-" : false; }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); @@ -3653,7 +3823,7 @@ for ( var type in Expr.match ) { Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } -var makeArray = function(array, results) { +var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { @@ -3672,17 +3842,20 @@ try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || [], i = 0; +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); + } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } + } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); @@ -3709,10 +3882,15 @@ if ( document.documentElement.compareDocumentPosition ) { return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; + } else { sortOrder = function( a, b ) { - var ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, - cur = aup, al, bl; + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; // The nodes are identical, we can exit early if ( a === b ) { @@ -3805,31 +3983,40 @@ Sizzle.getText = function( elems ) { (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), - id = "script" + (new Date()).getTime(); + id = "script" + (new Date()).getTime(), + root = document.documentElement; + form.innerHTML = "<a name='" + id + "'/>"; // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ + Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; } }; - Expr.filter.ID = function(elem, match){ + Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); - root = form = null; // release memory in IE + + // release memory in IE + root = form = null; })(); (function(){ @@ -3842,8 +4029,8 @@ Sizzle.getText = function( elems ) { // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { @@ -3864,19 +4051,25 @@ Sizzle.getText = function( elems ) { // Check to see if an attribute returns normalized href attributes div.innerHTML = "<a href='#'></a>"; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); }; } - div = null; // release memory in IE + // release memory in IE + div = null; })(); if ( document.querySelectorAll ) { (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + div.innerHTML = "<p class='TEST'></p>"; // Safari can't handle uppercase or unicode characters when @@ -3885,9 +4078,12 @@ if ( document.querySelectorAll ) { return; } - Sizzle = function(query, context, extra, seed){ + Sizzle = function( query, context, extra, seed ) { context = context || document; + // Make sure that attribute selectors are quoted + query = query.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { @@ -3901,17 +4097,19 @@ if ( document.querySelectorAll ) { // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var old = context.id, id = context.id = "__sizzle__"; + var old = context.getAttribute( "id" ), + nid = old || id; + + if ( !old ) { + context.setAttribute( "id", nid ); + } try { - return makeArray( context.querySelectorAll( "#" + id + " " + query ), extra ); + return makeArray( context.querySelectorAll( "#" + nid + " " + query ), extra ); } catch(pseudoError) { } finally { - if ( old ) { - context.id = old; - - } else { + if ( !old ) { context.removeAttribute( "id" ); } } @@ -3925,7 +4123,8 @@ if ( document.querySelectorAll ) { Sizzle[ prop ] = oldSizzle[ prop ]; } - div = null; // release memory in IE + // release memory in IE + div = null; })(); } @@ -3937,7 +4136,7 @@ if ( document.querySelectorAll ) { try { // This should fail with an exception // Gecko does not error, returns false instead - matches.call( document.documentElement, ":sizzle" ); + matches.call( document.documentElement, "[test!='']:sizzle" ); } catch( pseudoError ) { pseudoWorks = true; @@ -3945,13 +4144,18 @@ if ( document.querySelectorAll ) { if ( matches ) { Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) ) { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { return matches.call( node, expr ); } } catch(e) {} + } - return Sizzle(expr, null, null, [node]).length > 0; + return Sizzle(expr, null, null, [node]).length > 0; }; } })(); @@ -3975,22 +4179,25 @@ if ( document.querySelectorAll ) { } Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { + Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; - div = null; // release memory in IE + // release memory in IE + div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; + if ( elem ) { - elem = elem[dir]; var match = false; + elem = elem[dir]; + while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; @@ -4018,9 +4225,11 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; + if ( elem ) { - elem = elem[dir]; var match = false; + + elem = elem[dir]; while ( elem ) { if ( elem.sizcache === doneName ) { @@ -4033,6 +4242,7 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem.sizcache = doneName; elem.sizset = i; } + if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; @@ -4053,21 +4263,34 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { } } -Sizzle.contains = document.documentElement.contains ? function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -} : function(a, b){ - return !!(a.compareDocumentPosition(b) & 16); -}; +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; -Sizzle.isXML = function(elem){ +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; }; -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter @@ -4109,7 +4332,8 @@ var runtil = /Until$/, jQuery.fn.extend({ find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), length = 0; + var ret = this.pushStack( "", "find", selector ), + length = 0; for ( var i = 0, l = this.length; i < l; i++ ) { length = ret.length; @@ -4158,7 +4382,9 @@ jQuery.fn.extend({ var ret = [], i, l, cur = this[0]; if ( jQuery.isArray( selectors ) ) { - var match, matches = {}, selector, level = 1; + var match, selector, + matches = {}, + level = 1; if ( cur && selectors.length ) { for ( i = 0, l = selectors.length; i < l; i++ ) { @@ -4324,7 +4550,9 @@ jQuery.extend({ }, dir: function( elem, dir, until ) { - var matched = [], cur = elem[dir]; + var matched = [], + cur = elem[ dir ]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); @@ -4400,7 +4628,8 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, rnocache = /<(?:script|object|embed|option|style)/i, - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, // checked="checked" or checked (html5) + // checked="checked" or checked (html5) + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, raction = /\=([^="'>\s]+\/)>/g, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], @@ -4426,7 +4655,8 @@ jQuery.fn.extend({ text: function( text ) { if ( jQuery.isFunction(text) ) { return this.each(function(i) { - var self = jQuery(this); + var self = jQuery( this ); + self.text( text.call(this, i, self.text()) ); }); } @@ -4475,7 +4705,8 @@ jQuery.fn.extend({ } return this.each(function() { - var self = jQuery( this ), contents = self.contents(); + var self = jQuery( this ), + contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); @@ -4586,7 +4817,9 @@ jQuery.fn.extend({ // attributes in IE that are actually only stored // as properties will not be copied (such as the // the name attribute on an input). - var html = this.outerHTML, ownerDocument = this.ownerDocument; + var html = this.outerHTML, + ownerDocument = this.ownerDocument; + if ( !html ) { var div = ownerDocument.createElement("div"); div.appendChild( this.cloneNode(true) ); @@ -4641,7 +4874,8 @@ jQuery.fn.extend({ } else if ( jQuery.isFunction( value ) ) { this.each(function(i){ - var self = jQuery(this); + var self = jQuery( this ); + self.html( value.call(this, i, self.html()) ); }); @@ -4664,13 +4898,14 @@ jQuery.fn.extend({ } if ( typeof value !== "string" ) { - value = jQuery(value).detach(); + value = jQuery( value ).detach(); } return this.each(function() { - var next = this.nextSibling, parent = this.parentNode; + var next = this.nextSibling, + parent = this.parentNode; - jQuery(this).remove(); + jQuery( this ).remove(); if ( next ) { jQuery(next).before( value ); @@ -4688,7 +4923,9 @@ jQuery.fn.extend({ }, domManip: function( args, table, callback ) { - var results, first, value = args[0], scripts = [], fragment, parent; + var results, first, fragment, parent, + value = args[0], + scripts = []; // We can't cloneNode fragments that contain checked, in WebKit if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { @@ -4763,7 +5000,9 @@ function cloneCopyEvent(orig, ret) { return; } - var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events; + var oldData = jQuery.data( orig[i++] ), + curData = jQuery.data( this, oldData ), + events = oldData && oldData.events; if ( events ) { delete curData.handle; @@ -4820,7 +5059,8 @@ jQuery.each({ replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { - var ret = [], insert = jQuery( selector ), + var ret = [], + insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { @@ -5004,8 +5244,8 @@ var ralpha = /alpha\([^)]*\)/i, cssHeight = [ "Top", "Bottom" ], curCSS, - // cache check for defaultView.getComputedStyle - getComputedStyle = document.defaultView && document.defaultView.getComputedStyle, + getComputedStyle, + currentStyle, fcamelCase = function( all, letter ) { return letter.toUpperCase(); @@ -5161,7 +5401,29 @@ jQuery.each(["height", "width"], function( i, name ) { }); } - return val + "px"; + if ( val <= 0 ) { + val = curCSS( elem, name, name ); + + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + + if ( val != null ) { + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + } + + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + + return typeof val === "string" ? val : val + "px"; } }, @@ -5210,8 +5472,8 @@ if ( !jQuery.support.opacity ) { }; } -if ( getComputedStyle ) { - curCSS = function( elem, newName, name ) { +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, newName, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); @@ -5229,10 +5491,13 @@ if ( getComputedStyle ) { return ret; }; +} -} else if ( document.documentElement.currentStyle ) { - curCSS = function( elem, name ) { - var left, rsLeft, ret = elem.currentStyle && elem.currentStyle[ name ], style = elem.style; +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 @@ -5254,10 +5519,12 @@ if ( getComputedStyle ) { elem.runtimeStyle.left = rsLeft; } - return ret; + return ret === "" ? "auto" : ret; }; } +curCSS = getComputedStyle || currentStyle; + function getWH( elem, name, extra ) { var which = name === "width" ? cssWidth : cssHeight, val = name === "width" ? elem.offsetWidth : elem.offsetHeight; @@ -5284,7 +5551,8 @@ function getWH( elem, name, extra ) { if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { - var width = elem.offsetWidth, height = elem.offsetHeight; + var width = elem.offsetWidth, + height = elem.offsetHeight; return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); }; @@ -5301,7 +5569,7 @@ var jsc = jQuery.now(), rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, - rnoContent = /^(?:GET|HEAD|DELETE)$/, + rnoContent = /^(?:GET|HEAD)$/, rbracket = /\[\]$/, jsre = /\=\?(&|$)/, rquery = /\?/, @@ -5536,10 +5804,6 @@ jQuery.extend({ var customJsonp = window[ jsonp ]; window[ jsonp ] = function( tmp ) { - data = tmp; - jQuery.handleSuccess( s, xhr, status, data ); - jQuery.handleComplete( s, xhr, status, data ); - if ( jQuery.isFunction( customJsonp ) ) { customJsonp( tmp ); @@ -5551,6 +5815,10 @@ jQuery.extend({ delete window[ jsonp ]; } catch( jsonpError ) {} } + + data = tmp; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); if ( head ) { head.removeChild( script ); @@ -5562,7 +5830,7 @@ jQuery.extend({ s.cache = false; } - if ( s.cache === false && type === "GET" ) { + if ( s.cache === false && noContent ) { var ts = jQuery.now(); // try replacing _= if it is there @@ -5572,8 +5840,8 @@ jQuery.extend({ s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); } - // If data is available, append data to url for get requests - if ( s.data && type === "GET" ) { + // If data is available, append data to url for GET/HEAD requests + if ( s.data && noContent ) { s.url += (rquery.test(s.url) ? "&" : "?") + s.data; } @@ -5584,7 +5852,7 @@ jQuery.extend({ // Matches an absolute URL, and saves the domain var parts = rurl.exec( s.url ), - remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host); + remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); // If we're requesting a remote document // and trying to load JSON or Script with a GET @@ -5760,10 +6028,11 @@ jQuery.extend({ try { var oldAbort = xhr.abort; xhr.abort = function() { - // xhr.abort in IE7 is not a native JS function - // and does not have a call property - if ( xhr && oldAbort.call ) { - oldAbort.call( xhr ); + if ( xhr ) { + // oldAbort has no call property in IE7 so + // just do it this way, which works in all + // browsers + Function.prototype.call.call( oldAbort, xhr ); } onreadystatechange( "abort" ); @@ -5803,11 +6072,12 @@ jQuery.extend({ // Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { - var s = [], add = function( key, value ) { - // If value is a function, invoke it and return its value - value = jQuery.isFunction(value) ? value() : value; - s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); - }; + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value; + s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { @@ -6029,28 +6299,39 @@ var elemdisplay = {}, jQuery.fn.extend({ show: function( speed, easing, callback ) { + var elem, display; + if ( speed || speed === 0 ) { return this.animate( genFx("show", 3), speed, easing, callback); + } else { for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not - if ( !jQuery.data(this[i], "olddisplay") && this[i].style.display === "none" ) { - this[i].style.display = ""; + if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; } // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element - if ( this[i].style.display === "" && jQuery.css( this[i], "display" ) === "none" ) { - jQuery.data(this[i], "olddisplay", defaultDisplay(this[i].nodeName)); + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); } } // Set the display of most of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { - this[i].style.display = jQuery.data(this[i], "olddisplay") || ""; + elem = this[i]; + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery.data(elem, "olddisplay") || ""; + } } return this; @@ -6115,7 +6396,7 @@ jQuery.fn.extend({ } return this[ optall.queue === false ? "each" : "queue" ](function() { - // XXX ‘this’ does not always have a nodeName when running the + // XXX 'this' does not always have a nodeName when running the // test suite var opt = jQuery.extend({}, optall), p, @@ -6188,7 +6469,7 @@ jQuery.fn.extend({ } else { var parts = rfxnum.exec(val), - start = e.cur(true) || 0; + start = e.cur() || 0; if ( parts ) { var end = parseFloat( parts[2] ), @@ -6197,7 +6478,7 @@ jQuery.fn.extend({ // We need to compute starting value if ( unit !== "px" ) { jQuery.style( self, name, (end || 1) + unit); - start = ((end || 1) / e.cur(true)) * start; + start = ((end || 1) / e.cur()) * start; jQuery.style( self, name, start + unit); } @@ -6266,7 +6547,8 @@ jQuery.each({ slideUp: genFx("hide", 1), slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" } + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); @@ -6344,6 +6626,9 @@ jQuery.fx.prototype = { // Start an animation from one number to another custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + this.startTime = jQuery.now(); this.start = from; this.end = to; @@ -6351,7 +6636,6 @@ jQuery.fx.prototype = { this.now = this.start; this.pos = this.state = 0; - var self = this, fx = jQuery.fx; function t( gotoEnd ) { return self.step(gotoEnd); } @@ -6408,7 +6692,9 @@ jQuery.fx.prototype = { if ( done ) { // Reset the overflow if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - var elem = this.elem, options = this.options; + var elem = this.elem, + options = this.options; + jQuery.each( [ "", "X", "Y" ], function (index, value) { elem.style[ "overflow" + value ] = options.overflow[index]; } ); @@ -6587,11 +6873,16 @@ if ( "getBoundingClientRect" in document.documentElement ) { jQuery.offset.initialize(); - var offsetParent = elem.offsetParent, prevOffsetParent = elem, - doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement, - body = doc.body, defaultView = doc.defaultView, + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, - top = elem.offsetTop, left = elem.offsetLeft; + top = elem.offsetTop, + left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { @@ -6673,7 +6964,8 @@ jQuery.offset = { }, bodyOffset: function( body ) { - var top = body.offsetTop, left = body.offsetLeft; + var top = body.offsetTop, + left = body.offsetLeft; jQuery.offset.initialize(); @@ -6854,27 +7146,31 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { }); } - return jQuery.isWindow( elem ) ? + if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || - elem.document.body[ "client" + name ] : - - // Get document width or height - (elem.nodeType === 9) ? // is it a document - // Either scroll[Width/Height] or offset[Width/Height], whichever is greater - Math.max( - elem.documentElement["client" + name], - elem.body["scroll" + name], elem.documentElement["scroll" + name], - elem.body["offset" + name], elem.documentElement["offset" + name] - ) : - - // Get or set width or height on the element - size === undefined ? - // Get width or height on the element - parseFloat( jQuery.css( elem, type ) ) : - - // Set the width or height on the element (default to pixels if value is unitless) - this.css( type, typeof size === "string" ? size : size + "px" ); + return elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || + elem.document.body[ "client" + name ]; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNaN( ret ) ? orig : ret; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } }; }); diff --git a/railties/lib/rails/generators/rails/plugin_new/USAGE b/railties/lib/rails/generators/rails/plugin_new/USAGE new file mode 100644 index 0000000000..9a7bf9f396 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/USAGE @@ -0,0 +1,10 @@ +Description: + The 'rails plugin new' command creates a skeleton for developing any + kind of Rails extension with ability to run tests using dummy Rails + application. + +Example: + rails plugin new ~/Code/Ruby/blog + + This generates a skeletal Rails plugin in ~/Code/Ruby/blog. + See the README in the newly created plugin to get going. 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 new file mode 100644 index 0000000000..8fac6fc70a --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -0,0 +1,246 @@ +require 'active_support/core_ext/hash/slice' +require "rails/generators/rails/app/app_generator" + +module Rails + class PluginBuilder + def rakefile + template "Rakefile" + end + + def app + directory "app" if options[:mountable] + end + + def readme + copy_file "README.rdoc" + end + + def gemfile + template "Gemfile" + end + + def license + template "MIT-LICENSE" + end + + def gemspec + template "%name%.gemspec" + end + + def gitignore + copy_file "gitignore", ".gitignore" + end + + def lib + template "lib/%name%.rb" + template "lib/tasks/%name%_tasks.rake" + if full? + template "lib/%name%/engine.rb" + end + end + + def config + template "config/routes.rb" if mountable? + end + + def test + template "test/test_helper.rb" + template "test/%name%_test.rb" + append_file "Rakefile", <<-EOF +#{rakefile_test_tasks} + +task :default => :test + EOF + if full? + template "test/integration/navigation_test.rb" + end + end + + def generate_test_dummy(force = false) + opts = (options || {}).slice(:skip_active_record, :skip_javascript, :database, :javascript) + opts[:force] = force + + invoke Rails::Generators::AppGenerator, + [ File.expand_path(dummy_path, destination_root) ], opts + end + + def test_dummy_config + template "rails/boot.rb", "#{dummy_path}/config/boot.rb", :force => true + template "rails/application.rb", "#{dummy_path}/config/application.rb", :force => true + if mountable? + template "rails/routes.rb", "#{dummy_path}/config/routes.rb", :force => true + end + end + + def test_dummy_clean + inside dummy_path do + remove_file ".gitignore" + remove_file "db/seeds.rb" + remove_file "doc" + remove_file "Gemfile" + remove_file "lib/tasks" + remove_file "public/images/rails.png" + remove_file "public/index.html" + remove_file "public/robots.txt" + remove_file "README" + remove_file "test" + remove_file "vendor" + end + end + + def script(force = false) + directory "script", :force => force do |content| + "#{shebang}\n" + content + end + chmod "script", 0755, :verbose => false + end + end + + module Generators + class PluginNewGenerator < AppBase + add_shared_options_for "plugin" + + alias_method :plugin_path, :app_path + + class_option :dummy_path, :type => :string, :default => "test/dummy", + :desc => "Create dummy application at given path" + + class_option :full, :type => :boolean, :default => false, + :desc => "Generate rails engine with integration tests" + + class_option :mountable, :type => :boolean, :default => false, + :desc => "Generate mountable isolated application" + + def initialize(*args) + raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank? + + super + end + + public_task :create_root + + def create_root_files + build(:readme) + build(:rakefile) + build(:gemspec) + build(:license) + build(:gitignore) unless options[:skip_git] + build(:gemfile) unless options[:skip_gemfile] + end + + def create_app_files + build(:app) + end + + def create_config_files + build(:config) + end + + def create_lib_files + build(:lib) + end + + def create_script_files + build(:script) + end + + def create_test_files + build(:test) unless options[:skip_test_unit] + end + + def create_test_dummy_files + return if options[:skip_test_unit] + create_dummy_app + end + + def finish_template + build(:leftovers) + end + + public_task :apply_rails_template, :bundle_if_dev_or_edge + + protected + def create_dummy_app(path = nil) + dummy_path(path) if path + + say_status :vendor_app, dummy_path + mute do + build(:generate_test_dummy) + store_application_definition! + build(:test_dummy_config) + build(:test_dummy_clean) + # ensure that script/rails has proper dummy_path + build(:script, true) + end + end + + def full? + options[:full] || options[:mountable] + end + + def mountable? + options[:mountable] + end + + def self.banner + "rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]" + end + + def name + @name ||= File.basename(destination_root) + end + + def camelized + @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize + end + + def valid_const? + if camelized =~ /^\d/ + raise Error, "Invalid plugin name #{name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(name) + raise Error, "Invalid plugin name #{name}. Please give a name which does not match one of the reserved rails words." + elsif Object.const_defined?(camelized) + raise Error, "Invalid plugin name #{name}, constant #{camelized} is already in use. Please choose another application name." + end + end + + def application_definition + @application_definition ||= begin + + dummy_application_path = File.expand_path("#{dummy_path}/config/application.rb", destination_root) + unless options[:pretend] || !File.exists?(dummy_application_path) + contents = File.read(dummy_application_path) + contents[(contents.index("module Dummy"))..-1] + end + end + end + alias :store_application_definition! :application_definition + + def get_builder_class + defined?(::PluginBuilder) ? ::PluginBuilder : Rails::PluginBuilder + end + + def rakefile_test_tasks + <<-RUBY +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + RUBY + end + + def dummy_path(path = nil) + @dummy_path = path if path + @dummy_path || options[:dummy_path] + end + + def mute(&block) + shell.mute(&block) + end + end + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec new file mode 100644 index 0000000000..3d9bfb22c7 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -0,0 +1,9 @@ +# Provide a simple gemspec so you can easily use your +# project in your rails apps through git. +Gem::Specification.new do |s| + s.name = "<%= name %>" + s.summary = "Insert <%= camelized %> summary." + s.description = "Insert <%= camelized %> description." + s.files = Dir["lib/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"] + s.version = "0.0.1" +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile new file mode 100644 index 0000000000..29900c93dc --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -0,0 +1,11 @@ +source "http://rubygems.org" + +<%= rails_gemfile_entry -%> + +<% if full? -%> +<%= database_gemfile_entry -%> +<% end -%> + +if RUBY_VERSION < '1.9' + gem "ruby-debug", ">= 0.10.3" +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE new file mode 100644 index 0000000000..d7a9109894 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright <%= Date.today.year %> YOURNAME + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc new file mode 100644 index 0000000000..301d647731 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc @@ -0,0 +1,3 @@ += <%= camelized %> + +This project rocks and uses MIT-LICENSE.
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile new file mode 100644 index 0000000000..88f50f9f04 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile @@ -0,0 +1,18 @@ +# encoding: UTF-8 +require 'rubygems' +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rake' +require 'rake/rdoctask' + +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = '<%= camelized %>' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt new file mode 100644 index 0000000000..f225bc9f7f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt @@ -0,0 +1,4 @@ +module <%= camelized %> + class ApplicationController < ActiveController::Base + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt new file mode 100644 index 0000000000..40ae9f52c2 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt @@ -0,0 +1,4 @@ +module <%= camelized %> + module ApplicationHelper + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb new file mode 100644 index 0000000000..42ddf380d8 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb @@ -0,0 +1,3 @@ +<%= camelized %>::Engine.routes.draw do + +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore new file mode 100644 index 0000000000..1463de6dfb --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore @@ -0,0 +1,6 @@ +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/log/*.log +test/dummy/tmp/
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb new file mode 100644 index 0000000000..2d3bdc510c --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb @@ -0,0 +1,6 @@ +<% if full? -%> +require "<%= name %>/engine" + +<% end -%> +module <%= camelized %> +end 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 new file mode 100644 index 0000000000..9600ee0c3f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb @@ -0,0 +1,7 @@ +module <%= camelized %> + class Engine < Rails::Engine +<% if mountable? -%> + isolate_namespace <%= camelized %> +<% end -%> + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake b/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake new file mode 100644 index 0000000000..7121f5ae23 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :<%= name %> do +# # Task goes here +# end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb new file mode 100644 index 0000000000..8b68280a5e --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb @@ -0,0 +1,16 @@ +require File.expand_path('../boot', __FILE__) + +<% unless options[:skip_active_record] -%> +require 'rails/all' +<% else -%> +# require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "active_resource/railtie" +require "rails/test_unit/railtie" +<% end -%> + +Bundler.require +require "<%= name %>" + +<%= application_definition %> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb new file mode 100644 index 0000000000..eba0681370 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb @@ -0,0 +1,10 @@ +require 'rubygems' +gemfile = File.expand_path('../../../../Gemfile', __FILE__) + +if File.exist?(gemfile) + ENV['BUNDLE_GEMFILE'] = gemfile + require 'bundler' + Bundler.setup +end + +$:.unshift File.expand_path('../../../../lib', __FILE__)
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb new file mode 100644 index 0000000000..730ee31c3d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb @@ -0,0 +1,4 @@ +Rails.application.routes.draw do + + mount <%= camelized %>::Engine => "/<%= name %>" +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt new file mode 100644 index 0000000000..ebd5a77dd5 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +ENGINE_PATH = File.expand_path('../..', __FILE__) +load File.expand_path('../../<%= dummy_path %>/script/rails', __FILE__) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb new file mode 100644 index 0000000000..0a8bbd4aaf --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class <%= camelized %>Test < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, <%= camelized %> + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb new file mode 100644 index 0000000000..d06fe7cbd0 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + fixtures :all + + # Replace this with your real tests. + test "the truth" do + assert true + end +end + diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb new file mode 100644 index 0000000000..dbcaf6b92f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb @@ -0,0 +1,17 @@ +# Configure Rails Envinronment +ENV["RAILS_ENV"] = "test" + +require File.expand_path("../dummy/config/environment.rb", __FILE__) +require "rails/test_help" + +Rails.backtrace_cleaner.remove_silencers! + +<% if full? && !options[:skip_active_record] -%> +# Run any available migration from application +ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__) +# and from engine +ActiveRecord::Migrator.migrate File.expand_path("../../db/migrate/", __FILE__) +<% end -%> + +# Load support files +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb index 8a943013d3..c7345f3cfb 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -16,9 +16,9 @@ module Rails def add_resource_route return if options[:actions].present? - route_config = class_path.collect{|namespace| "namespace :#{namespace} do " }.join(" ") + route_config = regular_class_path.collect{|namespace| "namespace :#{namespace} do " }.join(" ") route_config << "resources :#{file_name.pluralize}" - route_config << " end" * class_path.size + route_config << " end" * regular_class_path.size route route_config end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index b21340f755..b5317a055b 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -1,3 +1,4 @@ +<% module_namespacing do -%> class <%= controller_class_name %>Controller < ApplicationController # GET <%= route_url %> # GET <%= route_url %>.xml @@ -81,3 +82,4 @@ class <%= controller_class_name %>Controller < ApplicationController end end end +<% end -%> diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 829f4b200a..d6ccfc496a 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -17,7 +17,7 @@ module Rails def initialize(*args) #:nodoc: super - if name == name.pluralize && !options[:force_plural] + if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural] unless ResourceHelpers.skip_warn say "Plural version of the model detected, using singularized version. Override with --force-plural." ResourceHelpers.skip_warn = true @@ -34,7 +34,7 @@ module Rails attr_reader :controller_name def controller_class_path - @class_path + class_path end def controller_file_name @@ -46,7 +46,7 @@ module Rails end def controller_class_name - @controller_class_name ||= (controller_class_path + [controller_file_name]).map!{ |m| m.camelize }.join('::') + (controller_class_path + [controller_file_name]).map!{ |m| m.camelize }.join('::') end def controller_i18n_scope diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index f23e495450..964d59d84c 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -1,5 +1,6 @@ require 'test_helper' +<% module_namespacing do -%> class <%= controller_class_name %>ControllerTest < ActionController::TestCase setup do @<%= singular_table_name %> = <%= table_name %>(:one) @@ -47,3 +48,4 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase assert_redirected_to <%= index_helper %>_path end end +<% end -%> diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 6cbd1f21c0..d05e031f56 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -1,5 +1,4 @@ require "cgi" -require "active_support/core_ext/cgi" module Rails module Info diff --git a/railties/railties.gemspec b/railties/railties.gemspec index d26c1bcdbc..c3793d3ac3 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.has_rdoc = false s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '~> 0.14.3') + s.add_dependency('thor', '~> 0.14.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 8e527236ea..719550f9d9 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -38,5 +38,14 @@ module ApplicationTests assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0", Dir.chdir(app_path){ `rake stats` } end + + def test_rake_routes_output_strips_anchors_from_http_verbs + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + get '/cart', :to => 'cart#show' + end + RUBY + assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` } + end end end diff --git a/railties/test/fixtures/lib/empty_builder.rb b/railties/test/fixtures/lib/app_builders/empty_builder.rb index babd9c2461..babd9c2461 100644 --- a/railties/test/fixtures/lib/empty_builder.rb +++ b/railties/test/fixtures/lib/app_builders/empty_builder.rb diff --git a/railties/test/fixtures/lib/app_builders/simple_builder.rb b/railties/test/fixtures/lib/app_builders/simple_builder.rb new file mode 100644 index 0000000000..993d3a2aa2 --- /dev/null +++ b/railties/test/fixtures/lib/app_builders/simple_builder.rb @@ -0,0 +1,7 @@ +class AppBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/app_builders/tweak_builder.rb b/railties/test/fixtures/lib/app_builders/tweak_builder.rb new file mode 100644 index 0000000000..cb50be01cb --- /dev/null +++ b/railties/test/fixtures/lib/app_builders/tweak_builder.rb @@ -0,0 +1,7 @@ +class AppBuilder < Rails::AppBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/create_test_dummy_template.rb b/railties/test/fixtures/lib/create_test_dummy_template.rb new file mode 100644 index 0000000000..e4378bbd1a --- /dev/null +++ b/railties/test/fixtures/lib/create_test_dummy_template.rb @@ -0,0 +1 @@ +create_dummy_app("spec/dummy") diff --git a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb new file mode 100644 index 0000000000..5c5607621c --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb @@ -0,0 +1,2 @@ +class PluginBuilder +end diff --git a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb new file mode 100644 index 0000000000..08f6c5535d --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb @@ -0,0 +1,7 @@ +class PluginBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb new file mode 100644 index 0000000000..aa18c7ddaa --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb @@ -0,0 +1,19 @@ +class PluginBuilder < Rails::PluginBuilder + def test + create_file "spec/spec_helper.rb" + append_file "Rakefile", <<-EOF +# spec tasks in rakefile + +task :default => :spec + EOF + end + + def generate_test_dummy + dummy_path("spec/dummy") + super + end + + def skip_test_unit? + true + end +end diff --git a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb new file mode 100644 index 0000000000..1e801409a4 --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb @@ -0,0 +1,7 @@ +class PluginBuilder < Rails::PluginBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/simple_builder.rb b/railties/test/fixtures/lib/simple_builder.rb deleted file mode 100644 index 47dcdc0d96..0000000000 --- a/railties/test/fixtures/lib/simple_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder - def configru - create_file "config.ru", <<-R.strip -run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] } - R - end -end
\ No newline at end of file diff --git a/railties/test/fixtures/lib/tweak_builder.rb b/railties/test/fixtures/lib/tweak_builder.rb deleted file mode 100644 index eed20ecc9b..0000000000 --- a/railties/test/fixtures/lib/tweak_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder < Rails::AppBuilder - def configru - create_file "config.ru", <<-R.strip -run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] } - R - end -end
\ No newline at end of file diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c7339cb8d2..7faa674a81 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'generators/generators_test_helper' require 'rails/generators/rails/app/app_generator' +require 'generators/shared_generator_tests.rb' DEFAULT_APP_FILES = %w( .gitignore @@ -40,36 +41,10 @@ DEFAULT_APP_FILES = %w( class AppGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments [destination_root] + include SharedGeneratorTests - def setup - Rails.application = TestApp::Application - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - - Kernel::silence_warnings do - Thor::Base.shell.send(:attr_accessor, :always_force) - @shell = Thor::Base.shell.new - @shell.send(:always_force=, true) - end - end - - def teardown - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - Rails.application = TestApp::Application.instance - end - - def test_application_skeleton_is_created - run_generator - - DEFAULT_APP_FILES.each{ |path| assert_file path } - end - - def test_application_generate_pretend - run_generator ["testapp", "--pretend"] - - DEFAULT_APP_FILES.each{ |path| assert_no_file path } + def default_files + ::DEFAULT_APP_FILES end def test_application_controller_and_layout_files @@ -78,36 +53,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "public/stylesheets/application.css" end - def test_options_before_application_name_raises_an_error - content = capture(:stderr){ run_generator(["--skip-active-record", destination_root]) } - assert_equal "Options should be given after the application name. For details run: rails --help\n", content - end - - def test_name_collision_raises_an_error - reserved_words = %w[application destroy plugin runner test] - reserved_words.each do |reserved| - content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } - assert_equal "Invalid application name #{reserved}. Please give a name which does not match one of the reserved rails words.\n", content - end - end - - def test_invalid_database_option_raises_an_error - content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } - assert_match /Invalid value for \-\-database option/, content - end - def test_invalid_application_name_raises_an_error content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] } assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content end - def test_application_name_raises_an_error_if_name_already_used_constant - %w{ String Hash Class Module Set Symbol }.each do |ruby_class| - content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } - assert_equal "Invalid application name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another application name.\n", content - end - end - def test_invalid_application_name_is_fixed run_generator [File.join(destination_root, "things-43")] assert_file "things-43/config/environment.rb", /Things43::Application\.initialize!/ @@ -133,11 +83,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/ assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/ end - + def test_rails_update_generates_correct_session_key app_root = File.join(destination_root, 'myapp') run_generator [app_root] - + Rails.application.config.root = app_root Rails.application.class.stubs(:name).returns("Myapp") Rails.application.stubs(:is_a?).returns(Rails::Application) @@ -187,12 +137,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "public/javascripts/rails.js" assert_file "test" end - - def test_test_unit_is_skipped_if_required - run_generator [destination_root, "--skip-test-unit"] - assert_no_file "test" - end - + def test_javascript_is_skipped_if_required run_generator [destination_root, "--skip-javascript"] assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/ @@ -200,7 +145,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "public/javascripts/prototype.js" assert_no_file "public/javascripts/rails.js" end - + def test_config_prototype_javascript_library run_generator [destination_root, "-j", "prototype"] assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/ @@ -208,7 +153,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "public/javascripts/prototype.js" assert_file "public/javascripts/rails.js", /prototype/ end - + def test_config_jquery_javascript_library run_generator [destination_root, "-j", "jquery"] assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/ @@ -217,36 +162,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "public/javascripts/rails.js", /jQuery/ end - def test_shebang_is_added_to_rails_file - run_generator [destination_root, "--ruby", "foo/bar/baz"] - assert_file "script/rails", /#!foo\/bar\/baz/ - end - - def test_shebang_when_is_the_same_as_default_use_env - run_generator [destination_root, "--ruby", Thor::Util.ruby_command] - assert_file "script/rails", /#!\/usr\/bin\/env/ - end - def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match /It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]) end - def test_template_raises_an_error_with_invalid_path - content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) } - assert_match /The template \[.*\] could not be loaded/, content - assert_match /non\/existant\/path/, content - end - - def test_template_is_executed_when_supplied - path = "http://gist.github.com/103208.txt" - template = %{ say "It works!" } - template.instance_eval "def read; self; end" # Make the string respond to read - - generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - assert_match /It works!/, silence(:stdout){ generator.invoke_all } - end - def test_usage_read_from_file File.expects(:read).returns("USAGE FROM FILE") assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc @@ -266,17 +186,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file 'lib/test_file.rb', 'heres test data' end - def test_dev_option - generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } - rails_path = File.expand_path('../../..', Rails.root) - assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ - end - - def test_edge_option - generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } - assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/ + def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given + run_generator [destination_root, "--skip-test-unit"] + assert_file "config/application.rb" do |file| + assert_match /config.generators.test_framework = false/, file + end end protected @@ -292,62 +206,21 @@ class CustomAppGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::AppGenerator arguments [destination_root] + include SharedCustomGeneratorTests - def setup - Rails.application = TestApp::Application - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - end - - def teardown - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - Object.class_eval { remove_const :AppBuilder if const_defined?(:AppBuilder) } - Rails.application = TestApp::Application.instance - end - - def test_builder_option_with_empty_app_builder - FileUtils.cd(Rails.root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/empty_builder.rb"]) - DEFAULT_APP_FILES.each{ |path| assert_no_file path } - end - - def test_builder_option_with_simple_app_builder - FileUtils.cd(Rails.root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/simple_builder.rb"]) - (DEFAULT_APP_FILES - ['config.ru']).each{ |path| assert_no_file path } - assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }] - end - - def test_builder_option_with_relative_path - here = File.expand_path(File.dirname(__FILE__)) - FileUtils.cd(here) - run_generator([destination_root, "-b", "../fixtures/lib/simple_builder.rb"]) - (DEFAULT_APP_FILES - ['config.ru']).each{ |path| assert_no_file path } - assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }] +protected + def default_files + ::DEFAULT_APP_FILES end - def test_builder_option_with_tweak_app_builder - FileUtils.cd(Rails.root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/tweak_builder.rb"]) - DEFAULT_APP_FILES.each{ |path| assert_file path } - assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }] + def builders_dir + "app_builders" end - def test_builder_option_with_http - path = "http://gist.github.com/103208.txt" - template = "class AppBuilder; end" - template.instance_eval "def read; self; end" # Make the string respond to read - - generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - capture(:stdout) { generator.invoke_all } - - DEFAULT_APP_FILES.each{ |path| assert_no_file path } + def builder_class + :AppBuilder end -protected - def action(*args, &block) silence(:stdout){ generator.send(*args, &block) } end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index d1190fd17d..d61a02d32f 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -3,6 +3,7 @@ require 'rails/generators/rails/controller/controller_generator' require 'rails/generators/rails/model/model_generator' require 'rails/generators/rails/observer/observer_generator' require 'rails/generators/mailer/mailer_generator' +require 'rails/generators/rails/scaffold/scaffold_generator' class NamespacedGeneratorTestCase < Rails::Generators::TestCase def setup @@ -202,3 +203,155 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase assert_file "app/views/test_app/notifier" end end + +class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase + include GeneratorsTestHelper + arguments %w(product_line title:string price:integer) + tests Rails::Generators::ScaffoldGenerator + + setup :copy_routes + + def test_scaffold_on_invoke + run_generator + + # Model + assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ActiveRecord::Base/ + assert_file "test/unit/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/product_lines.yml" + assert_migration "db/migrate/create_test_app_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/resources :product_lines$/, route) + end + + # Controller + assert_file "app/controllers/test_app/product_lines_controller.rb" do |content| + assert_match(/module TestApp\n class ProductLinesController < ApplicationController/, content) + end + + assert_file "test/functional/test_app/product_lines_controller_test.rb", + /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/product_lines/#{view}.html.erb" } + assert_no_file "app/views/layouts/test_app/product_lines.html.erb" + + # Helpers + assert_file "app/helpers/test_app/product_lines_helper.rb" + assert_file "test/unit/helpers/test_app/product_lines_helper_test.rb" + + # Stylesheets + assert_file "public/stylesheets/scaffold.css" + end + + def test_scaffold_on_revoke + run_generator + run_generator ["product_line"], :behavior => :revoke + + # Model + assert_no_file "app/models/test_app/product_line.rb" + assert_no_file "test/unit/test_app/product_line_test.rb" + assert_no_file "test/fixtures/test_app/product_lines.yml" + assert_no_migration "db/migrate/create_test_app_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :product_lines$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/product_lines_controller.rb" + assert_no_file "test/functional/test_app/product_lines_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/product_lines" + assert_no_file "app/views/test_app/layouts/product_lines.html.erb" + + # Helpers + assert_no_file "app/helpers/test_app/product_lines_helper.rb" + assert_no_file "test/unit/helpers/test_app/product_lines_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "public/stylesheets/scaffold.css" + end + + def test_scaffold_with_namespace_on_invoke + run_generator [ "admin/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ + assert_file "test/unit/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/roles.yml" + assert_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/namespace :admin do resources :roles end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content) + end + + assert_file "test/functional/test_app/admin/roles_controller_test.rb", + /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/admin/roles/#{view}.html.erb" } + assert_no_file "app/views/layouts/admin/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/roles_helper.rb" + assert_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" + + # Stylesheets + assert_file "public/stylesheets/scaffold.css" + end + + def test_scaffold_with_namespace_on_revoke + run_generator [ "admin/role", "name:string", "description:string" ] + run_generator [ "admin/role" ], :behavior => :revoke + + # Model + assert_file "app/models/test_app/admin.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/role.rb" + assert_no_file "test/unit/test_app/admin/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/namespace :admin do resources :roles end$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/roles_controller.rb" + assert_no_file "test/functional/test_app/admin/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/roles" + assert_no_file "app/views/layouts/test_app/admin/roles.html.erb" + + # Helpers + assert_no_file "app/helpers/test_app/admin/roles_helper.rb" + assert_no_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "public/stylesheets/scaffold.css" + end +end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb new file mode 100644 index 0000000000..12cae7cfdf --- /dev/null +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -0,0 +1,191 @@ +require 'abstract_unit' +require 'generators/generators_test_helper' +require 'rails/generators/rails/plugin_new/plugin_new_generator' +require 'generators/shared_generator_tests.rb' + +DEFAULT_PLUGIN_FILES = %w( + .gitignore + Gemfile + Rakefile + bukkits.gemspec + MIT-LICENSE + lib + lib/bukkits.rb + lib/tasks/bukkits_tasks.rake + script/rails + test/bukkits_test.rb + test/test_helper.rb + test/dummy +) + +class PluginNewGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + destination File.join(Rails.root, "tmp/bukkits") + arguments [destination_root] + include SharedGeneratorTests + + def default_files + ::DEFAULT_PLUGIN_FILES + end + + def test_invalid_plugin_name_raises_an_error + content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] } + assert_equal "Invalid plugin name 43-things. Please give a name which does not start with numbers.\n", content + end + + def test_invalid_plugin_name_is_fixed + run_generator [File.join(destination_root, "things-43")] + assert_file "things-43/lib/things-43.rb", /module Things43/ + end + + def test_generating_test_files + run_generator + assert_file "test/test_helper.rb" + assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/ + end + + def test_generating_test_files_in_full_mode + run_generator [destination_root, "--full"] + assert_directory "test/integration/" + + assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ + end + + def test_ensure_that_plugin_options_are_not_passed_to_app_generator + FileUtils.cd(Rails.root) + assert_no_match /It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]) + end + + def test_ensure_that_test_dummy_can_be_generated_from_a_template + FileUtils.cd(Rails.root) + run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test-unit"]) + assert_file "spec/dummy" + assert_no_file "test" + end + + def test_database_entry_is_assed_by_default_in_full_mode + run_generator([destination_root, "--full"]) + assert_file "test/dummy/config/database.yml", /sqlite/ + assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/ + end + + def test_config_another_database + run_generator([destination_root, "-d", "mysql", "--full"]) + assert_file "test/dummy/config/database.yml", /mysql/ + assert_file "Gemfile", /^gem\s+["']mysql2["']$/ + end + + def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + end + + def test_ensure_that_skip_active_record_option_is_passed_to_app_generator + run_generator [destination_root, "--skip_active_record"] + assert_no_file "test/dummy/config/database.yml" + assert_no_match /ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb")) + end + + def test_ensure_that_database_option_is_passed_to_app_generator + run_generator [destination_root, "--database", "postgresql"] + assert_file "test/dummy/config/database.yml", /postgres/ + 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" + end + + def test_ensure_that_skip_javascript_option_is_passed_to_app_generator + run_generator [destination_root, "--skip_javascript"] + assert_no_file "test/dummy/public/javascripts/prototype.js" + end + + def test_template_from_dir_pwd + FileUtils.cd(Rails.root) + assert_match /It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]) + end + + def test_ensure_that_tests_works + run_generator + FileUtils.cd destination_root + `bundle install` + assert_match /1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test` + end + + def test_ensure_that_tests_works_in_full_mode + run_generator [destination_root, "--full"] + FileUtils.cd destination_root + `bundle install` + assert_match /2 tests, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test` + end + + def test_creating_engine_in_full_mode + run_generator [destination_root, "--full"] + assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < Rails::Engine\n end\nend/ + assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ + end + + def test_being_quiet_while_creating_dummy_application + assert_no_match /create\s+config\/application.rb/, run_generator + end + + def test_create_mountable_application_with_mountable_option + run_generator [destination_root, "--mountable"] + assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ + assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ + assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ + assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActiveController::Base/ + assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/ + end + + def test_passing_dummy_path_as_a_parameter + run_generator [destination_root, "--dummy_path", "spec/dummy"] + assert_file "spec/dummy" + assert_file "spec/dummy/config/application.rb" + assert_no_file "test/dummy" + end + +protected + + def action(*args, &block) + silence(:stdout){ generator.send(*args, &block) } + end + +end + +class CustomPluginGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests Rails::Generators::PluginNewGenerator + + destination File.join(Rails.root, "tmp/bukkits") + arguments [destination_root] + include SharedCustomGeneratorTests + + def test_overriding_test_framework + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/plugin_builders/spec_builder.rb"]) + assert_file 'spec/spec_helper.rb' + assert_file 'spec/dummy' + assert_file 'Rakefile', /task :default => :spec/ + assert_file 'Rakefile', /# spec tasks in rakefile/ + assert_file 'script/rails', %r{spec/dummy} + end + +protected + def default_files + ::DEFAULT_PLUGIN_FILES + end + + def builder_class + :PluginBuilder + end + + def builders_dir + "plugin_builders" + end + + def action(*args, &block) + silence(:stdout){ generator.send(*args, &block) } + end +end diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 55d5bd6f83..71b3619351 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -73,6 +73,11 @@ class ResourceGeneratorTest < Rails::Generators::TestCase assert_no_match /Plural version of the model detected/, content end + def test_mass_nouns_do_not_throw_warnings + content = run_generator ["sheep".freeze] + assert_no_match /Plural version of the model detected/, content + end + def test_route_is_removed_on_revoke run_generator run_generator ["account"], :behavior => :revoke diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb new file mode 100644 index 0000000000..d117656fbd --- /dev/null +++ b/railties/test/generators/shared_generator_tests.rb @@ -0,0 +1,187 @@ +module SharedGeneratorTests + def setup + Rails.application = TestApp::Application + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + + Kernel::silence_warnings do + Thor::Base.shell.send(:attr_accessor, :always_force) + @shell = Thor::Base.shell.new + @shell.send(:always_force=, true) + end + end + + def teardown + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + Rails.application = TestApp::Application.instance + end + + def test_skeleton_is_created + run_generator + + default_files.each{ |path| assert_file path } + end + + def test_plugin_new_generate_pretend + run_generator ["testapp", "--pretend"] + + default_files.each{ |path| assert_no_file path } + end + + def test_invalid_database_option_raises_an_error + content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } + assert_match /Invalid value for \-\-database option/, content + end + + def test_test_unit_is_skipped_if_required + run_generator [destination_root, "--skip-test-unit"] + assert_no_file "test" + end + + def test_options_before_application_name_raises_an_error + content = capture(:stderr){ run_generator(["--pretend", destination_root]) } + assert_match /Options should be given after the \w+ name. For details run: rails( plugin)? --help\n/, content + end + + def test_name_collision_raises_an_error + reserved_words = %w[application destroy plugin runner test] + reserved_words.each do |reserved| + content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } + assert_match /Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content + end + end + + def test_name_raises_an_error_if_name_already_used_constant + %w{ String Hash Class Module Set Symbol }.each do |ruby_class| + content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } + assert_match /Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content + end + end + + def test_shebang_is_added_to_rails_file + run_generator [destination_root, "--ruby", "foo/bar/baz"] + assert_file "script/rails", /#!foo\/bar\/baz/ + end + + def test_shebang_when_is_the_same_as_default_use_env + run_generator [destination_root, "--ruby", Thor::Util.ruby_command] + assert_file "script/rails", /#!\/usr\/bin\/env/ + end + + def test_template_raises_an_error_with_invalid_path + content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) } + assert_match /The template \[.*\] could not be loaded/, content + assert_match /non\/existant\/path/, content + end + + def test_template_is_executed_when_supplied + path = "http://gist.github.com/103208.txt" + template = %{ say "It works!" } + template.instance_eval "def read; self; end" # Make the string respond to read + + generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) + assert_match /It works!/, silence(:stdout){ generator.invoke_all } + end + + def test_dev_option + generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + rails_path = File.expand_path('../../..', Rails.root) + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$} + end + + def test_template_raises_an_error_with_invalid_path + content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) } + assert_match /The template \[.*\] could not be loaded/, content + assert_match /non\/existant\/path/, content + end + + def test_template_is_executed_when_supplied + path = "http://gist.github.com/103208.txt" + template = %{ say "It works!" } + template.instance_eval "def read; self; end" # Make the string respond to read + + generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) + assert_match /It works!/, silence(:stdout){ generator.invoke_all } + end + + def test_dev_option + generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + rails_path = File.expand_path('../../..', Rails.root) + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$} + end +end + +module SharedCustomGeneratorTests + def setup + Rails.application = TestApp::Application + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + end + + def teardown + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + Object.class_eval do + remove_const :AppBuilder if const_defined?(:AppBuilder) + remove_const :PluginBuilder if const_defined?(:PluginBuilder) + end + Rails.application = TestApp::Application.instance + end + + def test_builder_option_with_empty_app_builder + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/empty_builder.rb"]) + default_files.each{ |path| assert_no_file path } + end + + def test_builder_option_with_simple_plugin_builder + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/simple_builder.rb"]) + (default_files - ['.gitignore']).each{ |path| assert_no_file path } + assert_file ".gitignore", "foobar" + end + + def test_builder_option_with_relative_path + here = File.expand_path(File.dirname(__FILE__)) + FileUtils.cd(here) + run_generator([destination_root, "-b", "../fixtures/lib/#{builders_dir}/simple_builder.rb"]) + FileUtils.cd(destination_root) + (default_files - ['.gitignore']).each{ |path| assert_no_file path } + assert_file ".gitignore", "foobar" + end + + def test_builder_option_with_tweak_plugin_builder + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/tweak_builder.rb"]) + default_files.each{ |path| assert_file path } + assert_file ".gitignore", "foobar" + end + + def test_builder_option_with_http + path = "http://gist.github.com/103208.txt" + template = "class #{builder_class}; end" + template.instance_eval "def read; self; end" # Make the string respond to read + + generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) + capture(:stdout) { generator.invoke_all } + + default_files.each{ |path| assert_no_file path } + end +end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 4b52260ecc..822be24ef1 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -685,5 +685,25 @@ module RailtiesTest assert_equal :haml , generators[:template_engine] assert_equal :rspec , generators[:test_framework] end + + test "engine should get default generators with ability to overwrite them" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + config.generators.test_framework :rspec + end + end + RUBY + + boot_rails + require "#{rails_root}/config/environment" + + generators = Bukkits::Engine.config.generators.options[:rails] + assert_equal :active_record, generators[:orm] + assert_equal :rspec , generators[:test_framework] + + app_generators = Rails.application.config.generators.options[:rails] + assert_equal :test_unit , app_generators[:test_framework] + end end end |