From 7a4a679dbada2e1fcfc28db8c47dd32e03afc1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 30 Jul 2009 22:43:37 +0200 Subject: Remove any resource logic from respond_to. --- .../lib/action_controller/base/mime_responds.rb | 31 ++++++++++------------ 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb index f4a4007a43..485668d543 100644 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ b/actionpack/lib/action_controller/base/mime_responds.rb @@ -177,19 +177,21 @@ module ActionController #:nodoc: # Be sure to check respond_with and respond_to documentation for more examples. # def respond_to(*mimes, &block) - options = mimes.extract_options! raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? - resource = options.delete(:with) responder = Responder.new - mimes = collect_mimes_from_class_level if mimes.empty? mimes.each { |mime| responder.send(mime) } block.call(responder) if block_given? if format = request.negotiate_mime(responder.order) - respond_to_block_or_template_or_resource(format, resource, - options, &responder.response_for(format)) + self.formats = [format.to_sym] + + if response = responder.response_for(format) + response.call + else + default_render + end else head :not_acceptable end @@ -257,26 +259,21 @@ module ActionController #:nodoc: # end # def respond_with(resource, options={}, &block) - respond_to(options.merge!(:with => resource), &block) - end - - protected - - def respond_to_block_or_template_or_resource(format, resource, options) - self.formats = [format.to_sym] - return yield if block_given? - begin - default_render + respond_to(&block) rescue ActionView::MissingTemplate => e - if resource && resource.respond_to?(:"to_#{format.to_sym}") - render options.merge(format.to_sym => resource) + format = self.formats.first + + if resource.respond_to?(:"to_#{format}") + render options.merge(format => resource) else raise e end end end + protected + # Collect mimes declared in the class method respond_to valid for the # current action. # -- cgit v1.2.3 From 5b7e81efec649b424037c68a93bddad1bc4e0c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 31 Jul 2009 11:59:05 +0200 Subject: Allow respond_with to deal with http verb accordingly. --- .../lib/action_controller/base/mime_responds.rb | 111 ++++++++++++++------- actionpack/test/controller/mime_responds_test.rb | 65 +++++++++++- 2 files changed, 136 insertions(+), 40 deletions(-) diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb index 485668d543..9a6c8aa58b 100644 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ b/actionpack/lib/action_controller/base/mime_responds.rb @@ -202,11 +202,11 @@ module ActionController #:nodoc: # formats allowed: # # class PeopleController < ApplicationController - # respond_to :html, :xml, :json + # respond_to :xml, :json # # def index # @people = Person.find(:all) - # respond_with(@person) + # respond_with(@people) # end # end # @@ -216,64 +216,101 @@ module ActionController #:nodoc: # # If neither are available, it will raise an error. # - # Extra parameters given to respond_with are used when :to_format is invoked. - # This allows you to set status and location for several formats at the same - # time. Consider this restful controller response on create for both xml - # and json formats: + # respond_with holds semantics for each HTTP verb. The example above cover + # GET requests. Let's check a POST request example: + # + # def create + # @person = Person.new(params[:person]) + # @person.save + # respond_with(@person) + # end + # + # Since the request is a POST, respond_with will check wether @people + # resource have errors or not. If it has errors, it will render the error + # object with unprocessable entity status (422). + # + # If no error was found, it will render the @people resource, with status + # created (201) and location header set to person_url(@people). + # + # If you also want to provide html behavior in the method above, you can + # supply a block to customize it: # # class PeopleController < ApplicationController - # respond_to :xml, :json + # respond_to :html, :xml, :json # Add :html to respond_to definition # # def create - # @person = Person.new(params[:person]) - # - # if @person.save - # respond_with(@person, :status => :ok, :location => person_url(@person)) - # else - # respond_with(@person.errors, :status => :unprocessable_entity) + # @person = Person.new(params[:pe]) + # + # respond_with(@person) do |format| + # if @person.save + # flash[:notice] = 'Person was successfully created.' + # format.html { redirect_to @person } + # else + # format.html { render :action => "new" } + # end # end # end # end # - # Finally, respond_with also accepts blocks, as in respond_to. Let's take - # the same controller and create action above and add common html behavior: + # It works similarly for PUT requests: # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json + # def update + # @person = Person.find(params[:id]) + # @person.update_attributes(params[:person]) + # respond_with(@person) + # end # - # def create - # @person = Person.new(params[:person]) + # In case of failures, it works as POST requests, but in success failures + # it just reply status ok (200) to the client. # - # if @person.save - # options = { :status => :ok, :location => person_url(@person) } + # A DELETE request also works in the same way: # - # respond_with(@person, options) do |format| - # format.html { redirect_to options[:location] } - # end - # else - # respond_with(@person.errors, :status => :unprocessable_entity) do - # format.html { render :action => :new } - # end - # end - # end + # def destroy + # @person = Person.find(params[:id]) + # @person.destroy + # respond_with(@person) # end # + # It just replies with status ok, indicating the record was successfuly + # destroyed. + # def respond_with(resource, options={}, &block) - begin - respond_to(&block) - rescue ActionView::MissingTemplate => e - format = self.formats.first + respond_to(&block) + rescue ActionView::MissingTemplate => e + format = self.formats.first + resource = normalize_resource_options_by_verb(resource, options) - if resource.respond_to?(:"to_#{format}") - render options.merge(format => resource) + if resource.respond_to?(:"to_#{format}") + if options.delete(:no_content) + head options else - raise e + render options.merge(format => resource) end + else + raise e end end protected + # Change respond with behavior based on the HTTP verb. + # + def normalize_resource_options_by_verb(resource_or_array, options) + resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array + has_errors = resource.respond_to?(:errors) && !resource.errors.empty? + + if has_errors && (request.post? || request.put?) + options.reverse_merge!(:status => :unprocessable_entity) + return resource.errors + elsif request.post? + options.reverse_merge!(:status => :created, :location => resource_or_array) + elsif !request.get? + options.reverse_merge!(:status => :ok, :no_content => true) + end + + return resource + end + # Collect mimes declared in the class method respond_to valid for the # current action. # diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 117f4ea4f0..1db951fdfe 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -474,6 +474,14 @@ end class RespondResource undef_method :to_json + def self.model_name + @_model_name ||= ActiveModel::Name.new(name) + end + + def to_param + 13 + end + def to_xml "XML" end @@ -481,6 +489,10 @@ class RespondResource def to_js "JS" end + + def errors + [] + end end class RespondWithController < ActionController::Base @@ -520,6 +532,10 @@ protected self.content_type ||= Mime::JS self.response_body = js.respond_to?(:to_js) ? js.to_js : js end + + def respond_resource_url(id) + request.host + "/respond/resource/#{id.to_param}" + end end class InheritedRespondWithController < RespondWithController @@ -593,6 +609,51 @@ class RespondWithControllerTest < ActionController::TestCase end end + def test_using_resource_for_post + @request.accept = "application/xml" + + post :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 201, @response.status + assert_equal "XML", @response.body + assert_equal "www.example.com/respond/resource/13", @response.location + + errors = { :name => :invalid } + RespondResource.any_instance.stubs(:errors).returns(errors) + post :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 422, @response.status + assert_equal errors.to_xml, @response.body + assert_nil @response.location + end + + def test_using_resource_for_put + @request.accept = "application/xml" + + put :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_equal " ", @response.body + assert_nil @response.location + + errors = { :name => :invalid } + RespondResource.any_instance.stubs(:errors).returns(errors) + put :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 422, @response.status + assert_equal errors.to_xml, @response.body + assert_nil @response.location + end + + def test_using_resource_for_delete + @request.accept = "application/xml" + delete :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_equal " ", @response.body + assert_nil @response.location + end + def test_using_resource_with_options @request.accept = "application/xml" get :using_resource_with_options @@ -648,8 +709,6 @@ class RespondWithControllerTest < ActionController::TestCase end class AbstractPostController < ActionController::Base - respond_to :html, :iphone - self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/" end @@ -658,7 +717,7 @@ class PostController < AbstractPostController around_filter :with_iphone def index - respond_to # It will use formats declared above + respond_to(:html, :iphone) end protected -- cgit v1.2.3 From b2d24baf790fee4f932fa32a8ae94f0212d14ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 31 Jul 2009 20:56:53 +0200 Subject: Added tests for nested resources. --- actionpack/test/controller/mime_responds_test.rb | 60 +++++++++++++++++++----- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 1db951fdfe..1d27e749ae 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -475,7 +475,7 @@ class RespondResource undef_method :to_json def self.model_name - @_model_name ||= ActiveModel::Name.new(name) + @_model_name ||= ActiveModel::Name.new("resource") end def to_param @@ -495,6 +495,16 @@ class RespondResource end end +class ParentResource + def self.model_name + @_model_name ||= ActiveModel::Name.new("parent") + end + + def to_param + 11 + end +end + class RespondWithController < ActionController::Base respond_to :html, :json respond_to :xml, :except => :using_defaults @@ -510,6 +520,12 @@ class RespondWithController < ActionController::Base respond_to(:js, :xml) end + def default_overwritten + respond_to do |format| + format.html { render :text => "HTML" } + end + end + def using_resource respond_with(RespondResource.new) end @@ -520,10 +536,8 @@ class RespondWithController < ActionController::Base end end - def default_overwritten - respond_to do |format| - format.html { render :text => "HTML" } - end + def using_resource_with_parent + respond_with([ParentResource.new, RespondResource.new]) end protected @@ -533,8 +547,12 @@ protected self.response_body = js.respond_to?(:to_js) ? js.to_js : js end - def respond_resource_url(id) - request.host + "/respond/resource/#{id.to_param}" + def resource_url(resource) + request.host + "/resource/#{resource.to_param}" + end + + def parent_resource_url(parent, resource) + request.host + "/parent/#{parent.to_param}/resource/#{resource.to_param}" end end @@ -592,6 +610,12 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "

Hello world!

\n", @response.body end + def test_default_overwritten + get :default_overwritten + assert_equal "text/html", @response.content_type + assert_equal "HTML", @response.body + end + def test_using_resource @request.accept = "text/html" get :using_resource @@ -616,7 +640,7 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 201, @response.status assert_equal "XML", @response.body - assert_equal "www.example.com/respond/resource/13", @response.location + assert_equal "www.example.com/resource/13", @response.location errors = { :name => :invalid } RespondResource.any_instance.stubs(:errors).returns(errors) @@ -668,10 +692,22 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "JS", @response.body end - def test_default_overwritten - get :default_overwritten - assert_equal "text/html", @response.content_type - assert_equal "HTML", @response.body + def test_using_resource_with_parent + @request.accept = "application/xml" + + post :using_resource_with_parent + assert_equal "application/xml", @response.content_type + assert_equal 201, @response.status + assert_equal "XML", @response.body + assert_equal "www.example.com/parent/11/resource/13", @response.location + + errors = { :name => :invalid } + RespondResource.any_instance.stubs(:errors).returns(errors) + post :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 422, @response.status + assert_equal errors.to_xml, @response.body + assert_nil @response.location end def test_clear_respond_to -- cgit v1.2.3 From 7e560d0b0d9414d5712f068f6eb5f8bde1980558 Mon Sep 17 00:00:00 2001 From: Rob Christie Date: Wed, 14 Jan 2009 01:14:02 -0500 Subject: Fixed adapter test cases that were failing in oracle because the asserts were looking for the presence of offset and limit which are not available in oracle. Changed the tests to check that the sql injection is not present in the output so that the tests are database adapter agnostic. --- activerecord/test/cases/adapter_test.rb | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 80530194ff..65c5fc2fe9 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -112,23 +112,14 @@ class AdapterTest < ActiveRecord::TestCase def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas sql_inject = "1 select * from schema" - assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit=>sql_inject) - if current_adapter?(:MysqlAdapter) - assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - else - assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - end + assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject) + assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) end def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas sql_inject = "1, 7 procedure help()" - if current_adapter?(:MysqlAdapter) - assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject) - assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7) - else - assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject) - assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - end + assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject) + assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) end def test_uniqueness_violations_are_translated_to_specific_exception -- cgit v1.2.3 From 04fea8a07ba94d381b03fae4a657077a7b966229 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 15 Mar 2009 14:40:07 +0200 Subject: modified native_oracle/connection.rb to run it with oracle_enhanced adapter --- .../test/connections/native_oracle/connection.rb | 25 ++++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 0954b27f87..1be4e23b84 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,25 +1,36 @@ +# gem "rsim-activerecord-oracle_enhanced-adapter" +# gem "activerecord-oracle_enhanced-adapter" +# uses local copy of oracle_enhanced adapter +$:.unshift("../../oracle-enhanced/lib") +require 'active_record/connection_adapters/oracle_enhanced_adapter' + print "Using Oracle\n" require_dependency 'models/course' require 'logger' -ActiveRecord::Base.logger = Logger.new STDOUT -ActiveRecord::Base.logger.level = Logger::WARN +# ActiveRecord::Base.logger = Logger.new STDOUT +# ActiveRecord::Base.logger.level = Logger::WARN +ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB'] || 'activerecord_unittest' +db = ENV['ARUNIT_DB'] || 'XE' ActiveRecord::Base.configurations = { 'arunit' => { - :adapter => 'oracle', + :adapter => 'oracle_enhanced', + :database => db, + :host => "arunit", # used just by JRuby to construct JDBC connect string :username => 'arunit', :password => 'arunit', - :database => db, + :emulate_oracle_adapter => true }, 'arunit2' => { - :adapter => 'oracle', + :adapter => 'oracle_enhanced', + :database => db, + :host => "arunit", # used just by JRuby to construct JDBC connect string :username => 'arunit2', :password => 'arunit2', - :database => db + :emulate_oracle_adapter => true } } -- cgit v1.2.3 From 963570b51ce4d95b046a441dd1c413dc6fcec8b4 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 22 Mar 2009 23:50:05 +0200 Subject: added additional objects necessary for OracleAdapter specific tests if OracleAdapter is used then use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in Oracle SELECT WHERE clause which causes many unit test failures --- activerecord/test/schema/oracle_specific_schema.rb | 27 ++++++++++++++++++++++ activerecord/test/schema/schema.rb | 24 ++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb index 2d87f34625..3314687445 100644 --- a/activerecord/test/schema/oracle_specific_schema.rb +++ b/activerecord/test/schema/oracle_specific_schema.rb @@ -2,6 +2,10 @@ ActiveRecord::Schema.define do execute "drop table test_oracle_defaults" rescue nil execute "drop sequence test_oracle_defaults_seq" rescue nil + execute "drop sequence companies_nonstd_seq" rescue nil + execute "drop synonym subjects" rescue nil + execute "drop table defaults" rescue nil + execute "drop sequence defaults_seq" rescue nil execute <<-SQL create table test_oracle_defaults ( @@ -16,4 +20,27 @@ create table test_oracle_defaults ( create sequence test_oracle_defaults_seq minvalue 10000 SQL + execute "create sequence companies_nonstd_seq minvalue 10000" + + execute "create synonym subjects for topics" + + execute <<-SQL + CREATE TABLE defaults ( + id integer not null, + modified_date date default sysdate, + modified_date_function date default sysdate, + fixed_date date default to_date('2004-01-01', 'YYYY-MM-DD'), + modified_time date default sysdate, + modified_time_function date default sysdate, + fixed_time date default TO_DATE('2004-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), + char1 varchar2(1) default 'Y', + char2 varchar2(50) default 'a varchar field', + char3 clob default 'a text field', + positive_integer integer default 1, + negative_integer integer default -1, + decimal_number number(3,2) default 2.78 + ) + SQL + execute "create sequence defaults_seq minvalue 10000" + end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 1e47cdbaf6..5752d6fa40 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -104,7 +104,13 @@ ActiveRecord::Schema.define do create_table :comments, :force => true do |t| t.integer :post_id, :null => false - t.text :body, :null => false + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :body, :null => false, :limit => 4000 + else + t.text :body, :null => false + end t.string :type end @@ -350,7 +356,13 @@ ActiveRecord::Schema.define do create_table :posts, :force => true do |t| t.integer :author_id t.string :title, :null => false - t.text :body, :null => false + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :body, :null => false, :limit => 4000 + else + t.text :body, :null => false + end t.string :type t.integer :comments_count, :default => 0 t.integer :taggings_count, :default => 0 @@ -423,7 +435,13 @@ ActiveRecord::Schema.define do t.datetime :written_on t.time :bonus_time t.date :last_read - t.text :content + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :content, :limit => 4000 + else + t.text :content + end t.boolean :approved, :default => true t.integer :replies_count, :default => 0 t.integer :parent_id -- cgit v1.2.3 From 5666a3ad065469f12e5b3a4de0be823c9ae4ff7d Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 22 Mar 2009 23:57:24 +0200 Subject: added :order option to find :first methods and associations as otherwise Oracle tests were failing Oracle stores '' string as NULL Oracle cannot have identifiers larger than 30 characters added missing fixtures to test setup method --- .../associations/belongs_to_associations_test.rb | 6 +- activerecord/test/cases/associations/eager_test.rb | 7 +- .../has_and_belongs_to_many_associations_test.rb | 6 +- .../associations/has_many_associations_test.rb | 80 ++++++++++++---------- .../test/cases/associations/join_model_test.rb | 10 ++- activerecord/test/models/company.rb | 8 ++- 6 files changed, 69 insertions(+), 48 deletions(-) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index ab6f752243..784c484178 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -293,7 +293,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object c = Client.new("firm_id" => 1) - assert_equal Firm.find(:first), c.firm_with_basic_id + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id end def test_forgetting_the_load_when_foreign_key_enters_late @@ -301,7 +302,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nil c.firm_with_basic_id c.firm_id = 1 - assert_equal Firm.find(:first), c.firm_with_basic_id + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id end def test_field_name_same_as_foreign_key diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 4cf49be668..811ebfbe3f 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -813,7 +813,12 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_many_using_primary_key expected = Firm.find(1).clients_using_primary_key.sort_by &:name - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + # Oracle adapter truncates alias to 30 characters + if current_adapter?(:OracleAdapter) + firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name' + else + firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + end assert_no_queries do assert_equal expected, firm.clients_using_primary_key end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 14b96caaae..11a159686e 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -284,12 +284,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_creation_respects_hash_condition - post = categories(:general).post_with_conditions.build(:body => '') + # in Oracle '' is saved as null therefore need to save ' ' in not null column + post = categories(:general).post_with_conditions.build(:body => ' ') assert post.save assert_equal 'Yet Another Testing Title', post.title - another_post = categories(:general).post_with_conditions.create(:body => '') + # in Oracle '' is saved as null therefore need to save ' ' in not null column + another_post = categories(:general).post_with_conditions.create(:body => ' ') assert !another_post.new_record? assert_equal 'Yet Another Testing Title', another_post.title diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 15919e2289..a3d92c3bdb 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -24,28 +24,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.each {|f| } end + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 2, Firm.find(:first).clients.count + assert_equal 2, Firm.find(:first, :order => "id").clients.count end def test_counting - assert_equal 2, Firm.find(:first).plain_clients.count + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count end def test_counting_with_empty_hash_conditions - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {}) + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {}) end def test_counting_with_single_conditions - assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"]) + assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"]) end def test_counting_with_single_hash - assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"}) + assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"}) end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:name) + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name) end def test_counting_with_association_limit @@ -55,12 +56,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.find(:first).clients.length + assert_equal 2, Firm.find(:first, :order => "id").clients.length end def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| - assert_equal 2, Firm.find(:first).clients.find(:all, :conditions => blank).size + assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size end end @@ -115,52 +116,53 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_triple_equality - assert !(Array === Firm.find(:first).clients) - assert Firm.find(:first).clients === Array + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert !(Array === Firm.find(:first, :order => "id").clients) + assert Firm.find(:first, :order => "id").clients === Array end def test_finding_default_orders - assert_equal "Summit", Firm.find(:first).clients.first.name + assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name + assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name end def test_finding_using_sql - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") first_client = firm.clients_using_sql.first assert_not_nil first_client assert_equal "Microsoft", first_client.name assert_equal 1, firm.clients_using_sql.size - assert_equal 1, Firm.find(:first).clients_using_sql.size + assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size end def test_counting_using_sql - assert_equal 1, Firm.find(:first).clients_using_counter_sql.size - assert Firm.find(:first).clients_using_counter_sql.any? - assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size - assert !Firm.find(:first).clients_using_zero_counter_sql.any? + assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size + assert Firm.find(:first, :order => "id").clients_using_counter_sql.any? + assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size + assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any? end def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size + assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size end def test_counting_using_finder_sql @@ -183,7 +185,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -203,7 +205,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_string_ids_when_using_finder_sql - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client = firm.clients_using_finder_sql.find("2") assert_kind_of Client, client @@ -219,7 +221,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length end @@ -264,24 +266,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all_sanitized - firm = Firm.find(:first) + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + firm = Firm.find(:first, :order => "id") summit = firm.clients.find(:all, :conditions => "name = 'Summit'") assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"]) assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }]) end def test_find_first - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client2 = Client.find(2) - assert_equal firm.clients.first, firm.clients.find(:first) - assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'") + assert_equal firm.clients.first, firm.clients.find(:first, :order => "id") + assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id") end def test_find_first_sanitized - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client2 = Client.find(2) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client']) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id") + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id") end def test_find_in_collection @@ -341,7 +344,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.plain_clients.create! end end @@ -731,7 +734,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_destroy_dependent_when_deleted_from_association - firm = Firm.find(:first) + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + firm = Firm.find(:first, :order => "id") assert_equal 2, firm.clients.size client = firm.clients.first @@ -798,7 +802,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_less - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -812,7 +816,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1104,7 +1108,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client = firm.clients_using_primary_key.create!(:name => 'test') assert_equal firm.name, client.firm_name end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index b1060d01af..9da7fc2639 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -14,7 +14,9 @@ require 'models/citation' class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books + fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, + # Reload edges table from fixtures as otherwise repeated test was failing + :edges def test_has_many assert authors(:david).categories.include?(categories(:general)) @@ -343,14 +345,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_polymorphic_with_source_type - assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts + # added sort by ID as otherwise Oracle select sometimes returned rows in different order + assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id) end def test_eager_has_many_polymorphic_with_source_type tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) desired = posts(:welcome, :thinking) assert_no_queries do - assert_equal desired, tag_with_include.tagged_posts + # added sort by ID as otherwise test using JRuby was failing as array elements were in different order + assert_equal desired.sort_by(&:id), tag_with_include.tagged_posts.sort_by(&:id) end assert_equal 5, tag_with_include.taggings.length end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 22168468a6..1c05e523e0 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -73,12 +73,16 @@ class Firm < Company has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true - has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account" + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account", :order => "id" has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete end class DependentFirm < Company - has_one :account, :foreign_key => "firm_id", :dependent => :nullify + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id" has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify end -- cgit v1.2.3 From 71c32d3cacb7b0c0f0828caa5555f279777364fa Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:03:34 +0200 Subject: 1=2 is invalid expression in Oracle SELECT --- activerecord/test/cases/attribute_methods_test.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ab8768ea3e..055590da0a 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -75,13 +75,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_false topic = Topic.create(:title => 'Budget') - topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + # Oracle does not support boolean expressions in SELECT + if current_adapter?(:OracleAdapter) + topic = Topic.find(:first, :select => "topics.*, 0 as is_test") + else + topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + end assert !topic.is_test? end def test_typecast_attribute_from_select_to_true topic = Topic.create(:title => 'Budget') - topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + # Oracle does not support boolean expressions in SELECT + if current_adapter?(:OracleAdapter) + topic = Topic.find(:first, :select => "topics.*, 1 as is_test") + else + topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + end assert topic.is_test? end -- cgit v1.2.3 From d40e3ea936fe37f0dba696c611d49c700ffa3542 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:05:41 +0200 Subject: Oracle saves empty string as NULL --- .../test/cases/autosave_association_test.rb | 46 +++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index ddca5e962d..271086af8e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -154,7 +154,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_save_fails_for_invalid_belongs_to - assert log = AuditLog.create(:developer_id => 0, :message => "") + # Oracle saves empty string as NULL therefore :message changed to one space + assert log = AuditLog.create(:developer_id => 0, :message => " ") log.developer = Developer.new assert !log.developer.valid? @@ -164,7 +165,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_save_succeeds_for_invalid_belongs_to_with_validate_false - assert log = AuditLog.create(:developer_id => 0, :message=> "") + # Oracle saves empty string as NULL therefore :message changed to one space + assert log = AuditLog.create(:developer_id => 0, :message=> " ") log.unvalidated_developer = Developer.new assert !log.unvalidated_developer.valid? @@ -666,7 +668,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.save(false) - assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] + else + assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + end end def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth @@ -678,7 +685,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.save(false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] - assert_equal ['', '', '', ''], values + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil, nil, nil], values + else + assert_equal ['', '', '', ''], values + end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @@ -756,7 +768,12 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @ship.pirate.catchphrase = '' @ship.name = '' @ship.save(false) - assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] + else + assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @@ -837,11 +854,20 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).each { |child| child.name = '' } assert @pirate.save(false) - assert_equal ['', '', ''], [ - @pirate.reload.catchphrase, - @pirate.send(@association_name).first.name, - @pirate.send(@association_name).last.name - ] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil, nil], [ + @pirate.reload.catchphrase, + @pirate.send(@association_name).first.name, + @pirate.send(@association_name).last.name + ] + else + assert_equal ['', '', ''], [ + @pirate.reload.catchphrase, + @pirate.send(@association_name).first.name, + @pirate.send(@association_name).last.name + ] + end end def test_should_validation_the_associated_models_on_create -- cgit v1.2.3 From a12358b3a5e69f41079595d5c92677b66ae6e642 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:07:23 +0200 Subject: Oracle adapter returns numeric (not string) value after SUM --- activerecord/test/cases/calculations_test.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 75f52dfa4a..24bc4f71ce 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -298,7 +298,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_expression - assert_equal '636', Account.sum("2 * credit_limit") + # Oracle adapter returns floating point value 636.0 after SUM + if current_adapter?(:OracleAdapter) + assert_equal 636, Account.sum("2 * credit_limit") + else + assert_equal '636', Account.sum("2 * credit_limit") + end end def test_count_with_from_option -- cgit v1.2.3 From c3e1ef0b4051c515fa2e3f49ea47309c79a89aee Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:08:37 +0200 Subject: Oracle needs sequence value for primary key in INSERT statement --- activerecord/test/cases/database_statements_test.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb index 6274d5250f..c689e97d83 100644 --- a/activerecord/test/cases/database_statements_test.rb +++ b/activerecord/test/cases/database_statements_test.rb @@ -6,7 +6,14 @@ class DatabaseStatementsTest < ActiveRecord::TestCase end def test_insert_should_return_the_inserted_id - id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if current_adapter?(:OracleAdapter) + sequence_name = "accounts_seq" + id_value = @connection.next_sequence_value(sequence_name) + id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name) + else + id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + end assert_not_nil id end end -- cgit v1.2.3 From 42fd2a3b162e8f93e9c39b07253561bde3c00e35 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:14:42 +0200 Subject: added :order to find :all as otherwise Oracle tests were failing --- activerecord/test/cases/inheritance_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 167d3abad9..5cd11e9799 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -137,7 +137,8 @@ class InheritanceTest < ActiveRecord::TestCase def test_update_all_within_inheritance Client.update_all "name = 'I am a client'" assert_equal "I am a client", Client.find(:all).first.name - assert_equal "37signals", Firm.find(:all).first.name + # Order by added as otherwise Oracle tests were failing because of different order of results + assert_equal "37signals", Firm.find(:all, :order => "id").first.name end def test_alt_update_all_within_inheritance -- cgit v1.2.3 From 3a1cbc5c3b3bcb2de4be6e4469bb87b99759dc59 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:15:59 +0200 Subject: Oracle adapter returns Time value for DATE columns --- activerecord/test/cases/invalid_date_test.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index e2bb17c37f..99af7d2986 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -11,13 +11,23 @@ class InvalidDateTest < Test::Unit::TestCase valid_dates.each do |date_src| topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) - assert_equal(topic.last_read, Date.new(*date_src)) + # Oracle DATE columns are datetime columns and Oracle adapter returns Time value + if current_adapter?(:OracleAdapter) + assert_equal(topic.last_read.to_date, Date.new(*date_src)) + else + assert_equal(topic.last_read, Date.new(*date_src)) + end end invalid_dates.each do |date_src| assert_nothing_raised do topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s}) - assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + # Oracle DATE columns are datetime columns and Oracle adapter returns Time value + if current_adapter?(:OracleAdapter) + assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + else + assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + end end end end -- cgit v1.2.3 From ee654e54c40ef41cf8bfa4c25324faeb2bf59de0 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:17:05 +0200 Subject: Oracle generates different ORDER BY fragment --- activerecord/test/cases/method_scoping_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index d8246f49b8..35f7bc5443 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -379,7 +379,8 @@ class NestedScopingTest < ActiveRecord::TestCase poor_jamis = developers(:poor_jamis) Developer.with_scope(:find => { :conditions => "salary < 100000" }) do Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do - assert_sql /ORDER BY id asc / do + # Oracle adapter does not generated space after asc therefore trailing space removed from regex + assert_sql /ORDER BY id asc/ do assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) end end -- cgit v1.2.3 From 5d0dece6a69c31437b29396a3d4d04f092a9fc1f Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:18:21 +0200 Subject: Oracle adapter gets Time or DateTime value already with timezone --- activerecord/test/cases/migration_test.rb | 63 ++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 215b5a427a..03788bf69e 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -446,18 +446,22 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal Date, bob.favorite_day.class end - # Test DateTime column and defaults, including timezone. - # FIXME: moment of truth may be Time on 64-bit platforms. - if bob.moment_of_truth.is_a?(DateTime) - - with_env_tz 'US/Eastern' do - assert_equal DateTime.local_offset, bob.moment_of_truth.offset - assert_not_equal 0, bob.moment_of_truth.offset - assert_not_equal "Z", bob.moment_of_truth.zone - # US/Eastern is -5 hours from GMT - assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM - assert_equal DateTime::ITALY, bob.moment_of_truth.start + # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column + # therefore no timezone change is done afterwards when default timezone is changed + unless current_adapter?(:OracleAdapter) + # Test DateTime column and defaults, including timezone. + # FIXME: moment of truth may be Time on 64-bit platforms. + if bob.moment_of_truth.is_a?(DateTime) + + with_env_tz 'US/Eastern' do + assert_equal DateTime.local_offset, bob.moment_of_truth.offset + assert_not_equal 0, bob.moment_of_truth.offset + assert_not_equal "Z", bob.moment_of_truth.zone + # US/Eastern is -5 hours from GMT + assert_equal Rational(-5, 24), bob.moment_of_truth.offset + assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM + assert_equal DateTime::ITALY, bob.moment_of_truth.start + end end end @@ -571,7 +575,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Base.connection.create_table(:hats) do |table| table.column :hat_name, :string, :default => nil end - exception = if current_adapter?(:PostgreSQLAdapter) + exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) ActiveRecord::StatementInvalid else ActiveRecord::ActiveRecordError @@ -625,7 +629,13 @@ if ActiveRecord::Base.connection.supports_migrations? table.column :hat_size, :integer table.column :hat_style, :string, :limit => 100 end - ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true + # Oracle index names should be 30 or less characters + if current_adapter?(:OracleAdapter) + ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true, + :name => 'index_hats_on_hat_style_size' + else + ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true + end assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } ensure @@ -783,7 +793,12 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 } - assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" } + else + assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + end ensure Person.connection.drop_table :testings rescue nil end @@ -799,7 +814,12 @@ if ActiveRecord::Base.connection.supports_migrations? person_klass.reset_column_information assert_equal 99, person_klass.columns_hash["wealth"].default assert_equal false, person_klass.columns_hash["wealth"].null - assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} + else + assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + end # change column default to see that column doesn't lose its not null definition person_klass.connection.change_column_default "testings", "wealth", 100 @@ -1054,7 +1074,12 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_migrator_db_has_no_schema_migrations_table - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") + # Oracle adapter raises error if semicolon is present as last character + if current_adapter?(:OracleAdapter) + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") + else + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") + end assert_nothing_raised do ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) end @@ -1412,6 +1437,8 @@ if ActiveRecord::Base.connection.supports_migrations? def string_column if current_adapter?(:PostgreSQLAdapter) "character varying(255)" + elsif current_adapter?(:OracleAdapter) + 'VARCHAR2(255)' else 'varchar(255)' end @@ -1420,6 +1447,8 @@ if ActiveRecord::Base.connection.supports_migrations? def integer_column if current_adapter?(:MysqlAdapter) 'int(11)' + elsif current_adapter?(:OracleAdapter) + 'NUMBER(38)' else 'integer' end -- cgit v1.2.3 From 04e6bc1134c7165d455106767c5caabb5993e52b Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:19:27 +0200 Subject: Oracle adapter recourns count() as numeric (not string) --- activerecord/test/cases/query_cache_test.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index f90a66d1dc..2af6a56b6a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -50,7 +50,12 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_does_not_wrap_string_results_in_arrays Task.cache do - assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + # Oracle adapter returns count() as Fixnum or Float + if current_adapter?(:OracleAdapter) + assert Task.connection.select_value("SELECT count(*) AS count_all FROM tasks").is_a?(Numeric) + else + assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + end end end end -- cgit v1.2.3 From 01a4e07c3625fc2985ca7f2cce40c9d34b1728f8 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:20:42 +0200 Subject: Oracle can store integers with any :limit --- activerecord/test/cases/schema_dumper_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9612b0beb6..bf9446a474 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -114,6 +114,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_6.*:limit => 6}, output assert_match %r{c_int_7.*:limit => 7}, output assert_match %r{c_int_8.*:limit => 8}, output + elsif current_adapter?(:OracleAdapter) + assert_match %r{c_int_5.*:limit => 5}, output + assert_match %r{c_int_6.*:limit => 6}, output + assert_match %r{c_int_7.*:limit => 7}, output + assert_match %r{c_int_8.*:limit => 8}, output else assert_match %r{c_int_5.*:limit => 8}, output assert_match %r{c_int_6.*:limit => 8}, output -- cgit v1.2.3 From 090ec47bec21261ea867fbca686bba132f01834d Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:25:58 +0200 Subject: added Subject#after_initialize to be the same as Topic#after_initialize --- activerecord/test/models/subject.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index 3502943f3a..1b9d8107f8 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -1,4 +1,12 @@ -# used for OracleSynonymTest, see test/synonym_test_oci.rb +# used for OracleSynonymTest, see test/synonym_test_oracle.rb # class Subject < ActiveRecord::Base + protected + # added initialization of author_email_address in the same way as in Topic class + # as otherwise synonym test was failing + def after_initialize + if self.new_record? + self.author_email_address = 'test@test.com' + end + end end -- cgit v1.2.3 From 8f34c966141bbbd0bd8da83e8ce7d8fa322bcc91 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:27:48 +0200 Subject: support for assert_queries when using Oracle adapter initialize $KCODE to UTF8 when JRuby is used --- .../test/connections/native_oracle/connection.rb | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 1be4e23b84..01d86aba93 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,8 +1,8 @@ # gem "rsim-activerecord-oracle_enhanced-adapter" -# gem "activerecord-oracle_enhanced-adapter" +gem "activerecord-oracle_enhanced-adapter", ">=1.2.0" # uses local copy of oracle_enhanced adapter -$:.unshift("../../oracle-enhanced/lib") -require 'active_record/connection_adapters/oracle_enhanced_adapter' +# $:.unshift("../../oracle-enhanced/lib") +# require 'active_record/connection_adapters/oracle_enhanced_adapter' print "Using Oracle\n" require_dependency 'models/course' @@ -13,7 +13,7 @@ require 'logger' ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB'] || 'XE' +db = ENV['ARUNIT_DB_NAME'] = 'XE' ActiveRecord::Base.configurations = { 'arunit' => { @@ -36,3 +36,19 @@ ActiveRecord::Base.configurations = { ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' + +# for assert_queries test helper +ActiveRecord::Base.connection.class.class_eval do + IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im] + + def select_with_query_record(sql, name = nil, return_column_names = false) + $queries_executed ||= [] + $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r } + select_without_query_record(sql, name, return_column_names) + end + + alias_method_chain :select, :query_record +end + +# For JRuby Set default $KCODE to UTF8 +$KCODE = "UTF8" if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' -- cgit v1.2.3 From 8afab34a7699abe5c4eed552815276df01510370 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 12 Apr 2009 20:11:40 +0300 Subject: always sort lists by id before comparison to avoid errors because of different sorting of same results (on Oracle database) --- activerecord/test/cases/named_scope_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index f4fdc9a39d..2a729f0678 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -154,7 +154,8 @@ class NamedScopeTest < ActiveRecord::TestCase assert !authors(:david).posts.ranked_by_comments.limit(5).empty? assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) - assert_equal authors(:david).posts.ranked_by_comments.limit(5), authors(:david).posts.top(5) + # Oracle sometimes sorts differently if WHERE condition is changed + assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) assert_equal Post.ranked_by_comments.limit(5), Post.top(5) end -- cgit v1.2.3 From 94e761551b884604b01b43de3da2c873715e9b4b Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sat, 2 May 2009 20:11:19 +0300 Subject: changed default connection to localhost orcl database --- activerecord/test/connections/native_oracle/connection.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 01d86aba93..23e9f8f9c6 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -13,13 +13,13 @@ require 'logger' ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB_NAME'] = 'XE' +db = ENV['ARUNIT_DB_NAME'] = 'orcl' ActiveRecord::Base.configurations = { 'arunit' => { :adapter => 'oracle_enhanced', :database => db, - :host => "arunit", # used just by JRuby to construct JDBC connect string + :host => "localhost", # used just by JRuby to construct JDBC connect string :username => 'arunit', :password => 'arunit', :emulate_oracle_adapter => true @@ -27,7 +27,7 @@ ActiveRecord::Base.configurations = { 'arunit2' => { :adapter => 'oracle_enhanced', :database => db, - :host => "arunit", # used just by JRuby to construct JDBC connect string + :host => "localhost", # used just by JRuby to construct JDBC connect string :username => 'arunit2', :password => 'arunit2', :emulate_oracle_adapter => true -- cgit v1.2.3 From 9b2309c4a8a8c8ad46658c170ccfdb828b30c443 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Fri, 5 Jun 2009 18:43:11 +0300 Subject: fix schema_dumper_test for Oracle as it supports precision up to 38 --- activerecord/test/cases/schema_dumper_test.rb | 7 ++++++- activerecord/test/schema/schema.rb | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index bf9446a474..4f8e20b3ba 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -198,6 +198,11 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_keeps_large_precision_integer_columns_as_decimal output = standard_dump - assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output + # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers + if current_adapter?(:OracleAdapter) + assert_match %r{t.integer\s+"atoms_in_universe",\s+:precision => 38,\s+:scale => 0}, output + else + assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output + end end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 5752d6fa40..5f60d5e137 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -285,7 +285,12 @@ ActiveRecord::Schema.define do t.decimal :my_house_population, :precision => 2, :scale => 0 t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78 t.float :temperature - t.decimal :atoms_in_universe, :precision => 55, :scale => 0 + # Oracle supports precision up to 38 + if current_adapter?(:OracleAdapter) + t.decimal :atoms_in_universe, :precision => 38, :scale => 0 + else + t.decimal :atoms_in_universe, :precision => 55, :scale => 0 + end end create_table :orders, :force => true do |t| -- cgit v1.2.3 From 44a7ef85eba5491aaef5a890ef2b44b111888d35 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Fri, 5 Jun 2009 18:45:09 +0300 Subject: modifications to Oracle connection.rb setup --- .../test/connections/native_oracle/connection.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 23e9f8f9c6..c8183dc0fb 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,8 +1,13 @@ # gem "rsim-activerecord-oracle_enhanced-adapter" -gem "activerecord-oracle_enhanced-adapter", ">=1.2.0" +# gem "activerecord-oracle_enhanced-adapter", ">=1.2.1" # uses local copy of oracle_enhanced adapter -# $:.unshift("../../oracle-enhanced/lib") -# require 'active_record/connection_adapters/oracle_enhanced_adapter' +$:.unshift("../../oracle-enhanced/lib") +require 'active_record/connection_adapters/oracle_enhanced_adapter' +# gem "activerecord-jdbc-adapter" +# require 'active_record/connection_adapters/jdbc_adapter' + +# otherwise failed with silence_warnings method missing exception +require 'active_support/core_ext/kernel/reporting' print "Using Oracle\n" require_dependency 'models/course' @@ -20,6 +25,9 @@ ActiveRecord::Base.configurations = { :adapter => 'oracle_enhanced', :database => db, :host => "localhost", # used just by JRuby to construct JDBC connect string + # :adapter => "jdbc", + # :driver => "oracle.jdbc.driver.OracleDriver", + # :url => "jdbc:oracle:thin:@localhost:1521:#{db}", :username => 'arunit', :password => 'arunit', :emulate_oracle_adapter => true @@ -28,6 +36,9 @@ ActiveRecord::Base.configurations = { :adapter => 'oracle_enhanced', :database => db, :host => "localhost", # used just by JRuby to construct JDBC connect string + # :adapter => "jdbc", + # :driver => "oracle.jdbc.driver.OracleDriver", + # :url => "jdbc:oracle:thin:@localhost:1521:#{db}", :username => 'arunit2', :password => 'arunit2', :emulate_oracle_adapter => true @@ -37,6 +48,9 @@ ActiveRecord::Base.configurations = { ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' +# ActiveRecord::Base.connection.execute %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} +# ActiveRecord::Base.connection.execute %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil + # for assert_queries test helper ActiveRecord::Base.connection.class.class_eval do IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im] -- cgit v1.2.3 From 53be10c5e6939c9093c4ae863fd380a6dbf1e50e Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Thu, 23 Jul 2009 20:02:04 +0300 Subject: fixed test_foreign_key_violations_are_translated_to_specific_exception to work with Oracle --- activerecord/test/cases/adapter_test.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 65c5fc2fe9..88136597e3 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -132,7 +132,13 @@ class AdapterTest < ActiveRecord::TestCase def test_foreign_key_violations_are_translated_to_specific_exception unless @connection.adapter_name == 'SQLite' assert_raises(ActiveRecord::InvalidForeignKey) do - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end end end end -- cgit v1.2.3 From 5f0c425e8d106df4cdf844ac4859fc373f9c43e1 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 3 Aug 2009 14:13:03 +0300 Subject: Some databases (e.g. Oracle) does not allow "AS" between table name and table alias name, for others it is optional --- activerecord/test/cases/finder_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 893fc34c36..7f3be1fa67 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1027,7 +1027,7 @@ class FinderTest < ActiveRecord::TestCase def test_joins_dont_clobber_id first = Firm.find( :first, - :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id', + :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id', :conditions => 'companies.id = 1' ) assert_equal 1, first.id -- cgit v1.2.3 From c44f7e39f46058842845f8c95c3e49f7c59c3aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 5 Aug 2009 22:07:46 +0200 Subject: Updated vendored thor to 0.11.5 --- railties/lib/generators.rb | 2 +- railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc | 75 --- railties/lib/vendor/thor-0.11.3/LICENSE | 20 - railties/lib/vendor/thor-0.11.3/README.markdown | 247 ---------- railties/lib/vendor/thor-0.11.3/bin/rake2thor | 87 ---- railties/lib/vendor/thor-0.11.3/bin/thor | 7 - railties/lib/vendor/thor-0.11.3/lib/thor.rb | 240 ---------- .../lib/vendor/thor-0.11.3/lib/thor/actions.rb | 270 ----------- .../thor-0.11.3/lib/thor/actions/create_file.rb | 102 ---- .../thor-0.11.3/lib/thor/actions/directory.rb | 87 ---- .../lib/thor/actions/empty_directory.rb | 133 ------ .../lib/thor/actions/file_manipulation.rb | 195 -------- .../lib/thor/actions/inject_into_file.rb | 78 ---- railties/lib/vendor/thor-0.11.3/lib/thor/base.rb | 516 --------------------- .../thor/core_ext/hash_with_indifferent_access.rb | 75 --- .../thor-0.11.3/lib/thor/core_ext/ordered_hash.rb | 102 ---- railties/lib/vendor/thor-0.11.3/lib/thor/error.rb | 27 -- railties/lib/vendor/thor-0.11.3/lib/thor/group.rb | 263 ----------- .../lib/vendor/thor-0.11.3/lib/thor/invocation.rb | 172 ------- railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb | 4 - .../vendor/thor-0.11.3/lib/thor/parser/argument.rb | 67 --- .../thor-0.11.3/lib/thor/parser/arguments.rb | 145 ------ .../vendor/thor-0.11.3/lib/thor/parser/option.rb | 132 ------ .../vendor/thor-0.11.3/lib/thor/parser/options.rb | 142 ------ railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb | 291 ------------ railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb | 72 --- .../lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb | 216 --------- .../lib/vendor/thor-0.11.3/lib/thor/shell/color.rb | 106 ----- railties/lib/vendor/thor-0.11.3/lib/thor/task.rb | 108 ----- railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb | 4 - .../vendor/thor-0.11.3/lib/thor/tasks/install.rb | 35 -- .../vendor/thor-0.11.3/lib/thor/tasks/package.rb | 31 -- .../lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb | 70 --- railties/lib/vendor/thor-0.11.3/lib/thor/util.rb | 229 --------- railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc | 77 +++ railties/lib/vendor/thor-0.11.5/LICENSE | 20 + railties/lib/vendor/thor-0.11.5/README.rdoc | 234 ++++++++++ railties/lib/vendor/thor-0.11.5/bin/rake2thor | 87 ++++ railties/lib/vendor/thor-0.11.5/bin/thor | 7 + railties/lib/vendor/thor-0.11.5/lib/thor.rb | 243 ++++++++++ .../lib/vendor/thor-0.11.5/lib/thor/actions.rb | 272 +++++++++++ .../thor-0.11.5/lib/thor/actions/create_file.rb | 102 ++++ .../thor-0.11.5/lib/thor/actions/directory.rb | 87 ++++ .../lib/thor/actions/empty_directory.rb | 133 ++++++ .../lib/thor/actions/file_manipulation.rb | 195 ++++++++ .../lib/thor/actions/inject_into_file.rb | 78 ++++ railties/lib/vendor/thor-0.11.5/lib/thor/base.rb | 510 ++++++++++++++++++++ .../thor/core_ext/hash_with_indifferent_access.rb | 75 +++ .../thor-0.11.5/lib/thor/core_ext/ordered_hash.rb | 100 ++++ railties/lib/vendor/thor-0.11.5/lib/thor/error.rb | 27 ++ railties/lib/vendor/thor-0.11.5/lib/thor/group.rb | 263 +++++++++++ .../lib/vendor/thor-0.11.5/lib/thor/invocation.rb | 178 +++++++ railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb | 4 + .../vendor/thor-0.11.5/lib/thor/parser/argument.rb | 67 +++ .../thor-0.11.5/lib/thor/parser/arguments.rb | 145 ++++++ .../vendor/thor-0.11.5/lib/thor/parser/option.rb | 132 ++++++ .../vendor/thor-0.11.5/lib/thor/parser/options.rb | 142 ++++++ .../lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb | 67 +++ railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb | 295 ++++++++++++ railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb | 72 +++ .../lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb | 220 +++++++++ .../lib/vendor/thor-0.11.5/lib/thor/shell/color.rb | 108 +++++ railties/lib/vendor/thor-0.11.5/lib/thor/task.rb | 116 +++++ railties/lib/vendor/thor-0.11.5/lib/thor/util.rb | 250 ++++++++++ 64 files changed, 4307 insertions(+), 4349 deletions(-) delete mode 100644 railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc delete mode 100644 railties/lib/vendor/thor-0.11.3/LICENSE delete mode 100644 railties/lib/vendor/thor-0.11.3/README.markdown delete mode 100755 railties/lib/vendor/thor-0.11.3/bin/rake2thor delete mode 100755 railties/lib/vendor/thor-0.11.3/bin/thor delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/base.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/error.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/group.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/shell/color.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/task.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb delete mode 100644 railties/lib/vendor/thor-0.11.3/lib/thor/util.rb create mode 100644 railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc create mode 100644 railties/lib/vendor/thor-0.11.5/LICENSE create mode 100644 railties/lib/vendor/thor-0.11.5/README.rdoc create mode 100755 railties/lib/vendor/thor-0.11.5/bin/rake2thor create mode 100755 railties/lib/vendor/thor-0.11.5/bin/thor create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/base.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/error.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/group.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/task.rb create mode 100644 railties/lib/vendor/thor-0.11.5/lib/thor/util.rb diff --git a/railties/lib/generators.rb b/railties/lib/generators.rb index 64ec808ee4..ef837e1488 100644 --- a/railties/lib/generators.rb +++ b/railties/lib/generators.rb @@ -11,7 +11,7 @@ end $:.unshift(File.dirname(__FILE__)) -require 'vendor/thor-0.11.3/lib/thor' +require 'vendor/thor-0.11.5/lib/thor' require 'generators/base' require 'generators/named_base' diff --git a/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc b/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc deleted file mode 100644 index 544dde8c02..0000000000 --- a/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc +++ /dev/null @@ -1,75 +0,0 @@ -== TODO - -* Improve spec coverage for Thor::Runner -* Improve help output to list shorthand switches, too - -== Current - -* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore - since it wrong behavior to the invocation system. - -* thor help now show information about any class/task. All those calls are - possible: - - thor help describe - thor help describe:amazing - - Or even with default namespaces: - - thor help :spec - -* Thor::Runner now invokes the default task if none is supplied: - - thor describe # invokes the default task, usually help - -* Thor::Runner now works with mappings: - - thor describe -h - -* Added some documentation and code refactoring. - -== 0.9.8, released 2008-10-20 - -* Fixed some tiny issues that were introduced lately. - -== 0.9.7, released 2008-10-13 - -* Setting global method options on the initialize method works as expected: - All other tasks will accept these global options in addition to their own. -* Added 'group' notion to Thor task sets (class Thor); by default all tasks - are in the 'standard' group. Running 'thor -T' will only show the standard - tasks - adding --all will show all tasks. You can also filter on a specific - group using the --group option: thor -T --group advanced - -== 0.9.6, released 2008-09-13 - -* Generic improvements - -== 0.9.5, released 2008-08-27 - -* Improve Windows compatibility -* Update (incorrect) README and task.thor sample file -* Options hash is now frozen (once returned) -* Allow magic predicates on options object. For instance: `options.force?` -* Add support for :numeric type -* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f]) -* Allow specifying optional args with default values: method_options(:user => "mislav") -* Don't write options for nil or false values. This allows, for example, turning color off when running specs. -* Exit with the status of the spec command to help CI stuff out some. - -== 0.9.4, released 2008-08-13 - -* Try to add Windows compatibility. -* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore -* Allow options at the beginning of the argument list as well as the end. -* Make options available with symbol keys in addition to string keys. -* Allow true to be passed to Thor#method_options to denote a boolean option. -* If loading a thor file fails, don't give up, just print a warning and keep going. -* Make sure that we re-raise errors if they happened further down the pipe than we care about. -* Only delete the old file on updating when the installation of the new one is a success -* Make it Ruby 1.8.5 compatible. -* Don't raise an error if a boolean switch is defined multiple times. -* Thor::Options now doesn't parse through things that look like options but aren't. -* Add URI detection to install task, and make sure we don't append ".thor" to URIs -* Add rake2thor to the gem binfiles. -* Make sure local Thorfiles override system-wide ones. diff --git a/railties/lib/vendor/thor-0.11.3/LICENSE b/railties/lib/vendor/thor-0.11.3/LICENSE deleted file mode 100644 index 98722da459..0000000000 --- a/railties/lib/vendor/thor-0.11.3/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008 Yehuda Katz - -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. \ No newline at end of file diff --git a/railties/lib/vendor/thor-0.11.3/README.markdown b/railties/lib/vendor/thor-0.11.3/README.markdown deleted file mode 100644 index a1d7259775..0000000000 --- a/railties/lib/vendor/thor-0.11.3/README.markdown +++ /dev/null @@ -1,247 +0,0 @@ -thor -==== - -Map options to a class. Simply create a class with the appropriate annotations -and have options automatically map to functions and parameters. - -Example: - - class App < Thor # [1] - map "-L" => :list # [2] - - desc "install APP_NAME", "install one of the available apps" # [3] - method_options :force => :boolean, :alias => :string # [4] - def install(name) - user_alias = options[:alias] - if options.force? - # do something - end - # other code - end - - desc "list [SEARCH]", "list all of the available apps, limited by SEARCH" - def list(search="") - # list everything - end - end - -Thor automatically maps commands as such: - - thor app:install myname --force - -That gets converted to: - - App.new.install("myname") - # with {'force' => true} as options hash - -1. Inherit from Thor to turn a class into an option mapper -2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list -3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description -4. Provide any additional options that will be available the instance method options. - -Types for `method_options` --------------------------- - -
-
:boolean
-
is parsed as --option or --option=true
-
:string
-
is parsed as --option=VALUE
-
:numeric
-
is parsed as --option=N
-
:array
-
is parsed as --option=one two three
-
:hash
-
is parsed as --option=key:value key:value key:value
-
- -Besides, method_option allows a default value to be given, examples: - - method_options :force => false - #=> Creates a boolean option with default value false - - method_options :alias => "bar" - #=> Creates a string option with default value "bar" - - method_options :threshold => 3.0 - #=> Creates a numeric option with default value 3.0 - -You can also supply :option => :required to mark an option as required. The -type is assumed to be string. If you want a required hash with default values -as option, you can use `method_option` which uses a more declarative style: - - method_option :attributes, :type => :hash, :default => {}, :required => true - -All arguments can be set to nil (except required arguments), by suppling a no or -skip variant. For example: - - thor app name --no-attributes - -In previous versions, aliases for options were created automatically, but now -they should be explicit. You can supply aliases in both short and declarative -styles: - - method_options %w( force -f ) => :boolean - -Or: - - method_option :force, :type => :boolean, :aliases => "-f" - -You can supply as many aliases as you want. - -NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead. - -Namespaces ----------- - -By default, your Thor tasks are invoked using Ruby namespace. In the example -above, tasks are invoked as: - - thor app:install name --force - -However, you could namespace your class as: - - module Sinatra - class App < Thor - # tasks - end - end - -And then you should invoke your tasks as: - - thor sinatra:app:install name --force - -If desired, you can change the namespace: - - module Sinatra - class App < Thor - namespace :myapp - # tasks - end - end - -And then your tasks hould be invoked as: - - thor myapp:install name --force - -Invocations ------------ - -Thor comes with a invocation-dependency system as well which allows a task to be -invoked only once. For example: - - class Counter < Thor - desc "one", "Prints 1, 2, 3" - def one - puts 1 - invoke :two - invoke :three - end - - desc "two", "Prints 2, 3" - def two - puts 2 - invoke :three - end - - desc "three", "Prints 3" - def three - puts 3 - end - end - -When invoking the task one: - - thor counter:one - -The output is "1 2 3", which means that the three task was invoked only once. -You can even invoke tasks from another class, so be sure to check the -documentation. - -Thor::Group ------------ - -Thor has a special class called Thor::Group. The main difference to Thor class -is that it invokes all tasks at once. The example above could be rewritten in -Thor::Group as this: - - class Counter < Thor::Group - desc "Prints 1, 2, 3" - - def one - puts 1 - end - - def two - puts 2 - end - - def three - puts 3 - end - end - -When invoked: - - thor counter - -It prints "1 2 3" as well. Notice you should described (desc) only the class -and not each task anymore. Thor::Group is a great tool to create generators, -since you can define several steps which are invoked in the order they are -defined (Thor::Group is the tool use in generators in Rails 3.0). - -Besides, Thor::Group can parse arguments and options as Thor tasks: - - class Counter < Thor::Group - # number will be available as attr_accessor - argument :number, :type => :numeric, :desc => "The number to start counting" - desc "Prints the 'number' given upto 'number+2'" - - def one - puts number + 0 - end - - def two - puts number + 1 - end - - def three - puts number + 2 - end - end - -The counter above expects one parameter and has the folling outputs: - - thor counter 5 - # Prints "5 6 7" - - thor counter 11 - # Prints "11 12 13" - -You can also give options to Thor::Group, but instead of using `method_option` and -`method_options`, you should use `class_option` and `class_options`. Both argument -and class_options methods are available to Thor class as well. - -Actions -------- - -Thor comes with several actions which helps with script and generator tasks. You -might be familiar with them since some came from Rails Templates. They are: `say`, -`ask`, `yes?`, `no?`, `add_file`, `remove_file`, `copy_file`, `template`, -`directory`, `inside`, `run`, `inject_into_file` and a couple more. - -To use them, you just need to include Thor::Actions in your Thor classes: - - class App < Thor - include Thor::Actions - # tasks - end - -Some actions like copy file requires that a class method called source_root is -defined in your class. This is the directory where your templates should be -placed. Be sure to check the documentation. - -License -------- - -See MIT LICENSE. diff --git a/railties/lib/vendor/thor-0.11.3/bin/rake2thor b/railties/lib/vendor/thor-0.11.3/bin/rake2thor deleted file mode 100755 index 50c7410d80..0000000000 --- a/railties/lib/vendor/thor-0.11.3/bin/rake2thor +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env ruby - -require 'rubygems' -require 'ruby2ruby' -require 'parse_tree' -if Ruby2Ruby::VERSION >= "1.2.0" - require 'parse_tree_extensions' -end -require 'rake' - -input = ARGV[0] || 'Rakefile' -output = ARGV[1] || 'Thorfile' - -$requires = [] - -module Kernel - def require_with_record(file) - $requires << file if caller[1] =~ /rake2thor:/ - require_without_record file - end - alias_method :require_without_record, :require - alias_method :require, :require_with_record -end - -load input - -@private_methods = [] - -def file_task_name(name) - "compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_') -end - -def method_for_task(task) - file_task = task.is_a?(Rake::FileTask) - comment = task.instance_variable_get('@comment') - prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?)) - actions = task.instance_variable_get('@actions') - name = task.name.gsub(/^([^:]+:)+/, '') - name = file_task_name(name) if file_task - meth = '' - - meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment - meth << "def #{name}\n" - - meth << prereqs.map do |pre| - pre = pre.to_s - pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask) - ' ' + pre - end.join("\n") - - meth << "\n\n" unless prereqs.empty? || actions.empty? - - meth << actions.map do |act| - act = act.to_ruby - unless act.gsub!(/^proc \{ \|(\w+)\|\n/, - " \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n") - act.gsub!(/^proc \{\n/, '') - end - act.gsub(/\n\}$/, '') - end.join("\n") - - meth << "\nend" - - if file_task - @private_methods << meth - return - end - - meth -end - -body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") - -unless @private_methods.empty? - body << "\n\n private\n\n" - body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") -end - -requires = $requires.map { |r| "require #{r.inspect}" }.join("\n") - -File.open(output, 'w') { |f| f.write(<:: name of the defaut task - # - def default_task(meth=nil) - case meth - when :none - @default_task = 'help' - when nil - @default_task ||= from_superclass(:default_task, 'help') - else - @default_task = meth.to_s - end - end - - # Defines the usage and the description of the next task. - # - # ==== Parameters - # usage - # description - # - def desc(usage, description, options={}) - if options[:for] - task = find_and_refresh_task(options[:for]) - task.usage = usage if usage - task.description = description if description - else - @usage, @desc = usage, description - end - end - - # Maps an input to a task. If you define: - # - # map "-T" => "list" - # - # Running: - # - # thor -T - # - # Will invoke the list task. - # - # ==== Parameters - # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task. - # - def map(mappings=nil) - @map ||= from_superclass(:map, {}) - - if mappings - mappings.each do |key, value| - if key.respond_to?(:each) - key.each {|subkey| @map[subkey] = value} - else - @map[key] = value - end - end - end - - @map - end - - # Declares the options for the next task to be declared. - # - # ==== Parameters - # Hash[Symbol => Object]:: The hash key is the name of the option and the value - # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric - # or :required (string). If you give a value, the type of the value is used. - # - def method_options(options=nil) - @method_options ||= {} - build_options(options, @method_options) if options - @method_options - end - - # Adds an option to the set of class options. If :for is given as option, - # it allows you to change the options from a previous defined task. - # - # def previous_task - # # magic - # end - # - # method_options :foo => :bar, :for => :previous_task - # - # def next_task - # # magic - # end - # - # ==== Parameters - # name:: The name of the argument. - # options:: Described below. - # - # ==== Options - # :desc - Description for the argument. - # :required - If the argument is required or not. - # :default - Default value for this argument. It cannot be required and have default values. - # :aliases - Aliases for this option. - # :type - The type of the argument, can be :string, :hash, :array, :numeric, :boolean or :default. - # Default accepts arguments as booleans (--switch) or as strings (--switch=VALUE). - # :group - The group for this options. Use by class options to output options in different levels. - # :banner - String to show on usage notes. - # - def method_option(name, options={}) - scope = if options[:for] - find_and_refresh_task(options[:for]).options - else - method_options - end - - build_option(name, options, scope) - end - - # Parses the task and options from the given args, instantiate the class - # and invoke the task. This method is used when the arguments must be parsed - # from an array. If you are inside Ruby and want to use a Thor class, you - # can simply initialize it: - # - # script = MyScript.new(args, options, config) - # script.invoke(:task, first_arg, second_arg, third_arg) - # - def start(given_args=ARGV, config={}) - super do - meth = normalize_task_name(given_args.shift) - task = all_tasks[meth] - - if task - args, opts = Thor::Options.split(given_args) - config.merge!(:task_options => task.options) - else - args, opts = given_args, {} - end - - task ||= Task.dynamic(meth) - trailing = args[Range.new(arguments.size, -1)] - new(args, opts, config).invoke(task, trailing || []) - end - end - - # Prints help information. If a task name is given, it shows information - # only about the specific task. - # - # ==== Parameters - # meth:: An optional task name to print usage information about. - # - # ==== Options - # namespace:: When true, shows the namespace in the output before the usage. - # skip_inherited:: When true, does not show tasks from superclass. - # - def help(shell, meth=nil, options={}) - meth, options = nil, meth if meth.is_a?(Hash) - - if meth - task = all_tasks[meth] - raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task - - shell.say "Usage:" - shell.say " #{banner(task, options[:namespace])}" - shell.say - class_options_help(shell, "Class") - shell.say task.description - else - list = (options[:short] ? tasks : all_tasks).map do |_, task| - item = [ " " + banner(task, options[:namespace]) ] - item << if task.short_description - "\n # #{task.short_description}\n" - else - "\n" - end - end - - if options[:short] - shell.print_table(list) - else - shell.say "Tasks:" - shell.print_table(list) - class_options_help(shell, "Class") - end - end - end - - protected - - # The banner for this class. You can customize it if you are invoking the - # thor class by another means which is not the Thor::Runner. It receives - # the task that is going to be invoked and if the namespace should be - # displayed. - # - def banner(task, namespace=true) #:nodoc: - task.formatted_usage(self, namespace) - end - - def baseclass #:nodoc: - Thor - end - - def create_task(meth) #:nodoc: - if @usage && @desc - tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options) - @usage, @desc, @method_options = nil - true - elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing - true - else - puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " << - "Call desc if you want this method to be available as task or declare it inside a " << - "no_tasks{} block. Invoked from #{caller[1].inspect}." - false - end - end - - def initialize_added #:nodoc: - class_options.merge!(method_options) - @method_options = nil - end - - # Receives a task name (can be nil), and try to get a map from it. - # If a map can't be found use the sent name or the default task. - # - def normalize_task_name(meth) #:nodoc: - mapping = map[meth.to_s] - meth = mapping || meth || default_task - meth.to_s.gsub('-','_') # treat foo-bar > foo_bar - end - end - - include Thor::Base - - map HELP_MAPPINGS => :help - - desc "help [TASK]", "Describe available tasks or one specific task" - def help(task=nil) - self.class.help(shell, task, :namespace => task && task.include?(?:)) - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb deleted file mode 100644 index b8cfde1940..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb +++ /dev/null @@ -1,270 +0,0 @@ -require 'fileutils' - -Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action| - require action -end - -class Thor - module Actions - attr_accessor :behavior - - # On inclusion, add some options to base. - # - def self.included(base) #:nodoc: - base.extend ClassMethods - return unless base.respond_to?(:class_option) - - base.class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, - :desc => "Run but do not make any changes" - - base.class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, - :desc => "Overwrite files that already exist" - - base.class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, - :desc => "Skip files that already exist" - - base.class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, - :desc => "Supress status output" - end - - module ClassMethods - # Hold source paths for one Thor instance. source_paths_for_search is the - # method responsible to gather source_paths from this current class, - # inherited paths and the source root. - # - def source_paths - @source_paths ||= [] - end - - # Returns the source paths in the following order: - # - # 1) This class source paths - # 2) Source root - # 3) Parents source paths - # - def source_paths_for_search - @source_paths_for_search ||= begin - paths = [] - paths += self.source_paths - paths << self.source_root if self.respond_to?(:source_root) - paths += from_superclass(:source_paths, []) - paths - end - end - end - - # Extends initializer to add more configuration options. - # - # ==== Configuration - # behavior:: The actions default behavior. Can be :invoke or :revoke. - # It also accepts :force, :skip and :pretend to set the behavior - # and the respective option. - # - # destination_root:: The root directory needed for some actions. It's also known - # as destination root. - # - def initialize(args=[], options={}, config={}) - self.behavior = case config[:behavior].to_s - when "force", "skip" - _cleanup_options_and_set(options, config[:behavior]) - :invoke - when "revoke" - :revoke - else - :invoke - end - - super - self.destination_root = config[:destination_root] - end - - # Wraps an action object and call it accordingly to the thor class behavior. - # - def action(instance) - if behavior == :revoke - instance.revoke! - else - instance.invoke! - end - end - - # Returns the root for this thor class (also aliased as destination root). - # - def destination_root - @destination_stack.last - end - - # Sets the root for this thor class. Relatives path are added to the - # directory where the script was invoked and expanded. - # - def destination_root=(root) - @destination_stack ||= [] - @destination_stack[0] = File.expand_path(root || '') - end - - # Returns the given path relative to the absolute root (ie, root where - # the script started). - # - def relative_to_original_destination_root(path, remove_dot=true) - path = path.gsub(@destination_stack[0], '.') - remove_dot ? (path[2..-1] || '') : path - end - - # Receives a file or directory and search for it in the source paths. - # - def find_in_source_paths(file) - relative_root = relative_to_original_destination_root(destination_root, false) - paths = self.class.source_paths_for_search - - paths.each do |source| - source_file = File.expand_path(file, File.join(source, relative_root)) - return source_file if File.exists?(source_file) - end - - if paths.empty? - raise Error, "You don't have any source path defined for class #{self.class.name}. To fix this, " << - "you can define a source_root in your class." - else - raise Error, "Could not find #{file.inspect} in source paths." - end - end - - # Do something in the root or on a provided subfolder. If a relative path - # is given it's referenced from the current root. The full path is yielded - # to the block you provide. The path is set back to the previous path when - # the method exits. - # - # ==== Parameters - # dir:: the directory to move to. - # config:: give :verbose => true to log and use padding. - # - def inside(dir='', config={}, &block) - verbose = config.fetch(:verbose, false) - - say_status :inside, dir, verbose - shell.padding += 1 if verbose - @destination_stack.push File.expand_path(dir, destination_root) - - FileUtils.mkdir_p(destination_root) unless File.exist?(destination_root) - FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } - - @destination_stack.pop - shell.padding -= 1 if verbose - end - - # Goes to the root and execute the given block. - # - def in_root - inside(@destination_stack.first) { yield } - end - - # Loads an external file and execute it in the instance binding. - # - # ==== Parameters - # path:: The path to the file to execute. Can be a web address or - # a relative path from the source root. - # - # ==== Examples - # - # apply "http://gist.github.com/103208" - # - # apply "recipes/jquery.rb" - # - def apply(path, config={}) - verbose = config.fetch(:verbose, true) - path = find_in_source_paths(path) unless path =~ /^http\:\/\// - - say_status :apply, path, verbose - shell.padding += 1 if verbose - instance_eval(open(path).read) - shell.padding -= 1 if verbose - end - - # Executes a command. - # - # ==== Parameters - # command:: the command to be executed. - # config:: give :verbose => false to not log the status. Specify :with - # to append an executable to command executation. - # - # ==== Example - # - # inside('vendor') do - # run('ln -s ~/edge rails') - # end - # - def run(command, config={}) - return unless behavior == :invoke - - destination = relative_to_original_destination_root(destination_root, false) - desc = "#{command} from #{destination.inspect}" - - if config[:with] - desc = "#{File.basename(config[:with].to_s)} #{desc}" - command = "#{config[:with]} #{command}" - end - - say_status :run, desc, config.fetch(:verbose, true) - `#{command}` unless options[:pretend] - end - - # Executes a ruby script (taking into account WIN32 platform quirks). - # - # ==== Parameters - # command:: the command to be executed. - # config:: give :verbose => false to not log the status. - # - def run_ruby_script(command, config={}) - return unless behavior == :invoke - run "#{command}", config.merge(:with => Thor::Util.ruby_command) - end - - # Run a thor command. A hash of options can be given and it's converted to - # switches. - # - # ==== Parameters - # task:: the task to be invoked - # args:: arguments to the task - # config:: give :verbose => false to not log the status. Other options - # are given as parameter to Thor. - # - # ==== Examples - # - # thor :install, "http://gist.github.com/103208" - # #=> thor install http://gist.github.com/103208 - # - # thor :list, :all => true, :substring => 'rails' - # #=> thor list --all --substring=rails - # - def thor(task, *args) - config = args.last.is_a?(Hash) ? args.pop : {} - verbose = config.key?(:verbose) ? config.delete(:verbose) : true - - args.unshift task - args.push Thor::Options.to_switches(config) - command = args.join(' ').strip - - run command, :with => :thor, :verbose => verbose - end - - protected - - # Allow current root to be shared between invocations. - # - def _shared_configuration #:nodoc: - super.merge!(:destination_root => self.destination_root) - end - - def _cleanup_options_and_set(options, key) #:nodoc: - case options - when Array - %w(--force -f --skip -s).each { |i| options.delete(i) } - options << "--#{key}" - when Hash - [:force, :skip, "force", "skip"].each { |i| options.delete(i) } - options.merge!(key => true) - end - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb deleted file mode 100644 index 8f6badee27..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'thor/actions/empty_directory' - -class Thor - module Actions - - # Create a new file relative to the destination root with the given data, - # which is the return value of a block or a data string. - # - # ==== Parameters - # destination:: the relative path to the destination root. - # data:: the data to append to the file. - # config:: give :verbose => false to not log the status. - # - # ==== Examples - # - # create_file "lib/fun_party.rb" do - # hostname = ask("What is the virtual hostname I should use?") - # "vhost.name = #{hostname}" - # end - # - # create_file "config/apach.conf", "your apache config" - # - def create_file(destination, data=nil, config={}, &block) - action CreateFile.new(self, destination, block || data.to_s, config) - end - alias :add_file :create_file - - # AddFile is a subset of Template, which instead of rendering a file with - # ERB, it gets the content from the user. - # - class CreateFile < EmptyDirectory #:nodoc: - attr_reader :data - - def initialize(base, destination, data, config={}) - @data = data - super(base, destination, config) - end - - # Checks if the content of the file at the destination is identical to the rendered result. - # - # ==== Returns - # Boolean:: true if it is identical, false otherwise. - # - def identical? - exists? && File.read(destination) == render - end - - # Holds the content to be added to the file. - # - def render - @render ||= if data.is_a?(Proc) - data.call - else - data - end - end - - def invoke! - invoke_with_conflict_check do - FileUtils.mkdir_p(File.dirname(destination)) - File.open(destination, 'w'){ |f| f.write render } - end - end - - protected - - # Now on conflict we check if the file is identical or not. - # - def on_conflict_behavior(&block) - if identical? - say_status :identical, :blue - else - options = base.options.merge(config) - force_or_skip_or_conflict(options[:force], options[:skip], &block) - end - end - - # If force is true, run the action, otherwise check if it's not being - # skipped. If both are false, show the file_collision menu, if the menu - # returns true, force it, otherwise skip. - # - def force_or_skip_or_conflict(force, skip, &block) - if force - say_status :force, :yellow - block.call unless pretend? - elsif skip - say_status :skip, :yellow - else - say_status :conflict, :red - force_or_skip_or_conflict(force_on_collision?, true, &block) - end - end - - # Shows the file collision menu to the user and gets the result. - # - def force_on_collision? - base.shell.file_collision(destination){ render } - end - - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb deleted file mode 100644 index e33639f4e5..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'thor/actions/empty_directory' - -class Thor - module Actions - - # Copies interactively the files from source directory to root directory. - # If any of the files finishes with .tt, it's considered to be a template - # and is placed in the destination without the extension .tt. If any - # empty directory is found, it's copied and all .empty_directory files are - # ignored. Remember that file paths can also be encoded, let's suppose a doc - # directory with the following files: - # - # doc/ - # components/.empty_directory - # README - # rdoc.rb.tt - # %app_name%.rb - # - # When invoked as: - # - # directory "doc" - # - # It will create a doc directory in the destination with the following - # files (assuming that the app_name is "blog"): - # - # doc/ - # components/ - # README - # rdoc.rb - # blog.rb - # - # ==== Parameters - # source:: the relative path to the source root. - # destination:: the relative path to the destination root. - # config:: give :verbose => false to not log the status. - # If :recursive => false, does not look for paths recursively. - # - # ==== Examples - # - # directory "doc" - # directory "doc", "docs", :recursive => false - # - def directory(source, destination=nil, config={}) - action Directory.new(self, source, destination || source, config) - end - - class Directory < EmptyDirectory #:nodoc: - attr_reader :source - - def initialize(base, source, destination=nil, config={}) - @source = File.expand_path(base.find_in_source_paths(source.to_s)) - super(base, destination, { :recursive => true }.merge(config)) - end - - def invoke! - base.empty_directory given_destination, config - execute! - end - - def revoke! - execute! - end - - protected - - def execute! - lookup = config[:recursive] ? File.join(source, '**') : source - lookup = File.join(lookup, '{*,.[a-z]*}') - - Dir[lookup].each do |file_source| - next if File.directory?(file_source) - file_destination = File.join(given_destination, file_source.gsub(source, '.')) - - case file_source - when /\.empty_directory$/ - base.empty_directory(File.dirname(file_destination), config) - when /\.tt$/ - base.template(file_source, file_destination[0..-4], config) - else - base.copy_file(file_source, file_destination, config) - end - end - end - - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb deleted file mode 100644 index 03c1fe4af1..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb +++ /dev/null @@ -1,133 +0,0 @@ -class Thor - module Actions - - # Creates an empty directory. - # - # ==== Parameters - # destination:: the relative path to the destination root. - # config:: give :verbose => false to not log the status. - # - # ==== Examples - # - # empty_directory "doc" - # - def empty_directory(destination, config={}) - action EmptyDirectory.new(self, destination, config) - end - - # Class which holds create directory logic. This is the base class for - # other actions like create_file and directory. - # - # This implementation is based in Templater actions, created by Jonas Nicklas - # and Michael S. Klishin under MIT LICENSE. - # - class EmptyDirectory #:nodoc: - attr_reader :base, :destination, :given_destination, :relative_destination, :config - - # Initializes given the source and destination. - # - # ==== Parameters - # base:: A Thor::Base instance - # source:: Relative path to the source of this file - # destination:: Relative path to the destination of this file - # config:: give :verbose => false to not log the status. - # - def initialize(base, destination, config={}) - @base, @config = base, { :verbose => true }.merge(config) - self.destination = destination - end - - # Checks if the destination file already exists. - # - # ==== Returns - # Boolean:: true if the file exists, false otherwise. - # - def exists? - ::File.exists?(destination) - end - - def invoke! - invoke_with_conflict_check do - ::FileUtils.mkdir_p(destination) - end - end - - def revoke! - say_status :remove, :red - ::FileUtils.rm_rf(destination) if !pretend? && exists? - end - - protected - - # Shortcut for pretend. - # - def pretend? - base.options[:pretend] - end - - # Sets the absolute destination value from a relative destination value. - # It also stores the given and relative destination. Let's suppose our - # script is being executed on "dest", it sets the destination root to - # "dest". The destination, given_destination and relative_destination - # are related in the following way: - # - # inside "bar" do - # empty_directory "baz" - # end - # - # destination #=> dest/bar/baz - # relative_destination #=> bar/baz - # given_destination #=> baz - # - def destination=(destination) - if destination - @given_destination = convert_encoded_instructions(destination.to_s) - @destination = ::File.expand_path(@given_destination, base.destination_root) - @relative_destination = base.relative_to_original_destination_root(@destination) - end - end - - # Filenames in the encoded form are converted. If you have a file: - # - # %class_name%.rb - # - # It gets the class name from the base and replace it: - # - # user.rb - # - def convert_encoded_instructions(filename) - filename.gsub(/%(.*?)%/) do |string| - instruction = $1.strip - base.respond_to?(instruction) ? base.send(instruction) : string - end - end - - # Receives a hash of options and just execute the block if some - # conditions are met. - # - def invoke_with_conflict_check(&block) - if exists? - on_conflict_behavior(&block) - else - say_status :create, :green - block.call unless pretend? - end - - destination - end - - # What to do when the destination file already exists. - # - def on_conflict_behavior(&block) - say_status :exist, :blue - end - - # Shortcut to say_status shell method. - # - def say_status(status, color) - base.shell.say_status status, relative_destination, color if config[:verbose] - end - - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb deleted file mode 100644 index 74c157ba8c..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb +++ /dev/null @@ -1,195 +0,0 @@ -require 'erb' -require 'open-uri' - -class Thor - module Actions - - # Copies the file from the relative source to the relative destination. If - # the destination is not given it's assumed to be equal to the source. - # - # ==== Parameters - # source:: the relative path to the source root. - # destination:: the relative path to the destination root. - # config:: give :verbose => false to not log the status. - # - # ==== Examples - # - # copy_file "README", "doc/README" - # - # copy_file "doc/README" - # - def copy_file(source, destination=nil, config={}) - destination ||= source - source = File.expand_path(find_in_source_paths(source.to_s)) - - create_file destination, nil, config do - File.read(source) - end - end - - # Gets the content at the given address and places it at the given relative - # destination. If a block is given instead of destination, the content of - # the url is yielded and used as location. - # - # ==== Parameters - # source:: the address of the given content. - # destination:: the relative path to the destination root. - # config:: give :verbose => false to not log the status. - # - # ==== Examples - # - # get "http://gist.github.com/103208", "doc/README" - # - # get "http://gist.github.com/103208" do |content| - # content.split("\n").first - # end - # - def get(source, destination=nil, config={}, &block) - source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\// - render = open(source).read - - destination ||= if block_given? - block.arity == 1 ? block.call(render) : block.call - else - File.basename(source) - end - - create_file destination, render, config - end - - # Gets an ERB template at the relative source, executes it and makes a copy - # at the relative destination. If the destination is not given it's assumed - # to be equal to the source removing .tt from the filename. - # - # ==== Parameters - # source:: the relative path to the source root. - # destination:: the relative path to the destination root. - # config:: give :verbose => false to not log the status. - # - # ==== Examples - # - # template "README", "doc/README" - # - # template "doc/README" - # - def template(source, destination=nil, config={}) - destination ||= source - source = File.expand_path(find_in_source_paths(source.to_s)) - context = instance_eval('binding') - - create_file destination, nil, config do - ERB.new(::File.read(source), nil, '-').result(context) - end - end - - # Changes the mode of the given file or directory. - # - # ==== Parameters - # mode:: the file mode - # path:: the name of the file to change mode - # config:: give :verbose => false to not log the status. - # - # ==== Example - # - # chmod "script/*", 0755 - # - def chmod(path, mode, config={}) - return unless behavior == :invoke - path = File.expand_path(path, destination_root) - say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true) - FileUtils.chmod_R(mode, path) unless options[:pretend] - end - - # Prepend text to a file. - # - # ==== Parameters - # path:: path of the file to be changed - # data:: the data to prepend to the file, can be also given as a block. - # config:: give :verbose => false to not log the status. - # - # ==== Example - # - # prepend_file 'config/environments/test.rb', 'config.gem "rspec"' - # - def prepend_file(path, data=nil, config={}, &block) - return unless behavior == :invoke - path = File.expand_path(path, destination_root) - say_status :prepend, relative_to_original_destination_root(path), config.fetch(:verbose, true) - - unless options[:pretend] - content = data || block.call - content << File.read(path) - File.open(path, 'wb') { |file| file.write(content) } - end - end - - # Append text to a file. - # - # ==== Parameters - # path:: path of the file to be changed - # data:: the data to append to the file, can be also given as a block. - # config:: give :verbose => false to not log the status. - # - # ==== Example - # - # append_file 'config/environments/test.rb', 'config.gem "rspec"' - # - def append_file(path, data=nil, config={}, &block) - return unless behavior == :invoke - path = File.expand_path(path, destination_root) - say_status :append, relative_to_original_destination_root(path), config.fetch(:verbose, true) - File.open(path, 'ab') { |file| file.write(data || block.call) } unless options[:pretend] - end - - # Run a regular expression replacement on a file. - # - # ==== Parameters - # path:: path of the file to be changed - # flag:: the regexp or string to be replaced - # replacement:: the replacement, can be also given as a block - # config:: give :verbose => false to not log the status. - # - # ==== Example - # - # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' - # - # gsub_file 'README', /rake/, :green do |match| - # match << " no more. Use thor!" - # end - # - def gsub_file(path, flag, *args, &block) - return unless behavior == :invoke - config = args.last.is_a?(Hash) ? args.pop : {} - - path = File.expand_path(path, destination_root) - say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) - - unless options[:pretend] - content = File.read(path) - content.gsub!(flag, *args, &block) - File.open(path, 'wb') { |file| file.write(content) } - end - end - - # Removes a file at the given location. - # - # ==== Parameters - # path:: path of the file to be changed - # config:: give :verbose => false to not log the status. - # - # ==== Example - # - # remove_file 'README' - # remove_file 'app/controllers/application_controller.rb' - # - def remove_file(path, config={}) - return unless behavior == :invoke - path = File.expand_path(path, destination_root) - - say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) - ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path) - end - alias :remove_dir :remove_file - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb deleted file mode 100644 index 089bd894e4..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'thor/actions/empty_directory' - -class Thor - module Actions - - # Injects the given content into a file. Different from append_file, - # prepend_file and gsub_file, this method is reversible. By this reason, - # the flag can only be strings. gsub_file is your friend if you need to - # deal with more complex cases. - # - # ==== Parameters - # destination:: Relative path to the destination root - # data:: Data to add to the file. Can be given as a block. - # config:: give :verbose => false to not log the status and the flag - # for injection (:after or :before). - # - # ==== Examples - # - # inject_into_file "config/environment.rb", "config.gem thor", :after => "Rails::Initializer.run do |config|\n" - # - # inject_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do - # gems = ask "Which gems would you like to add?" - # gems.split(" ").map{ |gem| " config.gem #{gem}" }.join("\n") - # end - # - def inject_into_file(destination, *args, &block) - if block_given? - data, config = block, args.shift - else - data, config = args.shift, args.shift - end - - log_status = args.empty? || args.pop - action InjectIntoFile.new(self, destination, data, config) - end - - class InjectIntoFile < EmptyDirectory - attr_reader :flag, :replacement - - def initialize(base, destination, data, config) - super(base, destination, { :verbose => true }.merge(config)) - - data = data.call if data.is_a?(Proc) - - @replacement = if @config.key?(:after) - @flag = @config.delete(:after) - @flag + data - else - @flag = @config.delete(:before) - data + @flag - end - end - - def invoke! - say_status :inject, config[:verbose] - replace!(flag, replacement) - end - - def revoke! - say_status :deinject, config[:verbose] - replace!(replacement, flag) - end - - protected - - # Adds the content to the file. - # - def replace!(regexp, string) - unless base.options[:pretend] - content = File.read(destination) - content.gsub!(regexp, string) - File.open(destination, 'wb') { |file| file.write(content) } - end - end - - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb deleted file mode 100644 index 0bdcc1f4d5..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb +++ /dev/null @@ -1,516 +0,0 @@ -require 'thor/core_ext/hash_with_indifferent_access' -require 'thor/core_ext/ordered_hash' -require 'thor/error' -require 'thor/shell' -require 'thor/invocation' -require 'thor/parser' -require 'thor/task' -require 'thor/util' - -class Thor - HELP_MAPPINGS = %w(-h -? --help -D) - THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root - action add_file create_file in_root inside run run_ruby_script) - - module Base - attr_accessor :options - - # It receives arguments in an Array and two hashes, one for options and - # other for configuration. - # - # Notice that it does not check if all required arguments were supplied. - # It should be done by the parser. - # - # ==== Parameters - # args:: An array of objects. The objects are applied to their - # respective accessors declared with argument. - # - # options:: An options hash that will be available as self.options. - # The hash given is converted to a hash with indifferent - # access, magic predicates (options.skip?) and then frozen. - # - # config:: Configuration for this Thor class. - # - def initialize(args=[], options={}, config={}) - Thor::Arguments.parse(self.class.arguments, args).each do |key, value| - send("#{key}=", value) - end - - parse_options = self.class.class_options - - if options.is_a?(Array) - task_options = config.delete(:task_options) # hook for start - parse_options = parse_options.merge(task_options) if task_options - array_options, hash_options = options, {} - else - array_options, hash_options = [], options - end - - options = Thor::Options.parse(parse_options, array_options) - self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).merge!(hash_options) - self.options.freeze - end - - class << self - def included(base) #:nodoc: - base.send :extend, ClassMethods - base.send :include, Invocation - base.send :include, Shell - end - - # Returns the classes that inherits from Thor or Thor::Group. - # - # ==== Returns - # Array[Class] - # - def subclasses - @subclasses ||= [] - end - - # Returns the files where the subclasses are kept. - # - # ==== Returns - # Hash[path => Class] - # - def subclass_files - @subclass_files ||= Hash.new{ |h,k| h[k] = [] } - end - - # Whenever a class inherits from Thor or Thor::Group, we should track the - # class and the file on Thor::Base. This is the method responsable for it. - # Also adds the source root to the source paths if the klass respond to it. - # - def register_klass_file(klass) #:nodoc: - file = caller[1].match(/(.*):\d+/)[1] - Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass) - - file_subclasses = Thor::Base.subclass_files[File.expand_path(file)] - file_subclasses << klass unless file_subclasses.include?(klass) - end - end - - module ClassMethods - # Adds an argument to the class and creates an attr_accessor for it. - # - # Arguments are different from options in several aspects. The first one - # is how they are parsed from the command line, arguments are retrieved - # from position: - # - # thor task NAME - # - # Instead of: - # - # thor task --name=NAME - # - # Besides, arguments are used inside your code as an accessor (self.argument), - # while options are all kept in a hash (self.options). - # - # Finally, arguments cannot have type :default or :boolean but can be - # optional (supplying :optional => :true or :required => false), although - # you cannot have a required argument after a non-required argument. If you - # try it, an error is raised. - # - # ==== Parameters - # name:: The name of the argument. - # options:: Described below. - # - # ==== Options - # :desc - Description for the argument. - # :required - If the argument is required or not. - # :optional - If the argument is optional or not. - # :type - The type of the argument, can be :string, :hash, :array, :numeric. - # :default - Default value for this argument. It cannot be required and have default values. - # :banner - String to show on usage notes. - # - # ==== Errors - # ArgumentError:: Raised if you supply a required argument after a non required one. - # - def argument(name, options={}) - is_thor_reserved_word?(name, :argument) - no_tasks { attr_accessor name } - - required = if options.key?(:optional) - !options[:optional] - elsif options.key?(:required) - options[:required] - else - options[:default].nil? - end - - remove_argument name - - arguments.each do |argument| - next if argument.required? - raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " << - "the non-required argument #{argument.human_name.inspect}." - end if required - - arguments << Thor::Argument.new(name, options[:desc], required, options[:type], - options[:default], options[:banner]) - end - - # Returns this class arguments, looking up in the ancestors chain. - # - # ==== Returns - # Array[Thor::Argument] - # - def arguments - @arguments ||= from_superclass(:arguments, []) - end - - # Adds a bunch of options to the set of class options. - # - # class_options :foo => false, :bar => :required, :baz => :string - # - # If you prefer more detailed declaration, check class_option. - # - # ==== Parameters - # Hash[Symbol => Object] - # - def class_options(options=nil) - @class_options ||= from_superclass(:class_options, {}) - build_options(options, @class_options) if options - @class_options - end - - # Adds an option to the set of class options - # - # ==== Parameters - # name:: The name of the argument. - # options:: Described below. - # - # ==== Options - # :desc - Description for the argument. - # :required - If the argument is required or not. - # :default - Default value for this argument. - # :group - The group for this options. Use by class options to output options in different levels. - # :aliases - Aliases for this option. - # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean. - # :banner - String to show on usage notes. - # - def class_option(name, options={}) - build_option(name, options, class_options) - end - - # Removes a previous defined argument. If :undefine is given, undefine - # accessors as well. - # - # ==== Paremeters - # names:: Arguments to be removed - # - # ==== Examples - # - # remove_argument :foo - # remove_argument :foo, :bar, :baz, :undefine => true - # - def remove_argument(*names) - options = names.last.is_a?(Hash) ? names.pop : {} - - names.each do |name| - arguments.delete_if { |a| a.name == name.to_s } - undef_method name, "#{name}=" if options[:undefine] - end - end - - # Removes a previous defined class option. - # - # ==== Paremeters - # names:: Class options to be removed - # - # ==== Examples - # - # remove_class_option :foo - # remove_class_option :foo, :bar, :baz - # - def remove_class_option(*names) - names.each do |name| - class_options.delete(name) - end - end - - # Defines the group. This is used when thor list is invoked so you can specify - # that only tasks from a pre-defined group will be shown. Defaults to standard. - # - # ==== Parameters - # name - # - def group(name=nil) - case name - when nil - @group ||= from_superclass(:group, 'standard') - else - @group = name.to_s - end - end - - # Returns the tasks for this Thor class. - # - # ==== Returns - # OrderedHash:: An ordered hash with this class tasks. - # - def tasks - @tasks ||= Thor::CoreExt::OrderedHash.new - end - - # Returns the tasks for this Thor class and all subclasses. - # - # ==== Returns - # OrderedHash - # - def all_tasks - @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new) - @all_tasks.merge(tasks) - end - - # Removes a given task from this Thor class. This is usually done if you - # are inheriting from another class and don't want it to be available - # anymore. - # - # By default it only remove the mapping to the task. But you can supply - # :undefine => true to undefine the method from the class as well. - # - # ==== Parameters - # name:: The name of the task to be removed - # options:: You can give :undefine => true if you want tasks the method - # to be undefined from the class as well. - # - def remove_task(*names) - options = names.last.is_a?(Hash) ? names.pop : {} - - names.each do |name| - tasks.delete(name.to_s) - all_tasks.delete(name.to_s) - undef_method name if options[:undefine] - end - end - - # All methods defined inside the given block are not added as tasks. - # - # So you can do: - # - # class MyScript < Thor - # no_tasks do - # def this_is_not_a_task - # end - # end - # end - # - # You can also add the method and remove it from the task list: - # - # class MyScript < Thor - # def this_is_not_a_task - # end - # remove_task :this_is_not_a_task - # end - # - def no_tasks - @no_tasks = true - yield - @no_tasks = false - end - - # Sets the namespace for the Thor or Thor::Group class. By default the - # namespace is retrieved from the class name. If your Thor class is named - # Scripts::MyScript, the help method, for example, will be called as: - # - # thor scripts:my_script -h - # - # If you change the namespace: - # - # namespace :my_scripts - # - # You change how your tasks are invoked: - # - # thor my_scripts -h - # - # Finally, if you change your namespace to default: - # - # namespace :default - # - # Your tasks can be invoked with a shortcut. Instead of: - # - # thor :my_task - # - def namespace(name=nil) - case name - when nil - @namespace ||= Thor::Util.constant_to_namespace(self, false) - else - @namespace = name.to_s - end - end - - # Default way to start generators from the command line. - # - def start(given_args=ARGV, config={}) #:nodoc: - config[:shell] ||= Thor::Base.shell.new - yield - rescue Thor::Error => e - if given_args.include?("--debug") - raise e - else - config[:shell].error e.message - end - end - - protected - - # Prints the class options per group. If an option does not belong to - # any group, it uses the ungrouped name value. This method provide to - # hooks to add extra options, one of them if the third argument called - # extra_group that should be a hash in the format :group => Array[Options]. - # - # The second is by returning a lamda used to print values. The lambda - # requires two options: the group name and the array of options. - # - def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: - groups = {} - - class_options.each do |_, value| - groups[value.group] ||= [] - groups[value.group] << value - end - - printer = proc do |group_name, options| - list = [] - padding = options.collect{ |o| o.aliases.size }.max.to_i * 4 - - options.each do |option| - item = [ option.usage(padding) ] - - item << if option.description - "# #{option.description}" - else - "" - end - - list << item - list << [ "", "# Default: #{option.default}" ] if option.show_default? - end - - unless list.empty? - if group_name - shell.say "#{group_name} options:" - else - shell.say "Options:" - end - - shell.print_table(list, :ident => 2) - shell.say "" - end - end - - # Deal with default group - global_options = groups.delete(nil) || [] - printer.call(ungrouped_name, global_options) if global_options - - # Print all others - groups = extra_group.merge(groups) if extra_group - groups.each(&printer) - printer - end - - # Raises an error if the word given is a Thor reserved word. - # - def is_thor_reserved_word?(word, type) - return false unless THOR_RESERVED_WORDS.include?(word.to_s) - raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}" - end - - # Build an option and adds it to the given scope. - # - # ==== Parameters - # name:: The name of the argument. - # options:: Described in both class_option and method_option. - # - def build_option(name, options, scope) - scope[name] = Thor::Option.new(name, options[:desc], options[:required], - options[:type], options[:default], options[:banner], - options[:group], options[:aliases]) - end - - # Receives a hash of options, parse them and add to the scope. This is a - # fast way to set a bunch of options: - # - # build_options :foo => true, :bar => :required, :baz => :string - # - # ==== Parameters - # Hash[Symbol => Object] - # - def build_options(options, scope) - options.each do |key, value| - scope[key] = Thor::Option.parse(key, value) - end - end - - # Finds a task with the given name. If the task belongs to the current - # class, just return it, otherwise dup it and add the fresh copy to the - # current task hash. - # - def find_and_refresh_task(name) - task = if task = tasks[name.to_s] - task - elsif task = all_tasks[name.to_s] - tasks[name.to_s] = task.clone - else - raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found." - end - end - - # Everytime someone inherits from a Thor class, register the klass - # and file into baseclass. - # - def inherited(klass) - Thor::Base.register_klass_file(klass) - end - - # Fire this callback whenever a method is added. Added methods are - # tracked as tasks if the requirements set by valid_task? are valid. - # - def method_added(meth) - meth = meth.to_s - - if meth == "initialize" - initialize_added - return - end - - # Return if it's not a public instance method - return unless public_instance_methods.include?(meth) || - public_instance_methods.include?(meth.to_sym) - - return if @no_tasks || !create_task(meth) - - is_thor_reserved_word?(meth, :task) - Thor::Base.register_klass_file(self) - end - - # Retrieves a value from superclass. If it reaches the baseclass, - # returns nil. - # - def from_superclass(method, default=nil) - if self == baseclass || !superclass.respond_to?(method, true) - default - else - value = superclass.send(method) - value.dup if value - end - end - - # SIGNATURE: Sets the baseclass. This is where the superclass lookup - # finishes. - def baseclass #:nodoc: - end - - # SIGNATURE: Creates a new task if valid_task? is true. This method is - # called when a new method is added to the class. - def create_task(meth) #:nodoc: - end - - # SIGNATURE: Defines behavior when the initialize method is added to the - # class. - def initialize_added #:nodoc: - end - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb deleted file mode 100644 index 3213961fe4..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb +++ /dev/null @@ -1,75 +0,0 @@ -class Thor - module CoreExt - - # A hash with indifferent access and magic predicates. - # - # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true - # - # hash[:foo] #=> 'bar' - # hash['foo'] #=> 'bar' - # hash.foo? #=> true - # - class HashWithIndifferentAccess < ::Hash - - def initialize(hash={}) - super() - hash.each do |key, value| - self[convert_key(key)] = value - end - end - - def [](key) - super(convert_key(key)) - end - - def []=(key, value) - super(convert_key(key), value) - end - - def delete(key) - super(convert_key(key)) - end - - def values_at(*indices) - indices.collect { |key| self[convert_key(key)] } - end - - def merge(other) - dup.merge!(other) - end - - def merge!(other) - other.each do |key, value| - self[convert_key(key)] = value - end - self - end - - protected - - def convert_key(key) - key.is_a?(Symbol) ? key.to_s : key - end - - # Magic predicates. For instance: - # - # options.force? # => !!options['force'] - # options.shebang # => "/usr/lib/local/ruby" - # options.test_framework?(:rspec) # => options[:test_framework] == :rspec - # - def method_missing(method, *args, &block) - method = method.to_s - if method =~ /^(\w+)\?$/ - if args.empty? - !!self[$1] - else - self[$1] == args.first - end - else - self[method] - end - end - - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb deleted file mode 100644 index 5e4ad5609f..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb +++ /dev/null @@ -1,102 +0,0 @@ -require 'forwardable' - -class Thor #:nodoc: - module CoreExt #:nodoc: - - if RUBY_VERSION >= '1.9' - class OrderedHash < ::Hash - end - else - # This class is based on the Ruby 1.9 ordered hashes. - # - # It keeps the semantics and most of the efficiency of normal hashes - # while also keeping track of the order in which elements were set. - # - class OrderedHash #:nodoc: - include Enumerable - - Node = Struct.new(:key, :value, :next, :prev) - - def initialize - @hash = {} - end - - def [](key) - @hash[key] && @hash[key].value - end - - def []=(key, value) - if node = @hash[key] - node.value = value - else - node = Node.new(key, value) - - if @first.nil? - @first = @last = node - else - node.prev = @last - @last.next = node - @last = node - end - end - - @hash[key] = node - value - end - - def delete(key) - if node = @hash[key] - prev_node = node.prev - next_node = node.next - - next_node.prev = prev_node if next_node - prev_node.next = next_node if prev_node - - @first = next_node if @first == node - @last = prev_node if @last == node - - value = node.value - end - - @hash.delete(key) - value - end - - def keys - self.map { |k, v| k } - end - - def values - self.map { |k, v| v } - end - - def each - return unless @first - yield [@first.key, @first.value] - node = @first - yield [node.key, node.value] while node = node.next - self - end - - def merge(other) - hash = self.class.new - - self.each do |key, value| - hash[key] = value - end - - other.each do |key, value| - hash[key] = value - end - - hash - end - - def empty? - @hash.empty? - end - end - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb deleted file mode 100644 index c846e9ce74..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb +++ /dev/null @@ -1,27 +0,0 @@ -class Thor - # Thor::Error is raised when it's caused by the user invoking the task and - # only errors that inherit from it are rescued. - # - # So, for example, if the developer declares a required argument after an - # option, it should raise an ::ArgumentError and not ::Thor::ArgumentError, - # because it was caused by the developer and not the "final user". - # - class Error < StandardError #:nodoc: - end - - # Raised when a task was not found. - # - class UndefinedTaskError < Error #:nodoc: - end - - # Raised when a task was found, but not invoked properly. - # - class InvocationError < Error #:nodoc: - end - - class RequiredArgumentMissingError < InvocationError #:nodoc: - end - - class MalformattedArgumentError < InvocationError #:nodoc: - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb deleted file mode 100644 index 1be1c35ba5..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb +++ /dev/null @@ -1,263 +0,0 @@ -# Thor has a special class called Thor::Group. The main difference to Thor class -# is that it invokes all tasks at once. It also include some methods that allows -# invocations to be done at the class method, which are not available to Thor -# tasks. -# -class Thor::Group - class << self - # The descrition for this Thor::Group. If none is provided, but a source root - # exists, tries to find the USAGE one folder above it, otherwise searches - # in the superclass. - # - # ==== Parameters - # description:: The description for this Thor::Group. - # - def desc(description=nil) - case description - when nil - @desc ||= from_superclass(:desc, nil) - else - @desc = description - end - end - - # Start works differently in Thor::Group, it simply invokes all tasks - # inside the class. - # - def start(given_args=ARGV, config={}) - super do - if Thor::HELP_MAPPINGS.include?(given_args.first) - help(config[:shell]) - return - end - - args, opts = Thor::Options.split(given_args) - new(args, opts, config).invoke - end - end - - # Prints help information. - # - # ==== Options - # short:: When true, shows only usage. - # - def help(shell, options={}) - if options[:short] - shell.say banner - else - shell.say "Usage:" - shell.say " #{banner}" - shell.say - class_options_help(shell) - shell.say self.desc if self.desc - end - end - - # Stores invocations for this class merging with superclass values. - # - def invocations #:nodoc: - @invocations ||= from_superclass(:invocations, {}) - end - - # Stores invocation blocks used on invoke_from_option. - # - def invocation_blocks #:nodoc: - @invocation_blocks ||= from_superclass(:invocation_blocks, {}) - end - - # Invoke the given namespace or class given. It adds an instance - # method that will invoke the klass and task. You can give a block to - # configure how it will be invoked. - # - # The namespace/class given will have its options showed on the help - # usage. Check invoke_from_option for more information. - # - def invoke(*names, &block) - options = names.last.is_a?(Hash) ? names.pop : {} - verbose = options.fetch(:verbose, :white) - - names.each do |name| - invocations[name] = false - invocation_blocks[name] = block if block_given? - - class_eval <<-METHOD, __FILE__, __LINE__ - def _invoke_#{name.to_s.gsub(/\W/, '_')} - klass, task = self.class.prepare_for_invocation(nil, #{name.inspect}) - - if klass - say_status :invoke, #{name.inspect}, #{verbose.inspect} - block = self.class.invocation_blocks[#{name.inspect}] - _invoke_for_class_method klass, task, &block - else - say_status :error, %(#{name.inspect} [not found]), :red - end - end - METHOD - end - end - - # Invoke a thor class based on the value supplied by the user to the - # given option named "name". A class option must be created before this - # method is invoked for each name given. - # - # ==== Examples - # - # class GemGenerator < Thor::Group - # class_option :test_framework, :type => :string - # invoke_from_option :test_framework - # end - # - # ==== Boolean options - # - # In some cases, you want to invoke a thor class if some option is true or - # false. This is automatically handled by invoke_from_option. Then the - # option name is used to invoke the generator. - # - # ==== Preparing for invocation - # - # In some cases you want to customize how a specified hook is going to be - # invoked. You can do that by overwriting the class method - # prepare_for_invocation. The class method must necessarily return a klass - # and an optional task. - # - # ==== Custom invocations - # - # You can also supply a block to customize how the option is giong to be - # invoked. The block receives two parameters, an instance of the current - # class and the klass to be invoked. - # - def invoke_from_option(*names, &block) - options = names.last.is_a?(Hash) ? names.pop : {} - verbose = options.fetch(:verbose, :white) - - names.each do |name| - unless class_options.key?(name) - raise ArgumentError, "You have to define the option #{name.inspect} " << - "before setting invoke_from_option." - end - - invocations[name] = true - invocation_blocks[name] = block if block_given? - - class_eval <<-METHOD, __FILE__, __LINE__ - def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} - return unless options[#{name.inspect}] - - value = options[#{name.inspect}] - value = #{name.inspect} if TrueClass === value - klass, task = self.class.prepare_for_invocation(#{name.inspect}, value) - - if klass - say_status :invoke, value, #{verbose.inspect} - block = self.class.invocation_blocks[#{name.inspect}] - _invoke_for_class_method klass, task, &block - else - say_status :error, %(\#{value} [not found]), :red - end - end - METHOD - end - end - - # Remove a previously added invocation. - # - # ==== Examples - # - # remove_invocation :test_framework - # - def remove_invocation(*names) - names.each do |name| - remove_task(name) - remove_class_option(name) - invocations.delete(name) - invocation_blocks.delete(name) - end - end - - # Overwrite class options help to allow invoked generators options to be - # shown recursively when invoking a generator. - # - def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: - group_options = {} - - get_options_from_invocations(group_options, class_options) do |klass| - klass.send(:get_options_from_invocations, group_options, class_options) - end - - group_options.merge!(extra_group) if extra_group - super(shell, ungrouped_name, group_options) - end - - # Get invocations array and merge options from invocations. Those - # options are added to group_options hash. Options that already exists - # in base_options are not added twice. - # - def get_options_from_invocations(group_options, base_options) #:nodoc: - invocations.each do |name, from_option| - value = if from_option - option = class_options[name] - option.type == :boolean ? name : option.default - else - name - end - next unless value - - klass, task = prepare_for_invocation(name, value) - next unless klass && klass.respond_to?(:class_options) - - value = value.to_s - human_name = value.respond_to?(:classify) ? value.classify : value - - group_options[human_name] ||= [] - group_options[human_name] += klass.class_options.values.select do |option| - base_options[option.name.to_sym].nil? && option.group.nil? && - !group_options.values.flatten.any? { |i| i.name == option.name } - end - - yield klass if block_given? - end - end - - protected - - # The banner for this class. You can customize it if you are invoking the - # thor class by another means which is not the Thor::Runner. - # - def banner #:nodoc: - "#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}" - end - - def baseclass #:nodoc: - Thor::Group - end - - def create_task(meth) #:nodoc: - tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil) - true - end - end - - include Thor::Base - - protected - - # Shortcut to invoke with padding and block handling. Use internally by - # invoke and invoke_from_option class methods. - # - def _invoke_for_class_method(klass, task=nil, *args, &block) - shell.padding += 1 - - result = if block_given? - if block.arity == 2 - block.call(self, klass) - else - block.call(self, klass, task) - end - else - invoke klass, task, *args - end - - shell.padding -= 1 - result - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb deleted file mode 100644 index 34e7a4b911..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb +++ /dev/null @@ -1,172 +0,0 @@ -class Thor - module Invocation - def self.included(base) #:nodoc: - base.extend ClassMethods - end - - module ClassMethods - # Prepare for class methods invocations. This method must return a klass to - # have the invoked class options showed in help messages in generators. - # - def prepare_for_invocation(key, name) #:nodoc: - case name - when Symbol, String - Thor::Util.namespace_to_thor_class(name.to_s, false) - else - name - end - end - end - - # Make initializer aware of invocations and the initializer proc. - # - def initialize(args=[], options={}, config={}, &block) #:nodoc: - @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] } - @_initializer = [ args, options, config ] - super - end - - # Receives a name and invokes it. The name can be a string (either "task" or - # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task - # cannot be guessed by name, it can also be supplied as second argument. - # - # You can also supply the arguments, options and configuration values for - # the task to be invoked, if none is given, the same values used to - # initialize the invoker are used to initialize the invoked. - # - # ==== Examples - # - # class A < Thor - # def foo - # invoke :bar - # invoke "b:hello", ["José"] - # end - # - # def bar - # invoke "b:hello", ["José"] - # end - # end - # - # class B < Thor - # def hello(name) - # puts "hello #{name}" - # end - # end - # - # You can notice that the method "foo" above invokes two tasks: "bar", - # which belongs to the same class and "hello" which belongs to the class B. - # - # By using an invocation system you ensure that a task is invoked only once. - # In the example above, invoking "foo" will invoke "b:hello" just once, even - # if it's invoked later by "bar" method. - # - # When class A invokes class B, all arguments used on A initialization are - # supplied to B. This allows lazy parse of options. Let's suppose you have - # some rspec tasks: - # - # class Rspec < Thor::Group - # class_option :mock_framework, :type => :string, :default => :rr - # - # def invoke_mock_framework - # invoke "rspec:#{options[:mock_framework]}" - # end - # end - # - # As you noticed, it invokes the given mock framework, which might have its - # own options: - # - # class Rspec::RR < Thor::Group - # class_option :style, :type => :string, :default => :mock - # end - # - # Since it's not rspec concern to parse mock framework options, when RR - # is invoked all options are parsed again, so RR can extract only the options - # that it's going to use. - # - # If you want Rspec::RR to be initialized with its own set of options, you - # have to do that explicitely: - # - # invoke "rspec:rr", [], :style => :foo - # - # Besides giving an instance, you can also give a class to invoke: - # - # invoke Rspec::RR, [], :style => :foo - # - def invoke(name=nil, task=nil, args=nil, opts=nil, config=nil) - task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array) - args, opts, config = nil, args, opts if args.is_a?(Hash) - - object, task = _prepare_for_invocation(name, task) - if object.is_a?(Class) - klass = object - - stored_args, stored_opts, stored_config = @_initializer - args ||= stored_args.dup - opts ||= stored_opts.dup - - config ||= {} - config = stored_config.merge(_shared_configuration).merge!(config) - instance = klass.new(args, opts, config) - else - klass, instance = object.class, object - end - - method_args = [] - current = @_invocations[klass] - - iterator = proc do |_, task| - unless current.include?(task.name) - current << task.name - task.run(instance, method_args) - end - end - - if task - args ||= [] - method_args = args[Range.new(klass.arguments.size, -1)] || [] - iterator.call(nil, task) - else - klass.all_tasks.map(&iterator) - end - end - - protected - - # Configuration values that are shared between invocations. - # - def _shared_configuration - { :invocations => @_invocations } - end - - # Prepare for invocation in the instance level. In this case, we have to - # take into account that a just a task name from the current class was - # given or even a Thor::Task object. - # - def _prepare_for_invocation(name, sent_task=nil) #:nodoc: - if name.is_a?(Thor::Task) - task = name - elsif task = self.class.all_tasks[name.to_s] - object = self - else - object, task = self.class.prepare_for_invocation(nil, name) - task ||= sent_task - end - - # If the object was not set, use self and use the name as task. - object, task = self, name unless object - return object, _validate_klass_and_task(object, task) - end - - # Check if the object given is a Thor class object and get a task object - # for it. - # - def _validate_klass_and_task(object, task) #:nodoc: - klass = object.is_a?(Class) ? object : object.class - raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base - - task ||= klass.default_task if klass <= Thor - task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task) - task - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb deleted file mode 100644 index 57a3f6e1a5..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'thor/parser/argument' -require 'thor/parser/arguments' -require 'thor/parser/option' -require 'thor/parser/options' diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb deleted file mode 100644 index 2d7f4dbafb..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb +++ /dev/null @@ -1,67 +0,0 @@ -class Thor - class Argument - VALID_TYPES = [ :numeric, :hash, :array, :string ] - - attr_reader :name, :description, :required, :type, :default, :banner - alias :human_name :name - - def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil) - class_name = self.class.name.split("::").last - - raise ArgumentError, "#{class_name} name can't be nil." if name.nil? - raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) - - @name = name.to_s - @description = description - @required = required || false - @type = (type || :string).to_sym - @default = default - @banner = banner || default_banner - - validate! # Trigger specific validations - end - - def usage - required? ? banner : "[#{banner}]" - end - - def required? - required - end - - def show_default? - case default - when Array, String, Hash - !default.empty? - else - default - end - end - - protected - - def validate! - raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? - end - - def valid_type?(type) - VALID_TYPES.include?(type.to_sym) - end - - def default_banner - case type - when :boolean - nil - when :string, :default - human_name.upcase - when :numeric - "N" - when :hash - "key:value" - when :array - "one two three" - end - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb deleted file mode 100644 index 9a2262d6f7..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb +++ /dev/null @@ -1,145 +0,0 @@ -class Thor - class Arguments - NUMERIC = /(\d*\.\d+|\d+)/ - - # Receives an array of args and returns two arrays, one with arguments - # and one with switches. - # - def self.split(args) - arguments = [] - - args.each do |item| - break if item =~ /^-/ - arguments << item - end - - return arguments, args[Range.new(arguments.size, -1)] - end - - def self.parse(base, args) - new(base).parse(args) - end - - # Takes an array of Thor::Argument objects. - # - def initialize(arguments=[]) - @assigns, @non_assigned_required = {}, [] - @switches = arguments - - arguments.each do |argument| - if argument.default - @assigns[argument.human_name] = argument.default - elsif argument.required? - @non_assigned_required << argument - end - end - end - - def parse(args) - @pile = args.dup - - @switches.each do |argument| - break unless peek - @non_assigned_required.delete(argument) - @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name) - end - - check_requirement! - @assigns - end - - private - - def peek - @pile.first - end - - def shift - @pile.shift - end - - def unshift(arg) - unless arg.kind_of?(Array) - @pile.unshift(arg) - else - @pile = arg + @pile - end - end - - def current_is_value? - peek && peek.to_s !~ /^-/ - end - - # Runs through the argument array getting strings that contains ":" and - # mark it as a hash: - # - # [ "name:string", "age:integer" ] - # - # Becomes: - # - # { "name" => "string", "age" => "integer" } - # - def parse_hash(name) - return shift if peek.is_a?(Hash) - hash = {} - - while current_is_value? && peek.include?(?:) - key, value = shift.split(':') - hash[key] = value - end - hash - end - - # Runs through the argument array getting all strings until no string is - # found or a switch is found. - # - # ["a", "b", "c"] - # - # And returns it as an array: - # - # ["a", "b", "c"] - # - def parse_array(name) - return shift if peek.is_a?(Array) - array = [] - - while current_is_value? - array << shift - end - array - end - - # Check if the peel is numeric ofrmat and return a Float or Integer. - # Otherwise raises an error. - # - def parse_numeric(name) - return shift if peek.is_a?(Numeric) - - unless peek =~ NUMERIC && $& == peek - raise MalformattedArgumentError, "expected numeric value for '#{name}'; got #{peek.inspect}" - end - - $&.index('.') ? shift.to_f : shift.to_i - end - - # Parse string, i.e., just return the current value in the pile. - # - def parse_string(name) - shift - end - - # Raises an error if @non_assigned_required array is not empty. - # - def check_requirement! - unless @non_assigned_required.empty? - names = @non_assigned_required.map do |o| - o.respond_to?(:switch_name) ? o.switch_name : o.human_name - end.join("', '") - - class_name = self.class.name.split('::').last.downcase - raise RequiredArgumentMissingError, "no value provided for required #{class_name} '#{names}'" - end - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb deleted file mode 100644 index 5c43f6b18f..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb +++ /dev/null @@ -1,132 +0,0 @@ -class Thor - class Option < Argument - attr_reader :aliases, :group - - VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] - - def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil) - super(name, description, required, type, default, banner) - @aliases = [*aliases].compact - @group = group.to_s.capitalize if group - end - - # This parse quick options given as method_options. It makes several - # assumptions, but you can be more specific using the option method. - # - # parse :foo => "bar" - # #=> Option foo with default value bar - # - # parse [:foo, :baz] => "bar" - # #=> Option foo with default value bar and alias :baz - # - # parse :foo => :required - # #=> Required option foo without default value - # - # parse :foo => 2 - # #=> Option foo with default value 2 and type numeric - # - # parse :foo => :numeric - # #=> Option foo without default value and type numeric - # - # parse :foo => true - # #=> Option foo with default value true and type boolean - # - # The valid types are :boolean, :numeric, :hash, :array and :string. If none - # is given a default type is assumed. This default type accepts arguments as - # string (--foo=value) or booleans (just --foo). - # - # By default all options are optional, unless :required is given. - # - def self.parse(key, value) - if key.is_a?(Array) - name, *aliases = key - else - name, aliases = key, [] - end - - name = name.to_s - default = value - - type = case value - when Symbol - default = nil - - if VALID_TYPES.include?(value) - value - elsif required = (value == :required) - :string - elsif value == :optional - # TODO Remove this warning in the future. - warn "Optional type is deprecated. Choose :boolean or :string instead. Assumed to be :boolean." - :boolean - end - when TrueClass, FalseClass - :boolean - when Numeric - :numeric - when Hash, Array, String - value.class.name.downcase.to_sym - end - - self.new(name.to_s, nil, required, type, default, nil, nil, aliases) - end - - def switch_name - @switch_name ||= dasherized? ? name : dasherize(name) - end - - def human_name - @human_name ||= dasherized? ? undasherize(name) : name - end - - def usage(padding=0) - sample = if banner && !banner.to_s.empty? - "#{switch_name}=#{banner}" - else - switch_name - end - - sample = "[#{sample}]" unless required? - - if aliases.empty? - (" " * padding) << sample - else - "#{aliases.join(', ')}, #{sample}" - end - end - - # Allow some type predicates as: boolean?, string? and etc. - # - def method_missing(method, *args, &block) - given = method.to_s.sub(/\?$/, '').to_sym - if valid_type?(given) - self.type == given - else - super - end - end - - protected - - def validate! - raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? - end - - def valid_type?(type) - VALID_TYPES.include?(type.to_sym) - end - - def dasherized? - name.index('-') == 0 - end - - def undasherize(str) - str.sub(/^-{1,2}/, '') - end - - def dasherize(str) - (str.length > 1 ? "--" : "-") + str.gsub('_', '-') - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb deleted file mode 100644 index 01c86b7b27..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb +++ /dev/null @@ -1,142 +0,0 @@ -class Thor - # This is a modified version of Daniel Berger's Getopt::Long class, licensed - # under Ruby's license. - # - class Options < Arguments - LONG_RE = /^(--\w+[-\w+]*)$/ - SHORT_RE = /^(-[a-z])$/i - EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i - SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args - SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i - - # Receives a hash and makes it switches. - # - def self.to_switches(options) - options.map do |key, value| - case value - when true - "--#{key}" - when Array - "--#{key} #{value.map{ |v| v.inspect }.join(' ')}" - when Hash - "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}" - when nil, false - "" - else - "--#{key} #{value.inspect}" - end - end.join(" ") - end - - # Takes a hash of Thor::Option objects. - # - def initialize(options={}) - options = options.values - super(options) - @shorts, @switches = {}, {} - - options.each do |option| - @switches[option.switch_name] = option - - option.aliases.each do |short| - @shorts[short.to_s] ||= option.switch_name - end - end - end - - def parse(args) - @pile = args.dup - - while peek - if current_is_switch? - case shift - when SHORT_SQ_RE - unshift($1.split('').map { |f| "-#{f}" }) - next - when EQ_RE, SHORT_NUM - unshift($2) - switch = $1 - when LONG_RE, SHORT_RE - switch = $1 - end - - switch = normalize_switch(switch) - next unless option = switch_option(switch) - - @assigns[option.human_name] = parse_peek(switch, option) - else - shift - end - end - - check_requirement! - @assigns - end - - protected - - # Returns true if the current value in peek is a registered switch. - # - def current_is_switch? - case peek - when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM - switch?($1) - when SHORT_SQ_RE - $1.split('').any? { |f| switch?("-#{f}") } - end - end - - def switch?(arg) - switch_option(arg) || @shorts.key?(arg) - end - - def switch_option(arg) - if match = no_or_skip?(arg) - @switches[arg] || @switches["--#{match}"] - else - @switches[arg] - end - end - - def no_or_skip?(arg) - arg =~ /^--(no|skip)-([-\w]+)$/ - $2 - end - - # Check if the given argument is actually a shortcut. - # - def normalize_switch(arg) - @shorts.key?(arg) ? @shorts[arg] : arg - end - - # Parse boolean values which can be given as --foo=true, --foo or --no-foo. - # - def parse_boolean(switch) - if current_is_value? - ["true", "TRUE", "t", "T", true].include?(shift) - else - @switches.key?(switch) || !no_or_skip?(switch) - end - end - - # Parse the value at the peek analyzing if it requires an input or not. - # - def parse_peek(switch, option) - unless current_is_value? - if option.boolean? - # No problem for boolean types - elsif no_or_skip?(switch) - return nil # User set value to nil - elsif option.string? && !option.required? - return option.human_name # Return the option name - else - raise MalformattedArgumentError, "no value provided for option '#{switch}'" - end - end - - @non_assigned_required.delete(option) - send(:"parse_#{option.type}", switch) - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb deleted file mode 100644 index 6782c61dec..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb +++ /dev/null @@ -1,291 +0,0 @@ -require 'fileutils' -require 'open-uri' -require 'yaml' -require 'digest/md5' -require 'pathname' - -class Thor::Runner < Thor - map "-T" => :list, "-i" => :install, "-u" => :update - - # Override Thor#help so it can give information about any class and any method. - # - def help(meth=nil) - if meth && !self.respond_to?(meth) - initialize_thorfiles(meth) - klass, task = Thor::Util.namespace_to_thor_class(meth) - klass.start(["-h", task].compact, :shell => self.shell) # send mapping -h because it works with Thor::Group too - else - super - end - end - - # If a task is not found on Thor::Runner, method missing is invoked and - # Thor::Runner is then responsable for finding the task in all classes. - # - def method_missing(meth, *args) - meth = meth.to_s - initialize_thorfiles(meth) - klass, task = Thor::Util.namespace_to_thor_class(meth) - args.unshift(task) if task - klass.start(args, :shell => shell) - end - - desc "install NAME", "Install a Thor file into your system tasks, optionally named for future updates" - method_options :as => :string, :relative => :boolean - def install(name) - initialize_thorfiles - - # If a directory name is provided as the argument, look for a 'main.thor' - # task in said directory. - begin - if File.directory?(File.expand_path(name)) - base, package = File.join(name, "main.thor"), :directory - contents = open(base).read - else - base, package = name, :file - contents = open(name).read - end - rescue OpenURI::HTTPError - raise Error, "Error opening URI '#{name}'" - rescue Errno::ENOENT - raise Error, "Error opening file '#{name}'" - end - - say "Your Thorfile contains:" - say contents - - return false if no?("Do you wish to continue [y/N]?") - - as = options["as"] || begin - first_line = contents.split("\n")[0] - (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil - end - - unless as - basename = File.basename(name) - as = ask("Please specify a name for #{name} in the system repository [#{basename}]:") - as = basename if as.empty? - end - - location = if options[:relative] || name =~ /^http:\/\// - name - else - File.expand_path(name) - end - - thor_yaml[as] = { - :filename => Digest::MD5.hexdigest(name + as), - :location => location, - :namespaces => Thor::Util.namespaces_in_contents(contents, base) - } - - save_yaml(thor_yaml) - say "Storing thor file in your system repository" - destination = File.join(thor_root, thor_yaml[as][:filename]) - - if package == :file - File.open(destination, "w") { |f| f.puts contents } - else - FileUtils.cp_r(name, destination) - end - - thor_yaml[as][:filename] # Indicate success - end - - desc "uninstall NAME", "Uninstall a named Thor module" - def uninstall(name) - raise Error, "Can't find module '#{name}'" unless thor_yaml[name] - say "Uninstalling #{name}." - FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}")) - - thor_yaml.delete(name) - save_yaml(thor_yaml) - - puts "Done." - end - - desc "update NAME", "Update a Thor file from its original location" - def update(name) - raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] - - say "Updating '#{name}' from #{thor_yaml[name][:location]}" - - old_filename = thor_yaml[name][:filename] - self.options = self.options.merge("as" => name) - filename = install(thor_yaml[name][:location]) - - unless filename == old_filename - File.delete(File.join(thor_root, old_filename)) - end - end - - desc "installed", "List the installed Thor modules and tasks" - method_options :internal => :boolean - def installed - initialize_thorfiles(nil, true) - - klasses = Thor::Base.subclasses - klasses -= [Thor, Thor::Runner] unless options["internal"] - - display_klasses(true, klasses) - end - - desc "list [SEARCH]", - "List the available thor tasks (--substring means SEARCH anywhere in the namespace)" - method_options :substring => :boolean, :group => :string, :all => :boolean - def list(search="") - initialize_thorfiles - - search = ".*#{search}" if options["substring"] - search = /^#{search}.*/i - group = options[:group] || "standard" - - klasses = Thor::Base.subclasses.select do |k| - (options[:all] || k.group == group) && k.namespace =~ search - end - - display_klasses(false, klasses) - end - - private - - def thor_root - Thor::Util.thor_root - end - - def thor_yaml - @thor_yaml ||= begin - yaml_file = File.join(thor_root, "thor.yml") - yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file) - yaml || {} - end - end - - # Save the yaml file. If none exists in thor root, creates one. - # - def save_yaml(yaml) - yaml_file = File.join(thor_root, "thor.yml") - - unless File.exists?(yaml_file) - FileUtils.mkdir_p(thor_root) - yaml_file = File.join(thor_root, "thor.yml") - FileUtils.touch(yaml_file) - end - - File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml } - end - - # Load the thorfiles. If relevant_to is supplied, looks for specific files - # in the thor_root instead of loading them all. - # - # By default, it also traverses the current path until find Thor files, as - # described in thorfiles. This look up can be skipped by suppliying - # skip_lookup true. - # - def initialize_thorfiles(relevant_to=nil, skip_lookup=false) - thorfiles(relevant_to, skip_lookup).each do |f| - Thor::Util.load_thorfile(f) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f)) - end - end - - # Finds Thorfiles by traversing from your current directory down to the root - # directory of your system. If at any time we find a Thor file, we stop. - # - # We also ensure that system-wide Thorfiles are loaded first, so local - # Thorfiles can override them. - # - # ==== Example - # - # If we start at /Users/wycats/dev/thor ... - # - # 1. /Users/wycats/dev/thor - # 2. /Users/wycats/dev - # 3. /Users/wycats <-- we find a Thorfile here, so we stop - # - # Suppose we start at c:\Documents and Settings\james\dev\thor ... - # - # 1. c:\Documents and Settings\james\dev\thor - # 2. c:\Documents and Settings\james\dev - # 3. c:\Documents and Settings\james - # 4. c:\Documents and Settings - # 5. c:\ <-- no Thorfiles found! - # - def thorfiles(relevant_to=nil, skip_lookup=false) - # Deal with deprecated thor when :namespaces: is available as constants - save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml) - - thorfiles = [] - - unless skip_lookup - Pathname.pwd.ascend do |path| - thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten - break unless thorfiles.empty? - end - end - - files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob) - files += thorfiles - files -= ["#{thor_root}/thor.yml"] - - files.map! do |file| - File.directory?(file) ? File.join(file, "main.thor") : file - end - end - - # Load thorfiles relevant to the given method. If you provide "foo:bar" it - # will load all thor files in the thor.yaml that has "foo" e "foo:bar" - # namespaces registered. - # - def thorfiles_relevant_to(meth) - lookup = [ meth, meth.split(":")[0...-1].join(":") ] - - files = thor_yaml.select do |k, v| - v[:namespaces] && !(v[:namespaces] & lookup).empty? - end - - files.map { |k, v| File.join(thor_root, "#{v[:filename]}") } - end - - # Display information about the given klasses. If with_module is given, - # it shows a table with information extracted from the yaml file. - # - def display_klasses(with_modules=false, klasses=Thor.subclasses) - klasses -= [Thor, Thor::Runner] unless with_modules - raise Error, "No Thor tasks available" if klasses.empty? - - if with_modules && !thor_yaml.empty? - info = [] - labels = ["Modules", "Namespaces"] - - info << labels - info << [ "-" * labels[0].size, "-" * labels[1].size ] - - thor_yaml.each do |name, hash| - info << [ name, hash[:namespaces].join(", ") ] - end - - print_table info - say "" - end - - unless klasses.empty? - klasses.each { |k| display_tasks(k) } - else - say "\033[1;34mNo Thor tasks available\033[0m" - end - end - - # Display tasks from the given Thor class. - # - def display_tasks(klass) - unless klass.tasks.empty? - base = klass.namespace - - color = base == "default" ? :magenta : :blue - say shell.set_color(base, color, true) - say "-" * base.length - - klass.help(shell, :short => true, :namespace => true) - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb deleted file mode 100644 index 7ed4a24bfb..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb +++ /dev/null @@ -1,72 +0,0 @@ -require 'thor/shell/color' - -class Thor - module Base - # Returns the shell used in all Thor classes. Default to color one. - # - def self.shell - @shell ||= Thor::Shell::Color - end - - # Sets the shell used in all Thor classes. - # - def self.shell=(klass) - @shell = klass - end - end - - module Shell - SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table] - - # Add shell to initialize config values. - # - # ==== Configuration - # shell:: An instance of the shell to be used. - # - # ==== Examples - # - # class MyScript < Thor - # argument :first, :type => :numeric - # end - # - # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new - # - def initialize(args=[], options={}, config={}) - super - self.shell = config[:shell] - self.shell.base ||= self if self.shell.respond_to?(:base) - end - - # Holds the shell for the given Thor instance. If no shell is given, - # it gets a default shell from Thor::Base.shell. - # - def shell - @shell ||= Thor::Base.shell.new - end - - # Sets the shell for this thor class. - # - def shell=(shell) - @shell = shell - end - - # Common methods that are delegated to the shell. - # - SHELL_DELEGATED_METHODS.each do |method| - module_eval <<-METHOD, __FILE__, __LINE__ - def #{method}(*args) - shell.#{method}(*args) - end - METHOD - end - - protected - - # Allow shell to be shared between invocations. - # - def _shared_configuration - super.merge!(:shell => self.shell) - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb deleted file mode 100644 index e294c87567..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb +++ /dev/null @@ -1,216 +0,0 @@ -require 'tempfile' - -class Thor - module Shell - class Basic - attr_accessor :base, :padding - - # Initialize base and padding to nil. - # - def initialize #:nodoc: - @base, @padding = nil, 0 - end - - # Do not allow padding to be less than zero. - # - def padding=(value) #:nodoc: - @padding = [0, value].max - end - - # Ask something to the user and receives a response. - # - # ==== Example - # ask("What is your name?") - # - def ask(statement, color=nil) - say("#{statement} ", color) - $stdin.gets.strip - end - - # Say (print) something to the user. If the sentence ends with a whitespace - # or tab character, a new line is not appended (print + flush). Otherwise - # are passed straight to puts (behavior got from Highline). - # - # ==== Example - # say("I know you knew that.") - # - def say(message="", color=nil, force_new_line=false) - message = message.to_s - new_line = force_new_line || !(message[-1, 1] == " " || message[-1, 1] == "\t") - message = set_color(message, color) if color - - if new_line - $stdout.puts(message) - else - $stdout.print(message) - $stdout.flush - end - end - - # Say a status with the given color and appends the message. Since this - # method is used frequently by actions, it allows nil or false to be given - # in log_status, avoiding the message from being shown. If a Symbol is - # given in log_status, it's used as the color. - # - def say_status(status, message, log_status=true) #:nodoc: - return if quiet? || log_status == false - spaces = " " * (padding + 1) - color = log_status.is_a?(Symbol) ? log_status : :green - - status = status.to_s.rjust(12) - status = set_color status, color, true if color - say "#{status}#{spaces}#{message}", nil, true - end - - # Make a question the to user and returns true if the user replies "y" or - # "yes". - # - def yes?(statement, color=nil) - ask(statement, color) =~ is?(:yes) - end - - # Make a question the to user and returns true if the user replies "n" or - # "no". - # - def no?(statement, color=nil) - !yes?(statement, color) - end - - # Prints a list of items. - # - # ==== Parameters - # list - # mode:: Can be :rows or :inline. Defaults to :rows. - # - def print_list(list, mode=:rows) - return if list.empty? - - content = case mode - when :inline - last = list.pop - "#{list.join(", ")}, and #{last}" - else # rows - list.join("\n") - end - - $stdout.puts content - end - - # Prints a table. - # - # ==== Parameters - # Array[Array[String, String, ...]] - # - # ==== Options - # ident:: Ident the first column by ident value. - # emphasize_last:: When true, add a different behavior to the last column. - # - def print_table(table, options={}) - return if table.empty? - - formats = [] - 0.upto(table.first.length - 2) do |i| - maxima = table.max{ |a,b| a[i].size <=> b[i].size }[i].size - formats << "%-#{maxima + 2}s" - end - - formats[0] = formats[0].insert(0, " " * options[:ident]) if options[:ident] - formats << "%s" - - table.each do |row| - row.each_with_index do |column, i| - $stdout.print formats[i] % column.to_s - end - $stdout.puts - end - end - - # Deals with file collision and returns true if the file should be - # overwriten and false otherwise. If a block is given, it uses the block - # response as the content for the diff. - # - # ==== Parameters - # destination:: the destination file to solve conflicts - # block:: an optional proc that returns the value to be used in diff - # - def file_collision(destination) - return true if @always_force - options = block_given? ? "[Ynaqdh]" : "[Ynaqh]" - - while true - answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}] - - case answer - when is?(:yes), is?(:force) - return true - when is?(:no), is?(:skip) - return false - when is?(:always) - return @always_force = true - when is?(:quit) - say 'Aborting...' - raise SystemExit - when is?(:diff) - show_diff(destination, yield) if block_given? - say 'Retrying...' - else - say file_collision_help - end - end - end - - # Called if something goes wrong during the execution. This is used by Thor - # internally and should not be used inside your scripts. If someone went - # wrong, you can always raise an exception. If you raise a Thor::Error, it - # will be rescued and wrapped in the method below. - # - def error(statement) #:nodoc: - $stderr.puts statement - end - - # Apply color to the given string with optional bold. - # - def set_color(string, color, bold=false) - string - end - - protected - - def is?(value) - value = value.to_s - - if value.size == 1 - /\A#{value}\z/i - else - /\A(#{value}|#{value[0,1]})\z/i - end - end - - def file_collision_help -< e - parse_argument_error(instance, e, caller) - rescue NoMethodError => e - parse_no_method_error(instance, e) - end - - # Returns the formatted usage. If a class is given, the class arguments are - # injected in the usage. - # - def formatted_usage(klass=nil, namespace=false) - formatted = '' - formatted << "#{klass.namespace.gsub(/^default/,'')}:" if klass && namespace - formatted << formatted_arguments(klass) - formatted << " #{formatted_options}" - formatted.strip! - formatted - end - - # Injects the class arguments into the task usage. - # - def formatted_arguments(klass) - if klass && !klass.arguments.empty? - usage.to_s.gsub(/^#{name}/) do |match| - match << " " << klass.arguments.map{ |a| a.usage }.join(' ') - end - else - usage.to_s - end - end - - # Returns the options usage for this task. - # - def formatted_options - @formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ") - end - - protected - - # Given a target, checks if this class name is not a private/protected method. - # - def public_method?(instance) - collection = instance.private_methods + instance.protected_methods - !(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9 - end - - # Clean everything that comes from the Thor gempath and remove the caller. - # - def sans_backtrace(backtrace, caller) - dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/ - saned = backtrace.reject { |frame| frame =~ dirname } - saned -= caller - end - - def parse_argument_error(instance, e, caller) - backtrace = sans_backtrace(e.backtrace, caller) - - if backtrace.empty? && e.message =~ /wrong number of arguments/ - if instance.is_a?(Thor::Group) - raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?" - else - raise InvocationError, "'#{name}' was called incorrectly. Call as " << - "'#{formatted_usage(instance.class, true)}'" - end - else - raise e - end - end - - def parse_no_method_error(instance, e) - if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ - raise UndefinedTaskError, "The #{instance.class.namespace} namespace " << - "doesn't have a '#{name}' task" - else - raise e - end - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb deleted file mode 100644 index d1a7b1c673..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb +++ /dev/null @@ -1,4 +0,0 @@ -# This only loads all tasks inside tasks. -Dir[File.join(File.dirname(__FILE__), "tasks", "*.rb")].each do |task| - require task -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb deleted file mode 100644 index 6b20ff1634..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb +++ /dev/null @@ -1,35 +0,0 @@ -class Thor - # Creates an install task. - # - # ==== Parameters - # spec - # - # ==== Options - # :dir - The directory where the package is hold before installation. Defaults to ./pkg. - # - def self.install_task(spec, options={}) - package_task(spec, options) - tasks['install'] = Thor::InstallTask.new(spec, options) - end - - class InstallTask < Task - attr_accessor :spec, :config - - def initialize(gemspec, config={}) - super(:install, "Install the gem", "install", {}) - @spec = gemspec - @config = { :dir => File.join(Dir.pwd, "pkg") }.merge(config) - end - - def run(instance, args=[]) - null, sudo, gem = RUBY_PLATFORM =~ /mswin|mingw/ ? ['NUL', '', 'gem.bat'] : - ['/dev/null', 'sudo', 'gem'] - - old_stderr, $stderr = $stderr.dup, File.open(null, "w") - instance.invoke(:package) - $stderr = old_stderr - - system %{#{sudo} #{Gem.ruby} -S #{gem} install #{config[:dir]}/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources} - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb deleted file mode 100644 index 603d61b4ab..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb +++ /dev/null @@ -1,31 +0,0 @@ -require "fileutils" - -class Thor - # Creates a package task. - # - # ==== Parameters - # spec - # - # ==== Options - # :dir - The package directory. Defaults to ./pkg. - # - def self.package_task(spec, options={}) - tasks['package'] = Thor::PackageTask.new(spec, options) - end - - class PackageTask < Task - attr_accessor :spec, :config - - def initialize(gemspec, config={}) - super(:package, "Build a gem package", "package", {}) - @spec = gemspec - @config = {:dir => File.join(Dir.pwd, "pkg")}.merge(config) - end - - def run(instance, args=[]) - FileUtils.mkdir_p(config[:dir]) - Gem::Builder.new(spec).build - FileUtils.mv(spec.file_name, File.join(config[:dir], spec.file_name)) - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb deleted file mode 100644 index c7d00968e8..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require "fileutils" - -class Thor - # Creates a spec task. - # - # ==== Parameters - # files - Array of files to spec - # - # ==== Options - # :name - The name of the task. It can be rcov or spec. Spec is the default. - # :rcov - A hash with rcov specific options. - # :rcov_dir - Where rcov reports should be printed. - # :verbose - Sets the default value for verbose, although it can be specified - # also through the command line. - # - # All other options are added to rspec. - # - def self.spec_task(files, options={}) - name = (options.delete(:name) || 'spec').to_s - tasks[name] = Thor::SpecTask.new(name, files, options) - end - - class SpecTask < Task - attr_accessor :name, :files, :rcov_dir, :rcov_config, :spec_config - - def initialize(name, files, config={}) - options = { :verbose => Thor::Option.parse(:verbose, config.delete(:verbose) || false) } - super(name, "#{name.capitalize} task", name, options) - - @name = name - @files = files.map{ |f| %["#{f}"] }.join(" ") - @rcov_dir = config.delete(:rdoc_dir) || File.join(Dir.pwd, 'coverage') - @rcov_config = config.delete(:rcov) || {} - @spec_config = { :format => 'specdoc', :color => true }.merge(config) - end - - def run(instance, args=[]) - rcov_opts = Thor::Options.to_switches(rcov_config) - spec_opts = Thor::Options.to_switches(spec_config) - - require 'rbconfig' - cmd = RbConfig::CONFIG['ruby_install_name'] << " " - - if rcov? - FileUtils.rm_rf(rcov_dir) - cmd << "-S #{where('rcov')} -o #{rcov_dir} #{rcov_opts} " - end - - cmd << [where('spec'), rcov? ? " -- " : nil, files, spec_opts].join(" ") - - puts cmd if instance.options.verbose? - system(cmd) - exit($?.exitstatus) - end - - private - - def rcov? - name == "rcov" - end - - def where(file) - ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| - file_with_path = File.join(path, file) - next unless File.exist?(file_with_path) && File.executable?(file_with_path) - return File.expand_path(file_with_path) - end - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb deleted file mode 100644 index 26db24aadb..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb +++ /dev/null @@ -1,229 +0,0 @@ -require 'rbconfig' - -class Thor - module Sandbox; end - - # This module holds several utilities: - # - # 1) Methods to convert thor namespaces to constants and vice-versa. - # - # Thor::Utils.constant_to_namespace(Foo::Bar::Baz) #=> "foo:bar:baz" - # Thor::Utils.namespace_to_constant("foo:bar:baz") #=> Foo::Bar::Baz - # - # 2) Loading thor files and sandboxing: - # - # Thor::Utils.load_thorfile("~/.thor/foo") - # - module Util - - # Receives a namespace and search for it in the Thor::Base subclasses. - # - # ==== Parameters - # namespace:: The namespace to search for. - # - def self.find_by_namespace(namespace) - namespace = 'default' if namespace.empty? - - Thor::Base.subclasses.find do |klass| - klass.namespace == namespace - end - end - - # Receives a constant and converts it to a Thor namespace. Since Thor tasks - # can be added to a sandbox, this method is also responsable for removing - # the sandbox namespace. - # - # This method should not be used in general because it's used to deal with - # older versions of Thor. On current versions, if you need to get the - # namespace from a class, just call namespace on it. - # - # ==== Parameters - # constant:: The constant to be converted to the thor path. - # - # ==== Returns - # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz" - # - def self.constant_to_namespace(constant, remove_default=true) - constant = constant.to_s.gsub(/^Thor::Sandbox::/, "") - constant = snake_case(constant).squeeze(":") - constant.gsub!(/^default/, '') if remove_default - constant - end - - # Given the contents, evaluate it inside the sandbox and returns the thor - # classes defined in the sandbox. - # - # ==== Parameters - # contents - # - # ==== Returns - # Array[Object] - # - def self.namespaces_in_contents(contents, file=__FILE__) - old_constants = Thor::Base.subclasses.dup - Thor::Base.subclasses.clear - - load_thorfile(file, contents) - - new_constants = Thor::Base.subclasses.dup - Thor::Base.subclasses.replace(old_constants) - - new_constants.map!{ |c| c.namespace } - new_constants.compact! - new_constants - end - - # Receives a string and convert it to snake case. SnakeCase returns snake_case. - # - # ==== Parameters - # String - # - # ==== Returns - # String - # - def self.snake_case(str) - return str.downcase if str =~ /^[A-Z_]+$/ - str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/ - return $+.downcase - end - - # Receives a namespace and tries to retrieve a Thor or Thor::Group class - # from it. It first searches for a class using the all the given namespace, - # if it's not found, removes the highest entry and searches for the class - # again. If found, returns the highest entry as the class name. - # - # ==== Examples - # - # class Foo::Bar < Thor - # def baz - # end - # end - # - # class Baz::Foo < Thor::Group - # end - # - # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default task - # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil - # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz" - # - # ==== Parameters - # namespace - # - # ==== Errors - # Thor::Error:: raised if the namespace cannot be found. - # - # Thor::Error:: raised if the namespace evals to a class which does not - # inherit from Thor or Thor::Group. - # - def self.namespace_to_thor_class(namespace, raise_if_nil=true) - klass, task_name = Thor::Util.find_by_namespace(namespace), nil - - if klass.nil? && namespace.include?(?:) - namespace = namespace.split(":") - task_name = namespace.pop - klass = Thor::Util.find_by_namespace(namespace.join(":")) - end - - raise Error, "could not find Thor class or task '#{namespace}'" if raise_if_nil && klass.nil? - - return klass, task_name - end - - # Receives a path and load the thor file in the path. The file is evaluated - # inside the sandbox to avoid namespacing conflicts. - # - def self.load_thorfile(path, content=nil) - content ||= File.read(path) - - begin - Thor::Sandbox.class_eval(content, path) - rescue Exception => e - $stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}" - end - end - - # Receives a yaml (hash) and updates all constants entries to namespace. - # This was added to deal with deprecated versions of Thor. - # - # TODO Deprecate this method in the future. - # - # ==== Returns - # TrueClass|FalseClass:: Returns true if any change to the yaml file was made. - # - def self.convert_constants_to_namespaces(yaml) - yaml_changed = false - - yaml.each do |k, v| - next unless v[:constants] && v[:namespaces].nil? - yaml_changed = true - yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.constant_to_namespace(c)} - end - - yaml_changed - end - - def self.user_home - @@user_home ||= if ENV["HOME"] - ENV["HOME"] - elsif ENV["USERPROFILE"] - ENV["USERPROFILE"] - elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"] - File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"]) - elsif ENV["APPDATA"] - ENV["APPDATA"] - else - begin - File.expand_path("~") - rescue - if File::ALT_SEPARATOR - "C:/" - else - "/" - end - end - end - end - - # Returns the root where thor files are located, dependending on the OS. - # - def self.thor_root - File.join(user_home, ".thor") - end - - # Returns the files in the thor root. On Windows thor_root will be something - # like this: - # - # C:\Documents and Settings\james\.thor - # - # If we don't #gsub the \ character, Dir.glob will fail. - # - def self.thor_root_glob - files = Dir["#{thor_root.gsub(/\\/, '/')}/*"] - - files.map! do |file| - File.directory?(file) ? File.join(file, "main.thor") : file - end - end - - # Where to look for Thor files. - # - def self.globs_for(path) - ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] - end - - # Return the path to the ruby interpreter taking into account multiple - # installations and windows extensions. - # - def self.ruby_command #:nodoc: - @ruby_command ||= begin - ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) - ruby << Config::CONFIG['EXEEXT'] - - # escape string in case path to ruby executable contain spaces. - ruby.sub!(/.*\s.*/m, '"\&"') - ruby - end - end - - end -end diff --git a/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc b/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc new file mode 100644 index 0000000000..dba25b7205 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc @@ -0,0 +1,77 @@ +== TODO + +* Improve spec coverage for Thor::Runner + +== 0.11.x, released 2009-07-01 + +* Added a rake compatibility layer. It allows you to use spec and rdoc tasks on + Thor classes. + +* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore + since it wrong behavior to the invocation system. + +* thor help now show information about any class/task. All those calls are + possible: + + thor help describe + thor help describe:amazing + + Or even with default namespaces: + + thor help :spec + +* Thor::Runner now invokes the default task if none is supplied: + + thor describe # invokes the default task, usually help + +* Thor::Runner now works with mappings: + + thor describe -h + +* Added some documentation and code refactoring. + +== 0.9.8, released 2008-10-20 + +* Fixed some tiny issues that were introduced lately. + +== 0.9.7, released 2008-10-13 + +* Setting global method options on the initialize method works as expected: + All other tasks will accept these global options in addition to their own. +* Added 'group' notion to Thor task sets (class Thor); by default all tasks + are in the 'standard' group. Running 'thor -T' will only show the standard + tasks - adding --all will show all tasks. You can also filter on a specific + group using the --group option: thor -T --group advanced + +== 0.9.6, released 2008-09-13 + +* Generic improvements + +== 0.9.5, released 2008-08-27 + +* Improve Windows compatibility +* Update (incorrect) README and task.thor sample file +* Options hash is now frozen (once returned) +* Allow magic predicates on options object. For instance: `options.force?` +* Add support for :numeric type +* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f]) +* Allow specifying optional args with default values: method_options(:user => "mislav") +* Don't write options for nil or false values. This allows, for example, turning color off when running specs. +* Exit with the status of the spec command to help CI stuff out some. + +== 0.9.4, released 2008-08-13 + +* Try to add Windows compatibility. +* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore +* Allow options at the beginning of the argument list as well as the end. +* Make options available with symbol keys in addition to string keys. +* Allow true to be passed to Thor#method_options to denote a boolean option. +* If loading a thor file fails, don't give up, just print a warning and keep going. +* Make sure that we re-raise errors if they happened further down the pipe than we care about. +* Only delete the old file on updating when the installation of the new one is a success +* Make it Ruby 1.8.5 compatible. +* Don't raise an error if a boolean switch is defined multiple times. +* Thor::Options now doesn't parse through things that look like options but aren't. +* Add URI detection to install task, and make sure we don't append ".thor" to URIs +* Add rake2thor to the gem binfiles. +* Make sure local Thorfiles override system-wide ones. diff --git a/railties/lib/vendor/thor-0.11.5/LICENSE b/railties/lib/vendor/thor-0.11.5/LICENSE new file mode 100644 index 0000000000..98722da459 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008 Yehuda Katz + +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. \ No newline at end of file diff --git a/railties/lib/vendor/thor-0.11.5/README.rdoc b/railties/lib/vendor/thor-0.11.5/README.rdoc new file mode 100644 index 0000000000..f1106f02b6 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/README.rdoc @@ -0,0 +1,234 @@ += thor + +Map options to a class. Simply create a class with the appropriate annotations +and have options automatically map to functions and parameters. + +Example: + + class App < Thor # [1] + map "-L" => :list # [2] + + desc "install APP_NAME", "install one of the available apps" # [3] + method_options :force => :boolean, :alias => :string # [4] + def install(name) + user_alias = options[:alias] + if options.force? + # do something + end + # other code + end + + desc "list [SEARCH]", "list all of the available apps, limited by SEARCH" + def list(search="") + # list everything + end + end + +Thor automatically maps commands as such: + + thor app:install myname --force + +That gets converted to: + + App.new.install("myname") + # with {'force' => true} as options hash + +1. Inherit from Thor to turn a class into an option mapper +2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list +3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description +4. Provide any additional options that will be available the instance method options. + +== Types for method_options + +* :boolean - is parsed as --option or --option=true +* :string - is parsed as --option=VALUE +* :numeric - is parsed as --option=N +* :array - is parsed as --option=one two three +* :hash - is parsed as --option=name:string age:integer + +Besides, method_option allows a default value to be given, examples: + + method_options :force => false + #=> Creates a boolean option with default value false + + method_options :alias => "bar" + #=> Creates a string option with default value "bar" + + method_options :threshold => 3.0 + #=> Creates a numeric option with default value 3.0 + +You can also supply :option => :required to mark an option as required. The +type is assumed to be string. If you want a required hash with default values +as option, you can use method_option which uses a more declarative style: + + method_option :attributes, :type => :hash, :default => {}, :required => true + +All arguments can be set to nil (except required arguments), by suppling a no or +skip variant. For example: + + thor app name --no-attributes + +In previous versions, aliases for options were created automatically, but now +they should be explicit. You can supply aliases in both short and declarative +styles: + + method_options %w( force -f ) => :boolean + +Or: + + method_option :force, :type => :boolean, :aliases => "-f" + +You can supply as many aliases as you want. + +NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead. + +== Namespaces + +By default, your Thor tasks are invoked using Ruby namespace. In the example +above, tasks are invoked as: + + thor app:install name --force + +However, you could namespace your class as: + + module Sinatra + class App < Thor + # tasks + end + end + +And then you should invoke your tasks as: + + thor sinatra:app:install name --force + +If desired, you can change the namespace: + + module Sinatra + class App < Thor + namespace :myapp + # tasks + end + end + +And then your tasks hould be invoked as: + + thor myapp:install name --force + +== Invocations + +Thor comes with a invocation-dependency system as well which allows a task to be +invoked only once. For example: + + class Counter < Thor + desc "one", "Prints 1, 2, 3" + def one + puts 1 + invoke :two + invoke :three + end + + desc "two", "Prints 2, 3" + def two + puts 2 + invoke :three + end + + desc "three", "Prints 3" + def three + puts 3 + end + end + +When invoking the task one: + + thor counter:one + +The output is "1 2 3", which means that the three task was invoked only once. +You can even invoke tasks from another class, so be sure to check the +documentation. + +== Thor::Group + +Thor has a special class called Thor::Group. The main difference to Thor class +is that it invokes all tasks at once. The example above could be rewritten in +Thor::Group as this: + + class Counter < Thor::Group + desc "Prints 1, 2, 3" + + def one + puts 1 + end + + def two + puts 2 + end + + def three + puts 3 + end + end + +When invoked: + + thor counter + +It prints "1 2 3" as well. Notice you should describe (using the method desc) +only the class and not each task anymore. Thor::Group is a great tool to create +generators, since you can define several steps which are invoked in the order they +are defined (Thor::Group is the tool use in generators in Rails 3.0). + +Besides, Thor::Group can parse arguments and options as Thor tasks: + + class Counter < Thor::Group + # number will be available as attr_accessor + argument :number, :type => :numeric, :desc => "The number to start counting" + desc "Prints the 'number' given upto 'number+2'" + + def one + puts number + 0 + end + + def two + puts number + 1 + end + + def three + puts number + 2 + end + end + +The counter above expects one parameter and has the folling outputs: + + thor counter 5 + # Prints "5 6 7" + + thor counter 11 + # Prints "11 12 13" + +You can also give options to Thor::Group, but instead of using method_option +and method_options, you should use class_option and class_options. +Both argument and class_options methods are available to Thor class as well. + +== Actions + +Thor comes with several actions which helps with script and generator tasks. You +might be familiar with them since some came from Rails Templates. They are: +say, ask, yes?, no?, add_file, +remove_file, copy_file, template, directory, +inside, run, inject_into_file and a couple more. + +To use them, you just need to include Thor::Actions in your Thor classes: + + class App < Thor + include Thor::Actions + # tasks + end + +Some actions like copy file requires that a class method called source_root is +defined in your class. This is the directory where your templates should be +placed. Be sure to check the documentation. + +== License + +See MIT LICENSE. diff --git a/railties/lib/vendor/thor-0.11.5/bin/rake2thor b/railties/lib/vendor/thor-0.11.5/bin/rake2thor new file mode 100755 index 0000000000..50c7410d80 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/bin/rake2thor @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'ruby2ruby' +require 'parse_tree' +if Ruby2Ruby::VERSION >= "1.2.0" + require 'parse_tree_extensions' +end +require 'rake' + +input = ARGV[0] || 'Rakefile' +output = ARGV[1] || 'Thorfile' + +$requires = [] + +module Kernel + def require_with_record(file) + $requires << file if caller[1] =~ /rake2thor:/ + require_without_record file + end + alias_method :require_without_record, :require + alias_method :require, :require_with_record +end + +load input + +@private_methods = [] + +def file_task_name(name) + "compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_') +end + +def method_for_task(task) + file_task = task.is_a?(Rake::FileTask) + comment = task.instance_variable_get('@comment') + prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?)) + actions = task.instance_variable_get('@actions') + name = task.name.gsub(/^([^:]+:)+/, '') + name = file_task_name(name) if file_task + meth = '' + + meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment + meth << "def #{name}\n" + + meth << prereqs.map do |pre| + pre = pre.to_s + pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask) + ' ' + pre + end.join("\n") + + meth << "\n\n" unless prereqs.empty? || actions.empty? + + meth << actions.map do |act| + act = act.to_ruby + unless act.gsub!(/^proc \{ \|(\w+)\|\n/, + " \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n") + act.gsub!(/^proc \{\n/, '') + end + act.gsub(/\n\}$/, '') + end.join("\n") + + meth << "\nend" + + if file_task + @private_methods << meth + return + end + + meth +end + +body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") + +unless @private_methods.empty? + body << "\n\n private\n\n" + body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") +end + +requires = $requires.map { |r| "require #{r.inspect}" }.join("\n") + +File.open(output, 'w') { |f| f.write(<:: name of the defaut task + # + def default_task(meth=nil) + case meth + when :none + @default_task = 'help' + when nil + @default_task ||= from_superclass(:default_task, 'help') + else + @default_task = meth.to_s + end + end + + # Defines the usage and the description of the next task. + # + # ==== Parameters + # usage + # description + # + def desc(usage, description, options={}) + if options[:for] + task = find_and_refresh_task(options[:for]) + task.usage = usage if usage + task.description = description if description + else + @usage, @desc = usage, description + end + end + + # Maps an input to a task. If you define: + # + # map "-T" => "list" + # + # Running: + # + # thor -T + # + # Will invoke the list task. + # + # ==== Parameters + # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task. + # + def map(mappings=nil) + @map ||= from_superclass(:map, {}) + + if mappings + mappings.each do |key, value| + if key.respond_to?(:each) + key.each {|subkey| @map[subkey] = value} + else + @map[key] = value + end + end + end + + @map + end + + # Declares the options for the next task to be declared. + # + # ==== Parameters + # Hash[Symbol => Object]:: The hash key is the name of the option and the value + # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric + # or :required (string). If you give a value, the type of the value is used. + # + def method_options(options=nil) + @method_options ||= {} + build_options(options, @method_options) if options + @method_options + end + + # Adds an option to the set of class options. If :for is given as option, + # it allows you to change the options from a previous defined task. + # + # def previous_task + # # magic + # end + # + # method_options :foo => :bar, :for => :previous_task + # + # def next_task + # # magic + # end + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described below. + # + # ==== Options + # :desc - Description for the argument. + # :required - If the argument is required or not. + # :default - Default value for this argument. It cannot be required and have default values. + # :aliases - Aliases for this option. + # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean. + # :group - The group for this options. Use by class options to output options in different levels. + # :banner - String to show on usage notes. + # + def method_option(name, options={}) + scope = if options[:for] + find_and_refresh_task(options[:for]).options + else + method_options + end + + build_option(name, options, scope) + end + + # Parses the task and options from the given args, instantiate the class + # and invoke the task. This method is used when the arguments must be parsed + # from an array. If you are inside Ruby and want to use a Thor class, you + # can simply initialize it: + # + # script = MyScript.new(args, options, config) + # script.invoke(:task, first_arg, second_arg, third_arg) + # + def start(given_args=ARGV, config={}) + super do + meth = normalize_task_name(given_args.shift) + task = all_tasks[meth] + + if task + args, opts = Thor::Options.split(given_args) + config.merge!(:task_options => task.options) + else + args, opts = given_args, {} + end + + task ||= Task.dynamic(meth) + trailing = args[Range.new(arguments.size, -1)] + new(args, opts, config).invoke(task, trailing || []) + end + end + + # Prints help information. If a task name is given, it shows information + # only about the specific task. + # + # ==== Parameters + # meth:: An optional task name to print usage information about. + # + # ==== Options + # namespace:: When true, shows the namespace in the output before the usage. + # skip_inherited:: When true, does not show tasks from superclass. + # + def help(shell, meth=nil, options={}) + meth, options = nil, meth if meth.is_a?(Hash) + + if meth + task = all_tasks[meth] + raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task + + shell.say "Usage:" + shell.say " #{banner(task, options[:namespace], false)}" + shell.say + class_options_help(shell, "Class", :Method => task.options.map { |_, o| o }) + shell.say task.description + else + list = (options[:short] ? tasks : all_tasks).map do |_, task| + item = [ banner(task, options[:namespace]) ] + item << "# #{task.short_description}" if task.short_description + item << " " + end + + options[:ident] ||= 2 + if options[:short] + shell.print_list(list, :ident => options[:ident]) + else + shell.say "Tasks:" + shell.print_list(list, :ident => options[:ident]) + end + + Thor::Util.thor_classes_in(self).each do |subclass| + namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '') + subclass.help(shell, options.merge(:short => true, :namespace => namespace)) + end + + class_options_help(shell, "Class") unless options[:short] + end + end + + protected + + # The banner for this class. You can customize it if you are invoking the + # thor class by another ways which is not the Thor::Runner. It receives + # the task that is going to be invoked and a boolean which indicates if + # the namespace should be displayed as arguments. + # + def banner(task, namespace=true, show_options=true) + task.formatted_usage(self, namespace, show_options) + end + + def baseclass #:nodoc: + Thor + end + + def create_task(meth) #:nodoc: + if @usage && @desc + tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options) + @usage, @desc, @method_options = nil + true + elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing + true + else + puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " << + "Call desc if you want this method to be available as task or declare it inside a " << + "no_tasks{} block. Invoked from #{caller[1].inspect}." + false + end + end + + def initialize_added #:nodoc: + class_options.merge!(method_options) + @method_options = nil + end + + # Receives a task name (can be nil), and try to get a map from it. + # If a map can't be found use the sent name or the default task. + # + def normalize_task_name(meth) #:nodoc: + mapping = map[meth.to_s] + meth = mapping || meth || default_task + meth.to_s.gsub('-','_') # treat foo-bar > foo_bar + end + end + + include Thor::Base + + map HELP_MAPPINGS => :help + + desc "help [TASK]", "Describe available tasks or one specific task" + def help(task=nil) + self.class.help(shell, task, :namespace => task && task.include?(?:)) + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb new file mode 100644 index 0000000000..1d09dc38ae --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb @@ -0,0 +1,272 @@ +require 'fileutils' + +Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action| + require action +end + +class Thor + module Actions + attr_accessor :behavior + + # On inclusion, add some options to base. + # + def self.included(base) #:nodoc: + base.extend ClassMethods + return unless base.respond_to?(:class_option) + + base.class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, + :desc => "Run but do not make any changes" + + base.class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, + :desc => "Overwrite files that already exist" + + base.class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, + :desc => "Skip files that already exist" + + base.class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, + :desc => "Supress status output" + end + + module ClassMethods + # Hold source paths for one Thor instance. source_paths_for_search is the + # method responsible to gather source_paths from this current class, + # inherited paths and the source root. + # + def source_paths + @source_paths ||= [] + end + + # Returns the source paths in the following order: + # + # 1) This class source paths + # 2) Source root + # 3) Parents source paths + # + def source_paths_for_search + paths = [] + paths += self.source_paths + paths << self.source_root if self.respond_to?(:source_root) + paths += from_superclass(:source_paths, []) + paths + end + end + + # Extends initializer to add more configuration options. + # + # ==== Configuration + # behavior:: The actions default behavior. Can be :invoke or :revoke. + # It also accepts :force, :skip and :pretend to set the behavior + # and the respective option. + # + # destination_root:: The root directory needed for some actions. + # + def initialize(args=[], options={}, config={}) + self.behavior = case config[:behavior].to_s + when "force", "skip" + _cleanup_options_and_set(options, config[:behavior]) + :invoke + when "revoke" + :revoke + else + :invoke + end + + super + self.destination_root = config[:destination_root] + end + + # Wraps an action object and call it accordingly to the thor class behavior. + # + def action(instance) #:nodoc: + if behavior == :revoke + instance.revoke! + else + instance.invoke! + end + end + + # Returns the root for this thor class (also aliased as destination root). + # + def destination_root + @destination_stack.last + end + + # Sets the root for this thor class. Relatives path are added to the + # directory where the script was invoked and expanded. + # + def destination_root=(root) + @destination_stack ||= [] + @destination_stack[0] = File.expand_path(root || '') + end + + # Returns the given path relative to the absolute root (ie, root where + # the script started). + # + def relative_to_original_destination_root(path, remove_dot=true) + path = path.gsub(@destination_stack[0], '.') + remove_dot ? (path[2..-1] || '') : path + end + + # Holds source paths in instance so they can be manipulated. + # + def source_paths + @source_paths ||= self.class.source_paths_for_search + end + + # Receives a file or directory and search for it in the source paths. + # + def find_in_source_paths(file) + relative_root = relative_to_original_destination_root(destination_root, false) + + source_paths.each do |source| + source_file = File.expand_path(file, File.join(source, relative_root)) + return source_file if File.exists?(source_file) + end + + if source_paths.empty? + raise Error, "You don't have any source path defined for class #{self.class.name}. To fix this, " << + "you can define a source_root in your class." + else + raise Error, "Could not find #{file.inspect} in source paths." + end + end + + # Do something in the root or on a provided subfolder. If a relative path + # is given it's referenced from the current root. The full path is yielded + # to the block you provide. The path is set back to the previous path when + # the method exits. + # + # ==== Parameters + # dir:: the directory to move to. + # config:: give :verbose => true to log and use padding. + # + def inside(dir='', config={}, &block) + verbose = config.fetch(:verbose, false) + + say_status :inside, dir, verbose + shell.padding += 1 if verbose + @destination_stack.push File.expand_path(dir, destination_root) + + FileUtils.mkdir_p(destination_root) unless File.exist?(destination_root) + FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } + + @destination_stack.pop + shell.padding -= 1 if verbose + end + + # Goes to the root and execute the given block. + # + def in_root + inside(@destination_stack.first) { yield } + end + + # Loads an external file and execute it in the instance binding. + # + # ==== Parameters + # path:: The path to the file to execute. Can be a web address or + # a relative path from the source root. + # + # ==== Examples + # + # apply "http://gist.github.com/103208" + # + # apply "recipes/jquery.rb" + # + def apply(path, config={}) + verbose = config.fetch(:verbose, true) + path = find_in_source_paths(path) unless path =~ /^http\:\/\// + + say_status :apply, path, verbose + shell.padding += 1 if verbose + instance_eval(open(path).read) + shell.padding -= 1 if verbose + end + + # Executes a command. + # + # ==== Parameters + # command:: the command to be executed. + # config:: give :verbose => false to not log the status. Specify :with + # to append an executable to command executation. + # + # ==== Example + # + # inside('vendor') do + # run('ln -s ~/edge rails') + # end + # + def run(command, config={}) + return unless behavior == :invoke + + destination = relative_to_original_destination_root(destination_root, false) + desc = "#{command} from #{destination.inspect}" + + if config[:with] + desc = "#{File.basename(config[:with].to_s)} #{desc}" + command = "#{config[:with]} #{command}" + end + + say_status :run, desc, config.fetch(:verbose, true) + system(command) unless options[:pretend] + end + + # Executes a ruby script (taking into account WIN32 platform quirks). + # + # ==== Parameters + # command:: the command to be executed. + # config:: give :verbose => false to not log the status. + # + def run_ruby_script(command, config={}) + return unless behavior == :invoke + run "#{command}", config.merge(:with => Thor::Util.ruby_command) + end + + # Run a thor command. A hash of options can be given and it's converted to + # switches. + # + # ==== Parameters + # task:: the task to be invoked + # args:: arguments to the task + # config:: give :verbose => false to not log the status. Other options + # are given as parameter to Thor. + # + # ==== Examples + # + # thor :install, "http://gist.github.com/103208" + # #=> thor install http://gist.github.com/103208 + # + # thor :list, :all => true, :substring => 'rails' + # #=> thor list --all --substring=rails + # + def thor(task, *args) + config = args.last.is_a?(Hash) ? args.pop : {} + verbose = config.key?(:verbose) ? config.delete(:verbose) : true + + args.unshift task + args.push Thor::Options.to_switches(config) + command = args.join(' ').strip + + run command, :with => :thor, :verbose => verbose + end + + protected + + # Allow current root to be shared between invocations. + # + def _shared_configuration #:nodoc: + super.merge!(:destination_root => self.destination_root) + end + + def _cleanup_options_and_set(options, key) #:nodoc: + case options + when Array + %w(--force -f --skip -s).each { |i| options.delete(i) } + options << "--#{key}" + when Hash + [:force, :skip, "force", "skip"].each { |i| options.delete(i) } + options.merge!(key => true) + end + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb new file mode 100644 index 0000000000..8f6badee27 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb @@ -0,0 +1,102 @@ +require 'thor/actions/empty_directory' + +class Thor + module Actions + + # Create a new file relative to the destination root with the given data, + # which is the return value of a block or a data string. + # + # ==== Parameters + # destination:: the relative path to the destination root. + # data:: the data to append to the file. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # create_file "lib/fun_party.rb" do + # hostname = ask("What is the virtual hostname I should use?") + # "vhost.name = #{hostname}" + # end + # + # create_file "config/apach.conf", "your apache config" + # + def create_file(destination, data=nil, config={}, &block) + action CreateFile.new(self, destination, block || data.to_s, config) + end + alias :add_file :create_file + + # AddFile is a subset of Template, which instead of rendering a file with + # ERB, it gets the content from the user. + # + class CreateFile < EmptyDirectory #:nodoc: + attr_reader :data + + def initialize(base, destination, data, config={}) + @data = data + super(base, destination, config) + end + + # Checks if the content of the file at the destination is identical to the rendered result. + # + # ==== Returns + # Boolean:: true if it is identical, false otherwise. + # + def identical? + exists? && File.read(destination) == render + end + + # Holds the content to be added to the file. + # + def render + @render ||= if data.is_a?(Proc) + data.call + else + data + end + end + + def invoke! + invoke_with_conflict_check do + FileUtils.mkdir_p(File.dirname(destination)) + File.open(destination, 'w'){ |f| f.write render } + end + end + + protected + + # Now on conflict we check if the file is identical or not. + # + def on_conflict_behavior(&block) + if identical? + say_status :identical, :blue + else + options = base.options.merge(config) + force_or_skip_or_conflict(options[:force], options[:skip], &block) + end + end + + # If force is true, run the action, otherwise check if it's not being + # skipped. If both are false, show the file_collision menu, if the menu + # returns true, force it, otherwise skip. + # + def force_or_skip_or_conflict(force, skip, &block) + if force + say_status :force, :yellow + block.call unless pretend? + elsif skip + say_status :skip, :yellow + else + say_status :conflict, :red + force_or_skip_or_conflict(force_on_collision?, true, &block) + end + end + + # Shows the file collision menu to the user and gets the result. + # + def force_on_collision? + base.shell.file_collision(destination){ render } + end + + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb new file mode 100644 index 0000000000..be5eb822ac --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb @@ -0,0 +1,87 @@ +require 'thor/actions/empty_directory' + +class Thor + module Actions + + # Copies recursively the files from source directory to root directory. + # If any of the files finishes with .tt, it's considered to be a template + # and is placed in the destination without the extension .tt. If any + # empty directory is found, it's copied and all .empty_directory files are + # ignored. Remember that file paths can also be encoded, let's suppose a doc + # directory with the following files: + # + # doc/ + # components/.empty_directory + # README + # rdoc.rb.tt + # %app_name%.rb + # + # When invoked as: + # + # directory "doc" + # + # It will create a doc directory in the destination with the following + # files (assuming that the app_name is "blog"): + # + # doc/ + # components/ + # README + # rdoc.rb + # blog.rb + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # If :recursive => false, does not look for paths recursively. + # + # ==== Examples + # + # directory "doc" + # directory "doc", "docs", :recursive => false + # + def directory(source, destination=nil, config={}) + action Directory.new(self, source, destination || source, config) + end + + class Directory < EmptyDirectory #:nodoc: + attr_reader :source + + def initialize(base, source, destination=nil, config={}) + @source = File.expand_path(base.find_in_source_paths(source.to_s)) + super(base, destination, { :recursive => true }.merge(config)) + end + + def invoke! + base.empty_directory given_destination, config + execute! + end + + def revoke! + execute! + end + + protected + + def execute! + lookup = config[:recursive] ? File.join(source, '**') : source + lookup = File.join(lookup, '{*,.[a-z]*}') + + Dir[lookup].each do |file_source| + next if File.directory?(file_source) + file_destination = File.join(given_destination, file_source.gsub(source, '.')) + + case file_source + when /\.empty_directory$/ + base.empty_directory(File.dirname(file_destination), config) + when /\.tt$/ + base.template(file_source, file_destination[0..-4], config) + else + base.copy_file(file_source, file_destination, config) + end + end + end + + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb new file mode 100644 index 0000000000..03c1fe4af1 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb @@ -0,0 +1,133 @@ +class Thor + module Actions + + # Creates an empty directory. + # + # ==== Parameters + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # empty_directory "doc" + # + def empty_directory(destination, config={}) + action EmptyDirectory.new(self, destination, config) + end + + # Class which holds create directory logic. This is the base class for + # other actions like create_file and directory. + # + # This implementation is based in Templater actions, created by Jonas Nicklas + # and Michael S. Klishin under MIT LICENSE. + # + class EmptyDirectory #:nodoc: + attr_reader :base, :destination, :given_destination, :relative_destination, :config + + # Initializes given the source and destination. + # + # ==== Parameters + # base:: A Thor::Base instance + # source:: Relative path to the source of this file + # destination:: Relative path to the destination of this file + # config:: give :verbose => false to not log the status. + # + def initialize(base, destination, config={}) + @base, @config = base, { :verbose => true }.merge(config) + self.destination = destination + end + + # Checks if the destination file already exists. + # + # ==== Returns + # Boolean:: true if the file exists, false otherwise. + # + def exists? + ::File.exists?(destination) + end + + def invoke! + invoke_with_conflict_check do + ::FileUtils.mkdir_p(destination) + end + end + + def revoke! + say_status :remove, :red + ::FileUtils.rm_rf(destination) if !pretend? && exists? + end + + protected + + # Shortcut for pretend. + # + def pretend? + base.options[:pretend] + end + + # Sets the absolute destination value from a relative destination value. + # It also stores the given and relative destination. Let's suppose our + # script is being executed on "dest", it sets the destination root to + # "dest". The destination, given_destination and relative_destination + # are related in the following way: + # + # inside "bar" do + # empty_directory "baz" + # end + # + # destination #=> dest/bar/baz + # relative_destination #=> bar/baz + # given_destination #=> baz + # + def destination=(destination) + if destination + @given_destination = convert_encoded_instructions(destination.to_s) + @destination = ::File.expand_path(@given_destination, base.destination_root) + @relative_destination = base.relative_to_original_destination_root(@destination) + end + end + + # Filenames in the encoded form are converted. If you have a file: + # + # %class_name%.rb + # + # It gets the class name from the base and replace it: + # + # user.rb + # + def convert_encoded_instructions(filename) + filename.gsub(/%(.*?)%/) do |string| + instruction = $1.strip + base.respond_to?(instruction) ? base.send(instruction) : string + end + end + + # Receives a hash of options and just execute the block if some + # conditions are met. + # + def invoke_with_conflict_check(&block) + if exists? + on_conflict_behavior(&block) + else + say_status :create, :green + block.call unless pretend? + end + + destination + end + + # What to do when the destination file already exists. + # + def on_conflict_behavior(&block) + say_status :exist, :blue + end + + # Shortcut to say_status shell method. + # + def say_status(status, color) + base.shell.say_status status, relative_destination, color if config[:verbose] + end + + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb new file mode 100644 index 0000000000..74c157ba8c --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb @@ -0,0 +1,195 @@ +require 'erb' +require 'open-uri' + +class Thor + module Actions + + # Copies the file from the relative source to the relative destination. If + # the destination is not given it's assumed to be equal to the source. + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # copy_file "README", "doc/README" + # + # copy_file "doc/README" + # + def copy_file(source, destination=nil, config={}) + destination ||= source + source = File.expand_path(find_in_source_paths(source.to_s)) + + create_file destination, nil, config do + File.read(source) + end + end + + # Gets the content at the given address and places it at the given relative + # destination. If a block is given instead of destination, the content of + # the url is yielded and used as location. + # + # ==== Parameters + # source:: the address of the given content. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # get "http://gist.github.com/103208", "doc/README" + # + # get "http://gist.github.com/103208" do |content| + # content.split("\n").first + # end + # + def get(source, destination=nil, config={}, &block) + source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\// + render = open(source).read + + destination ||= if block_given? + block.arity == 1 ? block.call(render) : block.call + else + File.basename(source) + end + + create_file destination, render, config + end + + # Gets an ERB template at the relative source, executes it and makes a copy + # at the relative destination. If the destination is not given it's assumed + # to be equal to the source removing .tt from the filename. + # + # ==== Parameters + # source:: the relative path to the source root. + # destination:: the relative path to the destination root. + # config:: give :verbose => false to not log the status. + # + # ==== Examples + # + # template "README", "doc/README" + # + # template "doc/README" + # + def template(source, destination=nil, config={}) + destination ||= source + source = File.expand_path(find_in_source_paths(source.to_s)) + context = instance_eval('binding') + + create_file destination, nil, config do + ERB.new(::File.read(source), nil, '-').result(context) + end + end + + # Changes the mode of the given file or directory. + # + # ==== Parameters + # mode:: the file mode + # path:: the name of the file to change mode + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # chmod "script/*", 0755 + # + def chmod(path, mode, config={}) + return unless behavior == :invoke + path = File.expand_path(path, destination_root) + say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true) + FileUtils.chmod_R(mode, path) unless options[:pretend] + end + + # Prepend text to a file. + # + # ==== Parameters + # path:: path of the file to be changed + # data:: the data to prepend to the file, can be also given as a block. + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # prepend_file 'config/environments/test.rb', 'config.gem "rspec"' + # + def prepend_file(path, data=nil, config={}, &block) + return unless behavior == :invoke + path = File.expand_path(path, destination_root) + say_status :prepend, relative_to_original_destination_root(path), config.fetch(:verbose, true) + + unless options[:pretend] + content = data || block.call + content << File.read(path) + File.open(path, 'wb') { |file| file.write(content) } + end + end + + # Append text to a file. + # + # ==== Parameters + # path:: path of the file to be changed + # data:: the data to append to the file, can be also given as a block. + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # append_file 'config/environments/test.rb', 'config.gem "rspec"' + # + def append_file(path, data=nil, config={}, &block) + return unless behavior == :invoke + path = File.expand_path(path, destination_root) + say_status :append, relative_to_original_destination_root(path), config.fetch(:verbose, true) + File.open(path, 'ab') { |file| file.write(data || block.call) } unless options[:pretend] + end + + # Run a regular expression replacement on a file. + # + # ==== Parameters + # path:: path of the file to be changed + # flag:: the regexp or string to be replaced + # replacement:: the replacement, can be also given as a block + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' + # + # gsub_file 'README', /rake/, :green do |match| + # match << " no more. Use thor!" + # end + # + def gsub_file(path, flag, *args, &block) + return unless behavior == :invoke + config = args.last.is_a?(Hash) ? args.pop : {} + + path = File.expand_path(path, destination_root) + say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) + + unless options[:pretend] + content = File.read(path) + content.gsub!(flag, *args, &block) + File.open(path, 'wb') { |file| file.write(content) } + end + end + + # Removes a file at the given location. + # + # ==== Parameters + # path:: path of the file to be changed + # config:: give :verbose => false to not log the status. + # + # ==== Example + # + # remove_file 'README' + # remove_file 'app/controllers/application_controller.rb' + # + def remove_file(path, config={}) + return unless behavior == :invoke + path = File.expand_path(path, destination_root) + + say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true) + ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path) + end + alias :remove_dir :remove_file + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb new file mode 100644 index 0000000000..66dd1f5fc1 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb @@ -0,0 +1,78 @@ +require 'thor/actions/empty_directory' + +class Thor + module Actions + + # Injects the given content into a file. Different from append_file, + # prepend_file and gsub_file, this method is reversible. By this reason, + # the flag can only be strings. gsub_file is your friend if you need to + # deal with more complex cases. + # + # ==== Parameters + # destination:: Relative path to the destination root + # data:: Data to add to the file. Can be given as a block. + # config:: give :verbose => false to not log the status and the flag + # for injection (:after or :before). + # + # ==== Examples + # + # inject_into_file "config/environment.rb", "config.gem thor", :after => "Rails::Initializer.run do |config|\n" + # + # inject_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do + # gems = ask "Which gems would you like to add?" + # gems.split(" ").map{ |gem| " config.gem #{gem}" }.join("\n") + # end + # + def inject_into_file(destination, *args, &block) + if block_given? + data, config = block, args.shift + else + data, config = args.shift, args.shift + end + + log_status = args.empty? || args.pop + action InjectIntoFile.new(self, destination, data, config) + end + + class InjectIntoFile < EmptyDirectory #:nodoc: + attr_reader :flag, :replacement + + def initialize(base, destination, data, config) + super(base, destination, { :verbose => true }.merge(config)) + + data = data.call if data.is_a?(Proc) + + @replacement = if @config.key?(:after) + @flag = @config.delete(:after) + @flag + data + else + @flag = @config.delete(:before) + data + @flag + end + end + + def invoke! + say_status :inject, config[:verbose] + replace!(flag, replacement) + end + + def revoke! + say_status :deinject, config[:verbose] + replace!(replacement, flag) + end + + protected + + # Adds the content to the file. + # + def replace!(regexp, string) + unless base.options[:pretend] + content = File.read(destination) + content.gsub!(regexp, string) + File.open(destination, 'wb') { |file| file.write(content) } + end + end + + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb new file mode 100644 index 0000000000..0fa87f8162 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb @@ -0,0 +1,510 @@ +require 'thor/core_ext/hash_with_indifferent_access' +require 'thor/core_ext/ordered_hash' +require 'thor/error' +require 'thor/shell' +require 'thor/invocation' +require 'thor/parser' +require 'thor/task' +require 'thor/util' + +class Thor + # Shortcuts for help. + HELP_MAPPINGS = %w(-h -? --help -D) + + # Thor methods that should not be overwritten by the user. + THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root + action add_file create_file in_root inside run run_ruby_script) + + module Base + attr_accessor :options + + # It receives arguments in an Array and two hashes, one for options and + # other for configuration. + # + # Notice that it does not check if all required arguments were supplied. + # It should be done by the parser. + # + # ==== Parameters + # args:: An array of objects. The objects are applied to their + # respective accessors declared with argument. + # + # options:: An options hash that will be available as self.options. + # The hash given is converted to a hash with indifferent + # access, magic predicates (options.skip?) and then frozen. + # + # config:: Configuration for this Thor class. + # + def initialize(args=[], options={}, config={}) + Thor::Arguments.parse(self.class.arguments, args).each do |key, value| + send("#{key}=", value) + end + + parse_options = self.class.class_options + + if options.is_a?(Array) + task_options = config.delete(:task_options) # hook for start + parse_options = parse_options.merge(task_options) if task_options + array_options, hash_options = options, {} + else + array_options, hash_options = [], options + end + + options = Thor::Options.parse(parse_options, array_options) + self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).merge!(hash_options) + self.options.freeze + end + + class << self + def included(base) #:nodoc: + base.send :extend, ClassMethods + base.send :include, Invocation + base.send :include, Shell + end + + # Returns the classes that inherits from Thor or Thor::Group. + # + # ==== Returns + # Array[Class] + # + def subclasses + @subclasses ||= [] + end + + # Returns the files where the subclasses are kept. + # + # ==== Returns + # Hash[path => Class] + # + def subclass_files + @subclass_files ||= Hash.new{ |h,k| h[k] = [] } + end + + # Whenever a class inherits from Thor or Thor::Group, we should track the + # class and the file on Thor::Base. This is the method responsable for it. + # + def register_klass_file(klass) #:nodoc: + file = caller[1].match(/(.*):\d+/)[1] + Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass) + + file_subclasses = Thor::Base.subclass_files[File.expand_path(file)] + file_subclasses << klass unless file_subclasses.include?(klass) + end + end + + module ClassMethods + # Adds an argument to the class and creates an attr_accessor for it. + # + # Arguments are different from options in several aspects. The first one + # is how they are parsed from the command line, arguments are retrieved + # from position: + # + # thor task NAME + # + # Instead of: + # + # thor task --name=NAME + # + # Besides, arguments are used inside your code as an accessor (self.argument), + # while options are all kept in a hash (self.options). + # + # Finally, arguments cannot have type :default or :boolean but can be + # optional (supplying :optional => :true or :required => false), although + # you cannot have a required argument after a non-required argument. If you + # try it, an error is raised. + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described below. + # + # ==== Options + # :desc - Description for the argument. + # :required - If the argument is required or not. + # :optional - If the argument is optional or not. + # :type - The type of the argument, can be :string, :hash, :array, :numeric. + # :default - Default value for this argument. It cannot be required and have default values. + # :banner - String to show on usage notes. + # + # ==== Errors + # ArgumentError:: Raised if you supply a required argument after a non required one. + # + def argument(name, options={}) + is_thor_reserved_word?(name, :argument) + no_tasks { attr_accessor name } + + required = if options.key?(:optional) + !options[:optional] + elsif options.key?(:required) + options[:required] + else + options[:default].nil? + end + + remove_argument name + + arguments.each do |argument| + next if argument.required? + raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " << + "the non-required argument #{argument.human_name.inspect}." + end if required + + arguments << Thor::Argument.new(name, options[:desc], required, options[:type], + options[:default], options[:banner]) + end + + # Returns this class arguments, looking up in the ancestors chain. + # + # ==== Returns + # Array[Thor::Argument] + # + def arguments + @arguments ||= from_superclass(:arguments, []) + end + + # Adds a bunch of options to the set of class options. + # + # class_options :foo => false, :bar => :required, :baz => :string + # + # If you prefer more detailed declaration, check class_option. + # + # ==== Parameters + # Hash[Symbol => Object] + # + def class_options(options=nil) + @class_options ||= from_superclass(:class_options, {}) + build_options(options, @class_options) if options + @class_options + end + + # Adds an option to the set of class options + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described below. + # + # ==== Options + # :desc - Description for the argument. + # :required - If the argument is required or not. + # :default - Default value for this argument. + # :group - The group for this options. Use by class options to output options in different levels. + # :aliases - Aliases for this option. + # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean. + # :banner - String to show on usage notes. + # + def class_option(name, options={}) + build_option(name, options, class_options) + end + + # Removes a previous defined argument. If :undefine is given, undefine + # accessors as well. + # + # ==== Paremeters + # names:: Arguments to be removed + # + # ==== Examples + # + # remove_argument :foo + # remove_argument :foo, :bar, :baz, :undefine => true + # + def remove_argument(*names) + options = names.last.is_a?(Hash) ? names.pop : {} + + names.each do |name| + arguments.delete_if { |a| a.name == name.to_s } + undef_method name, "#{name}=" if options[:undefine] + end + end + + # Removes a previous defined class option. + # + # ==== Paremeters + # names:: Class options to be removed + # + # ==== Examples + # + # remove_class_option :foo + # remove_class_option :foo, :bar, :baz + # + def remove_class_option(*names) + names.each do |name| + class_options.delete(name) + end + end + + # Defines the group. This is used when thor list is invoked so you can specify + # that only tasks from a pre-defined group will be shown. Defaults to standard. + # + # ==== Parameters + # name + # + def group(name=nil) + case name + when nil + @group ||= from_superclass(:group, 'standard') + else + @group = name.to_s + end + end + + # Returns the tasks for this Thor class. + # + # ==== Returns + # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task + # objects as values. + # + def tasks + @tasks ||= Thor::CoreExt::OrderedHash.new + end + + # Returns the tasks for this Thor class and all subclasses. + # + # ==== Returns + # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task + # objects as values. + # + def all_tasks + @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new) + @all_tasks.merge(tasks) + end + + # Removes a given task from this Thor class. This is usually done if you + # are inheriting from another class and don't want it to be available + # anymore. + # + # By default it only remove the mapping to the task. But you can supply + # :undefine => true to undefine the method from the class as well. + # + # ==== Parameters + # name:: The name of the task to be removed + # options:: You can give :undefine => true if you want tasks the method + # to be undefined from the class as well. + # + def remove_task(*names) + options = names.last.is_a?(Hash) ? names.pop : {} + + names.each do |name| + tasks.delete(name.to_s) + all_tasks.delete(name.to_s) + undef_method name if options[:undefine] + end + end + + # All methods defined inside the given block are not added as tasks. + # + # So you can do: + # + # class MyScript < Thor + # no_tasks do + # def this_is_not_a_task + # end + # end + # end + # + # You can also add the method and remove it from the task list: + # + # class MyScript < Thor + # def this_is_not_a_task + # end + # remove_task :this_is_not_a_task + # end + # + def no_tasks + @no_tasks = true + yield + @no_tasks = false + end + + # Sets the namespace for the Thor or Thor::Group class. By default the + # namespace is retrieved from the class name. If your Thor class is named + # Scripts::MyScript, the help method, for example, will be called as: + # + # thor scripts:my_script -h + # + # If you change the namespace: + # + # namespace :my_scripts + # + # You change how your tasks are invoked: + # + # thor my_scripts -h + # + # Finally, if you change your namespace to default: + # + # namespace :default + # + # Your tasks can be invoked with a shortcut. Instead of: + # + # thor :my_task + # + def namespace(name=nil) + case name + when nil + @namespace ||= Thor::Util.namespace_from_thor_class(self, false) + else + @namespace = name.to_s + end + end + + # Default way to start generators from the command line. + # + def start(given_args=ARGV, config={}) + config[:shell] ||= Thor::Base.shell.new + yield + rescue Thor::Error => e + if given_args.include?("--debug") + raise e + else + config[:shell].error e.message + end + end + + protected + + # Prints the class options per group. If an option does not belong to + # any group, it uses the ungrouped name value. This method provide to + # hooks to add extra options, one of them if the third argument called + # extra_group that should be a hash in the format :group => Array[Options]. + # + # The second is by returning a lambda used to print values. The lambda + # requires two options: the group name and the array of options. + # + def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: + groups = {} + + class_options.each do |_, value| + groups[value.group] ||= [] + groups[value.group] << value + end + + printer = proc do |group_name, options| + list = [] + padding = options.collect{ |o| o.aliases.size }.max.to_i * 4 + + options.each do |option| + item = [ option.usage(padding) ] + item.push(option.description ? "# #{option.description}" : "") + + list << item + list << [ "", "# Default: #{option.default}" ] if option.show_default? + end + + unless list.empty? + shell.say(group_name ? "#{group_name} options:" : "Options:") + shell.print_table(list, :ident => 2) + shell.say "" + end + end + + # Deal with default group + global_options = groups.delete(nil) || [] + printer.call(ungrouped_name, global_options) if global_options + + # Print all others + groups = extra_group.merge(groups) if extra_group + groups.each(&printer) + printer + end + + # Raises an error if the word given is a Thor reserved word. + # + def is_thor_reserved_word?(word, type) #:nodoc: + return false unless THOR_RESERVED_WORDS.include?(word.to_s) + raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}" + end + + # Build an option and adds it to the given scope. + # + # ==== Parameters + # name:: The name of the argument. + # options:: Described in both class_option and method_option. + # + def build_option(name, options, scope) #:nodoc: + scope[name] = Thor::Option.new(name, options[:desc], options[:required], + options[:type], options[:default], options[:banner], + options[:group], options[:aliases]) + end + + # Receives a hash of options, parse them and add to the scope. This is a + # fast way to set a bunch of options: + # + # build_options :foo => true, :bar => :required, :baz => :string + # + # ==== Parameters + # Hash[Symbol => Object] + # + def build_options(options, scope) #:nodoc: + options.each do |key, value| + scope[key] = Thor::Option.parse(key, value) + end + end + + # Finds a task with the given name. If the task belongs to the current + # class, just return it, otherwise dup it and add the fresh copy to the + # current task hash. + # + def find_and_refresh_task(name) #:nodoc: + task = if task = tasks[name.to_s] + task + elsif task = all_tasks[name.to_s] + tasks[name.to_s] = task.clone + else + raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found." + end + end + + # Everytime someone inherits from a Thor class, register the klass + # and file into baseclass. + # + def inherited(klass) + Thor::Base.register_klass_file(klass) + end + + # Fire this callback whenever a method is added. Added methods are + # tracked as tasks by invoking the create_task method. + # + def method_added(meth) + meth = meth.to_s + + if meth == "initialize" + initialize_added + return + end + + # Return if it's not a public instance method + return unless public_instance_methods.include?(meth) || + public_instance_methods.include?(meth.to_sym) + + return if @no_tasks || !create_task(meth) + + is_thor_reserved_word?(meth, :task) + Thor::Base.register_klass_file(self) + end + + # Retrieves a value from superclass. If it reaches the baseclass, + # returns default. + # + def from_superclass(method, default=nil) + if self == baseclass || !superclass.respond_to?(method, true) + default + else + value = superclass.send(method) + value.dup if value + end + end + + # SIGNATURE: Sets the baseclass. This is where the superclass lookup + # finishes. + def baseclass #:nodoc: + end + + # SIGNATURE: Creates a new task if valid_task? is true. This method is + # called when a new method is added to the class. + def create_task(meth) #:nodoc: + end + + # SIGNATURE: Defines behavior when the initialize method is added to the + # class. + def initialize_added #:nodoc: + end + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb new file mode 100644 index 0000000000..78bc5cf4bf --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -0,0 +1,75 @@ +class Thor + module CoreExt #:nodoc: + + # A hash with indifferent access and magic predicates. + # + # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true + # + # hash[:foo] #=> 'bar' + # hash['foo'] #=> 'bar' + # hash.foo? #=> true + # + class HashWithIndifferentAccess < ::Hash #:nodoc: + + def initialize(hash={}) + super() + hash.each do |key, value| + self[convert_key(key)] = value + end + end + + def [](key) + super(convert_key(key)) + end + + def []=(key, value) + super(convert_key(key), value) + end + + def delete(key) + super(convert_key(key)) + end + + def values_at(*indices) + indices.collect { |key| self[convert_key(key)] } + end + + def merge(other) + dup.merge!(other) + end + + def merge!(other) + other.each do |key, value| + self[convert_key(key)] = value + end + self + end + + protected + + def convert_key(key) + key.is_a?(Symbol) ? key.to_s : key + end + + # Magic predicates. For instance: + # + # options.force? # => !!options['force'] + # options.shebang # => "/usr/lib/local/ruby" + # options.test_framework?(:rspec) # => options[:test_framework] == :rspec + # + def method_missing(method, *args, &block) + method = method.to_s + if method =~ /^(\w+)\?$/ + if args.empty? + !!self[$1] + else + self[$1] == args.first + end + else + self[method] + end + end + + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb new file mode 100644 index 0000000000..27fea5bb35 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb @@ -0,0 +1,100 @@ +class Thor + module CoreExt #:nodoc: + + if RUBY_VERSION >= '1.9' + class OrderedHash < ::Hash + end + else + # This class is based on the Ruby 1.9 ordered hashes. + # + # It keeps the semantics and most of the efficiency of normal hashes + # while also keeping track of the order in which elements were set. + # + class OrderedHash #:nodoc: + include Enumerable + + Node = Struct.new(:key, :value, :next, :prev) + + def initialize + @hash = {} + end + + def [](key) + @hash[key] && @hash[key].value + end + + def []=(key, value) + if node = @hash[key] + node.value = value + else + node = Node.new(key, value) + + if @first.nil? + @first = @last = node + else + node.prev = @last + @last.next = node + @last = node + end + end + + @hash[key] = node + value + end + + def delete(key) + if node = @hash[key] + prev_node = node.prev + next_node = node.next + + next_node.prev = prev_node if next_node + prev_node.next = next_node if prev_node + + @first = next_node if @first == node + @last = prev_node if @last == node + + value = node.value + end + + @hash.delete(key) + value + end + + def keys + self.map { |k, v| k } + end + + def values + self.map { |k, v| v } + end + + def each + return unless @first + yield [@first.key, @first.value] + node = @first + yield [node.key, node.value] while node = node.next + self + end + + def merge(other) + hash = self.class.new + + self.each do |key, value| + hash[key] = value + end + + other.each do |key, value| + hash[key] = value + end + + hash + end + + def empty? + @hash.empty? + end + end + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb new file mode 100644 index 0000000000..f9b31a35d1 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb @@ -0,0 +1,27 @@ +class Thor + # Thor::Error is raised when it's caused by wrong usage of thor classes. Those + # errors have their backtrace supressed and are nicely shown to the user. + # + # Errors that are caused by the developer, like declaring a method which + # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we + # ensure that developer errors are shown with full backtrace. + # + class Error < StandardError + end + + # Raised when a task was not found. + # + class UndefinedTaskError < Error + end + + # Raised when a task was found, but not invoked properly. + # + class InvocationError < Error + end + + class RequiredArgumentMissingError < InvocationError + end + + class MalformattedArgumentError < InvocationError + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb new file mode 100644 index 0000000000..1e59df2313 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb @@ -0,0 +1,263 @@ +# Thor has a special class called Thor::Group. The main difference to Thor class +# is that it invokes all tasks at once. It also include some methods that allows +# invocations to be done at the class method, which are not available to Thor +# tasks. +# +class Thor::Group + class << self + # The descrition for this Thor::Group. If none is provided, but a source root + # exists, tries to find the USAGE one folder above it, otherwise searches + # in the superclass. + # + # ==== Parameters + # description:: The description for this Thor::Group. + # + def desc(description=nil) + case description + when nil + @desc ||= from_superclass(:desc, nil) + else + @desc = description + end + end + + # Start works differently in Thor::Group, it simply invokes all tasks + # inside the class. + # + def start(given_args=ARGV, config={}) + super do + if Thor::HELP_MAPPINGS.include?(given_args.first) + help(config[:shell]) + return + end + + args, opts = Thor::Options.split(given_args) + new(args, opts, config).invoke + end + end + + # Prints help information. + # + # ==== Options + # short:: When true, shows only usage. + # + def help(shell, options={}) + if options[:short] + shell.say banner + else + shell.say "Usage:" + shell.say " #{banner}" + shell.say + class_options_help(shell) + shell.say self.desc if self.desc + end + end + + # Stores invocations for this class merging with superclass values. + # + def invocations #:nodoc: + @invocations ||= from_superclass(:invocations, {}) + end + + # Stores invocation blocks used on invoke_from_option. + # + def invocation_blocks #:nodoc: + @invocation_blocks ||= from_superclass(:invocation_blocks, {}) + end + + # Invoke the given namespace or class given. It adds an instance + # method that will invoke the klass and task. You can give a block to + # configure how it will be invoked. + # + # The namespace/class given will have its options showed on the help + # usage. Check invoke_from_option for more information. + # + def invoke(*names, &block) + options = names.last.is_a?(Hash) ? names.pop : {} + verbose = options.fetch(:verbose, :white) + + names.each do |name| + invocations[name] = false + invocation_blocks[name] = block if block_given? + + class_eval <<-METHOD, __FILE__, __LINE__ + def _invoke_#{name.to_s.gsub(/\W/, '_')} + klass, task = self.class.prepare_for_invocation(nil, #{name.inspect}) + + if klass + say_status :invoke, #{name.inspect}, #{verbose.inspect} + block = self.class.invocation_blocks[#{name.inspect}] + _invoke_for_class_method klass, task, &block + else + say_status :error, %(#{name.inspect} [not found]), :red + end + end + METHOD + end + end + + # Invoke a thor class based on the value supplied by the user to the + # given option named "name". A class option must be created before this + # method is invoked for each name given. + # + # ==== Examples + # + # class GemGenerator < Thor::Group + # class_option :test_framework, :type => :string + # invoke_from_option :test_framework + # end + # + # ==== Boolean options + # + # In some cases, you want to invoke a thor class if some option is true or + # false. This is automatically handled by invoke_from_option. Then the + # option name is used to invoke the generator. + # + # ==== Preparing for invocation + # + # In some cases you want to customize how a specified hook is going to be + # invoked. You can do that by overwriting the class method + # prepare_for_invocation. The class method must necessarily return a klass + # and an optional task. + # + # ==== Custom invocations + # + # You can also supply a block to customize how the option is giong to be + # invoked. The block receives two parameters, an instance of the current + # class and the klass to be invoked. + # + def invoke_from_option(*names, &block) + options = names.last.is_a?(Hash) ? names.pop : {} + verbose = options.fetch(:verbose, :white) + + names.each do |name| + unless class_options.key?(name) + raise ArgumentError, "You have to define the option #{name.inspect} " << + "before setting invoke_from_option." + end + + invocations[name] = true + invocation_blocks[name] = block if block_given? + + class_eval <<-METHOD, __FILE__, __LINE__ + def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} + return unless options[#{name.inspect}] + + value = options[#{name.inspect}] + value = #{name.inspect} if TrueClass === value + klass, task = self.class.prepare_for_invocation(#{name.inspect}, value) + + if klass + say_status :invoke, value, #{verbose.inspect} + block = self.class.invocation_blocks[#{name.inspect}] + _invoke_for_class_method klass, task, &block + else + say_status :error, %(\#{value} [not found]), :red + end + end + METHOD + end + end + + # Remove a previously added invocation. + # + # ==== Examples + # + # remove_invocation :test_framework + # + def remove_invocation(*names) + names.each do |name| + remove_task(name) + remove_class_option(name) + invocations.delete(name) + invocation_blocks.delete(name) + end + end + + # Overwrite class options help to allow invoked generators options to be + # shown recursively when invoking a generator. + # + def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: + group_options = {} + + get_options_from_invocations(group_options, class_options) do |klass| + klass.send(:get_options_from_invocations, group_options, class_options) + end + + group_options.merge!(extra_group) if extra_group + super(shell, ungrouped_name, group_options) + end + + # Get invocations array and merge options from invocations. Those + # options are added to group_options hash. Options that already exists + # in base_options are not added twice. + # + def get_options_from_invocations(group_options, base_options) #:nodoc: + invocations.each do |name, from_option| + value = if from_option + option = class_options[name] + option.type == :boolean ? name : option.default + else + name + end + next unless value + + klass, task = prepare_for_invocation(name, value) + next unless klass && klass.respond_to?(:class_options) + + value = value.to_s + human_name = value.respond_to?(:classify) ? value.classify : value + + group_options[human_name] ||= [] + group_options[human_name] += klass.class_options.values.select do |option| + base_options[option.name.to_sym].nil? && option.group.nil? && + !group_options.values.flatten.any? { |i| i.name == option.name } + end + + yield klass if block_given? + end + end + + protected + + # The banner for this class. You can customize it if you are invoking the + # thor class by another ways which is not the Thor::Runner. + # + def banner + "#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}" + end + + def baseclass #:nodoc: + Thor::Group + end + + def create_task(meth) #:nodoc: + tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil) + true + end + end + + include Thor::Base + + protected + + # Shortcut to invoke with padding and block handling. Use internally by + # invoke and invoke_from_option class methods. + # + def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc: + shell.padding += 1 + + result = if block_given? + if block.arity == 2 + block.call(self, klass) + else + block.call(self, klass, task) + end + else + invoke klass, task, *args + end + + shell.padding -= 1 + result + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb new file mode 100644 index 0000000000..c0388dd863 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb @@ -0,0 +1,178 @@ +class Thor + module Invocation + def self.included(base) #:nodoc: + base.extend ClassMethods + end + + module ClassMethods + # Prepare for class methods invocations. This method must return a klass to + # have the invoked class options showed in help messages in generators. + # + def prepare_for_invocation(key, name) #:nodoc: + case name + when Symbol, String + Thor::Util.namespace_to_thor_class_and_task(name.to_s, false) + else + name + end + end + end + + # Make initializer aware of invocations and the initializer proc. + # + def initialize(args=[], options={}, config={}, &block) #:nodoc: + @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] } + @_initializer = [ args, options, config ] + super + end + + # Receives a name and invokes it. The name can be a string (either "task" or + # "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task + # cannot be guessed by name, it can also be supplied as second argument. + # + # You can also supply the arguments, options and configuration values for + # the task to be invoked, if none is given, the same values used to + # initialize the invoker are used to initialize the invoked. + # + # ==== Examples + # + # class A < Thor + # def foo + # invoke :bar + # invoke "b:hello", ["José"] + # end + # + # def bar + # invoke "b:hello", ["José"] + # end + # end + # + # class B < Thor + # def hello(name) + # puts "hello #{name}" + # end + # end + # + # You can notice that the method "foo" above invokes two tasks: "bar", + # which belongs to the same class and "hello" which belongs to the class B. + # + # By using an invocation system you ensure that a task is invoked only once. + # In the example above, invoking "foo" will invoke "b:hello" just once, even + # if it's invoked later by "bar" method. + # + # When class A invokes class B, all arguments used on A initialization are + # supplied to B. This allows lazy parse of options. Let's suppose you have + # some rspec tasks: + # + # class Rspec < Thor::Group + # class_option :mock_framework, :type => :string, :default => :rr + # + # def invoke_mock_framework + # invoke "rspec:#{options[:mock_framework]}" + # end + # end + # + # As you noticed, it invokes the given mock framework, which might have its + # own options: + # + # class Rspec::RR < Thor::Group + # class_option :style, :type => :string, :default => :mock + # end + # + # Since it's not rspec concern to parse mock framework options, when RR + # is invoked all options are parsed again, so RR can extract only the options + # that it's going to use. + # + # If you want Rspec::RR to be initialized with its own set of options, you + # have to do that explicitely: + # + # invoke "rspec:rr", [], :style => :foo + # + # Besides giving an instance, you can also give a class to invoke: + # + # invoke Rspec::RR, [], :style => :foo + # + def invoke(name=nil, task=nil, args=nil, opts=nil, config=nil) + task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array) + args, opts, config = nil, args, opts if args.is_a?(Hash) + + object, task = _prepare_for_invocation(name, task) + klass, instance = _initialize_klass_with_initializer(object, args, opts, config) + + method_args = [] + current = @_invocations[klass] + + iterator = proc do |_, task| + unless current.include?(task.name) + current << task.name + task.run(instance, method_args) + end + end + + if task + args ||= [] + method_args = args[Range.new(klass.arguments.size, -1)] || [] + iterator.call(nil, task) + else + klass.all_tasks.map(&iterator) + end + end + + protected + + # Configuration values that are shared between invocations. + # + def _shared_configuration #:nodoc: + { :invocations => @_invocations } + end + + # Prepare for invocation in the instance level. In this case, we have to + # take into account that a just a task name from the current class was + # given or even a Thor::Task object. + # + def _prepare_for_invocation(name, sent_task=nil) #:nodoc: + if name.is_a?(Thor::Task) + task = name + elsif task = self.class.all_tasks[name.to_s] + object = self + else + object, task = self.class.prepare_for_invocation(nil, name) + task ||= sent_task + end + + # If the object was not set, use self and use the name as task. + object, task = self, name unless object + return object, _validate_task(object, task) + end + + # Check if the object given is a Thor class object and get a task object + # for it. + # + def _validate_task(object, task) #:nodoc: + klass = object.is_a?(Class) ? object : object.class + raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base + + task ||= klass.default_task if klass <= Thor + task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task) + task + end + + # Initialize klass using values stored in the @_initializer. + # + def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc: + if object.is_a?(Class) + klass = object + + stored_args, stored_opts, stored_config = @_initializer + args ||= stored_args.dup + opts ||= stored_opts.dup + + config ||= {} + config = stored_config.merge(_shared_configuration).merge!(config) + [ klass, klass.new(args, opts, config) ] + else + [ object.class, object ] + end + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb new file mode 100644 index 0000000000..57a3f6e1a5 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb @@ -0,0 +1,4 @@ +require 'thor/parser/argument' +require 'thor/parser/arguments' +require 'thor/parser/option' +require 'thor/parser/options' diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb new file mode 100644 index 0000000000..aa8ace4719 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb @@ -0,0 +1,67 @@ +class Thor + class Argument #:nodoc: + VALID_TYPES = [ :numeric, :hash, :array, :string ] + + attr_reader :name, :description, :required, :type, :default, :banner + alias :human_name :name + + def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil) + class_name = self.class.name.split("::").last + + raise ArgumentError, "#{class_name} name can't be nil." if name.nil? + raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) + + @name = name.to_s + @description = description + @required = required || false + @type = (type || :string).to_sym + @default = default + @banner = banner || default_banner + + validate! # Trigger specific validations + end + + def usage + required? ? banner : "[#{banner}]" + end + + def required? + required + end + + def show_default? + case default + when Array, String, Hash + !default.empty? + else + default + end + end + + protected + + def validate! + raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil? + end + + def valid_type?(type) + VALID_TYPES.include?(type.to_sym) + end + + def default_banner + case type + when :boolean + nil + when :string, :default + human_name.upcase + when :numeric + "N" + when :hash + "key:value" + when :array + "one two three" + end + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb new file mode 100644 index 0000000000..fb5d965e06 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb @@ -0,0 +1,145 @@ +class Thor + class Arguments #:nodoc: + NUMERIC = /(\d*\.\d+|\d+)/ + + # Receives an array of args and returns two arrays, one with arguments + # and one with switches. + # + def self.split(args) + arguments = [] + + args.each do |item| + break if item =~ /^-/ + arguments << item + end + + return arguments, args[Range.new(arguments.size, -1)] + end + + def self.parse(base, args) + new(base).parse(args) + end + + # Takes an array of Thor::Argument objects. + # + def initialize(arguments=[]) + @assigns, @non_assigned_required = {}, [] + @switches = arguments + + arguments.each do |argument| + if argument.default + @assigns[argument.human_name] = argument.default + elsif argument.required? + @non_assigned_required << argument + end + end + end + + def parse(args) + @pile = args.dup + + @switches.each do |argument| + break unless peek + @non_assigned_required.delete(argument) + @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name) + end + + check_requirement! + @assigns + end + + private + + def peek + @pile.first + end + + def shift + @pile.shift + end + + def unshift(arg) + unless arg.kind_of?(Array) + @pile.unshift(arg) + else + @pile = arg + @pile + end + end + + def current_is_value? + peek && peek.to_s !~ /^-/ + end + + # Runs through the argument array getting strings that contains ":" and + # mark it as a hash: + # + # [ "name:string", "age:integer" ] + # + # Becomes: + # + # { "name" => "string", "age" => "integer" } + # + def parse_hash(name) + return shift if peek.is_a?(Hash) + hash = {} + + while current_is_value? && peek.include?(?:) + key, value = shift.split(':') + hash[key] = value + end + hash + end + + # Runs through the argument array getting all strings until no string is + # found or a switch is found. + # + # ["a", "b", "c"] + # + # And returns it as an array: + # + # ["a", "b", "c"] + # + def parse_array(name) + return shift if peek.is_a?(Array) + array = [] + + while current_is_value? + array << shift + end + array + end + + # Check if the peel is numeric ofrmat and return a Float or Integer. + # Otherwise raises an error. + # + def parse_numeric(name) + return shift if peek.is_a?(Numeric) + + unless peek =~ NUMERIC && $& == peek + raise MalformattedArgumentError, "expected numeric value for '#{name}'; got #{peek.inspect}" + end + + $&.index('.') ? shift.to_f : shift.to_i + end + + # Parse string, i.e., just return the current value in the pile. + # + def parse_string(name) + shift + end + + # Raises an error if @non_assigned_required array is not empty. + # + def check_requirement! + unless @non_assigned_required.empty? + names = @non_assigned_required.map do |o| + o.respond_to?(:switch_name) ? o.switch_name : o.human_name + end.join("', '") + + class_name = self.class.name.split('::').last.downcase + raise RequiredArgumentMissingError, "no value provided for required #{class_name} '#{names}'" + end + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb new file mode 100644 index 0000000000..9e40ec73fa --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb @@ -0,0 +1,132 @@ +class Thor + class Option < Argument #:nodoc: + attr_reader :aliases, :group + + VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] + + def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil) + super(name, description, required, type, default, banner) + @aliases = [*aliases].compact + @group = group.to_s.capitalize if group + end + + # This parse quick options given as method_options. It makes several + # assumptions, but you can be more specific using the option method. + # + # parse :foo => "bar" + # #=> Option foo with default value bar + # + # parse [:foo, :baz] => "bar" + # #=> Option foo with default value bar and alias :baz + # + # parse :foo => :required + # #=> Required option foo without default value + # + # parse :foo => 2 + # #=> Option foo with default value 2 and type numeric + # + # parse :foo => :numeric + # #=> Option foo without default value and type numeric + # + # parse :foo => true + # #=> Option foo with default value true and type boolean + # + # The valid types are :boolean, :numeric, :hash, :array and :string. If none + # is given a default type is assumed. This default type accepts arguments as + # string (--foo=value) or booleans (just --foo). + # + # By default all options are optional, unless :required is given. + # + def self.parse(key, value) + if key.is_a?(Array) + name, *aliases = key + else + name, aliases = key, [] + end + + name = name.to_s + default = value + + type = case value + when Symbol + default = nil + + if VALID_TYPES.include?(value) + value + elsif required = (value == :required) + :string + elsif value == :optional + # TODO Remove this warning in the future. + warn "Optional type is deprecated. Choose :boolean or :string instead. Assumed to be :boolean." + :boolean + end + when TrueClass, FalseClass + :boolean + when Numeric + :numeric + when Hash, Array, String + value.class.name.downcase.to_sym + end + + self.new(name.to_s, nil, required, type, default, nil, nil, aliases) + end + + def switch_name + @switch_name ||= dasherized? ? name : dasherize(name) + end + + def human_name + @human_name ||= dasherized? ? undasherize(name) : name + end + + def usage(padding=0) + sample = if banner && !banner.to_s.empty? + "#{switch_name}=#{banner}" + else + switch_name + end + + sample = "[#{sample}]" unless required? + + if aliases.empty? + (" " * padding) << sample + else + "#{aliases.join(', ')}, #{sample}" + end + end + + # Allow some type predicates as: boolean?, string? and etc. + # + def method_missing(method, *args, &block) + given = method.to_s.sub(/\?$/, '').to_sym + if valid_type?(given) + self.type == given + else + super + end + end + + protected + + def validate! + raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? + end + + def valid_type?(type) + VALID_TYPES.include?(type.to_sym) + end + + def dasherized? + name.index('-') == 0 + end + + def undasherize(str) + str.sub(/^-{1,2}/, '') + end + + def dasherize(str) + (str.length > 1 ? "--" : "-") + str.gsub('_', '-') + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb new file mode 100644 index 0000000000..75092308b5 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb @@ -0,0 +1,142 @@ +class Thor + # This is a modified version of Daniel Berger's Getopt::Long class, licensed + # under Ruby's license. + # + class Options < Arguments #:nodoc: + LONG_RE = /^(--\w+[-\w+]*)$/ + SHORT_RE = /^(-[a-z])$/i + EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i + SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args + SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i + + # Receives a hash and makes it switches. + # + def self.to_switches(options) + options.map do |key, value| + case value + when true + "--#{key}" + when Array + "--#{key} #{value.map{ |v| v.inspect }.join(' ')}" + when Hash + "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}" + when nil, false + "" + else + "--#{key} #{value.inspect}" + end + end.join(" ") + end + + # Takes a hash of Thor::Option objects. + # + def initialize(options={}) + options = options.values + super(options) + @shorts, @switches = {}, {} + + options.each do |option| + @switches[option.switch_name] = option + + option.aliases.each do |short| + @shorts[short.to_s] ||= option.switch_name + end + end + end + + def parse(args) + @pile = args.dup + + while peek + if current_is_switch? + case shift + when SHORT_SQ_RE + unshift($1.split('').map { |f| "-#{f}" }) + next + when EQ_RE, SHORT_NUM + unshift($2) + switch = $1 + when LONG_RE, SHORT_RE + switch = $1 + end + + switch = normalize_switch(switch) + next unless option = switch_option(switch) + + @assigns[option.human_name] = parse_peek(switch, option) + else + shift + end + end + + check_requirement! + @assigns + end + + protected + + # Returns true if the current value in peek is a registered switch. + # + def current_is_switch? + case peek + when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM + switch?($1) + when SHORT_SQ_RE + $1.split('').any? { |f| switch?("-#{f}") } + end + end + + def switch?(arg) + switch_option(arg) || @shorts.key?(arg) + end + + def switch_option(arg) + if match = no_or_skip?(arg) + @switches[arg] || @switches["--#{match}"] + else + @switches[arg] + end + end + + def no_or_skip?(arg) + arg =~ /^--(no|skip)-([-\w]+)$/ + $2 + end + + # Check if the given argument is actually a shortcut. + # + def normalize_switch(arg) + @shorts.key?(arg) ? @shorts[arg] : arg + end + + # Parse boolean values which can be given as --foo=true, --foo or --no-foo. + # + def parse_boolean(switch) + if current_is_value? + ["true", "TRUE", "t", "T", true].include?(shift) + else + @switches.key?(switch) || !no_or_skip?(switch) + end + end + + # Parse the value at the peek analyzing if it requires an input or not. + # + def parse_peek(switch, option) + unless current_is_value? + if option.boolean? + # No problem for boolean types + elsif no_or_skip?(switch) + return nil # User set value to nil + elsif option.string? && !option.required? + return option.human_name # Return the option name + else + raise MalformattedArgumentError, "no value provided for option '#{switch}'" + end + end + + @non_assigned_required.delete(option) + send(:"parse_#{option.type}", switch) + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb new file mode 100644 index 0000000000..3ab6bb21f5 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb @@ -0,0 +1,67 @@ +require 'rake' + +class Thor + # Adds a compatibility layer to your Thor classes which allows you to use + # rake package tasks. For example, to use rspec rake tasks, one can do: + # + # require 'thor/rake_compat' + # + # class Default < Thor + # include Thor::RakeCompat + # + # Spec::Rake::SpecTask.new(:spec) do |t| + # t.spec_opts = ['--options', "spec/spec.opts"] + # t.spec_files = FileList['spec/**/*_spec.rb'] + # end + # end + # + module RakeCompat + def self.rake_classes + @rake_classes ||= [] + end + + def self.included(base) + # Hack. Make rakefile point to invoker, so rdoc task is generated properly. + Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1]) + self.rake_classes << base + end + end +end + +class Object #:nodoc: + alias :rake_task :task + alias :rake_namespace :namespace + + def task(*args, &block) + task = rake_task(*args, &block) + + if klass = Thor::RakeCompat.rake_classes.last + non_namespaced_name = task.name.split(':').last + + description = non_namespaced_name + description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ') + description.strip! + + klass.desc description, task.comment || non_namespaced_name + klass.class_eval <<-METHOD + def #{non_namespaced_name}(#{task.arg_names.join(', ')}) + Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')}) + end + METHOD + end + + task + end + + def namespace(name, &block) + if klass = Thor::RakeCompat.rake_classes.last + const_name = Thor::Util.camel_case(name.to_s).to_sym + klass.const_set(const_name, Class.new(Thor)) + new_klass = klass.const_get(const_name) + Thor::RakeCompat.rake_classes << new_klass + end + + rake_namespace(name, &block) + Thor::RakeCompat.rake_classes.pop + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb new file mode 100644 index 0000000000..3639ac0aa9 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb @@ -0,0 +1,295 @@ +require 'fileutils' +require 'open-uri' +require 'yaml' +require 'digest/md5' +require 'pathname' + +class Thor::Runner < Thor #:nodoc: + map "-T" => :list, "-i" => :install, "-u" => :update + + # Override Thor#help so it can give information about any class and any method. + # + def help(meth=nil) + if meth && !self.respond_to?(meth) + initialize_thorfiles(meth) + klass, task = Thor::Util.namespace_to_thor_class_and_task(meth) + # Send mapping -h because it works with Thor::Group too + klass.start(["-h", task].compact, :shell => self.shell) + else + super + end + end + + # If a task is not found on Thor::Runner, method missing is invoked and + # Thor::Runner is then responsable for finding the task in all classes. + # + def method_missing(meth, *args) + meth = meth.to_s + initialize_thorfiles(meth) + klass, task = Thor::Util.namespace_to_thor_class_and_task(meth) + args.unshift(task) if task + klass.start(args, :shell => shell) + end + + desc "install NAME", "Install an optionally named Thor file into your system tasks" + method_options :as => :string, :relative => :boolean + def install(name) + initialize_thorfiles + + # If a directory name is provided as the argument, look for a 'main.thor' + # task in said directory. + begin + if File.directory?(File.expand_path(name)) + base, package = File.join(name, "main.thor"), :directory + contents = open(base).read + else + base, package = name, :file + contents = open(name).read + end + rescue OpenURI::HTTPError + raise Error, "Error opening URI '#{name}'" + rescue Errno::ENOENT + raise Error, "Error opening file '#{name}'" + end + + say "Your Thorfile contains:" + say contents + + return false if no?("Do you wish to continue [y/N]?") + + as = options["as"] || begin + first_line = contents.split("\n")[0] + (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil + end + + unless as + basename = File.basename(name) + as = ask("Please specify a name for #{name} in the system repository [#{basename}]:") + as = basename if as.empty? + end + + location = if options[:relative] || name =~ /^http:\/\// + name + else + File.expand_path(name) + end + + thor_yaml[as] = { + :filename => Digest::MD5.hexdigest(name + as), + :location => location, + :namespaces => Thor::Util.namespaces_in_content(contents, base) + } + + save_yaml(thor_yaml) + say "Storing thor file in your system repository" + destination = File.join(thor_root, thor_yaml[as][:filename]) + + if package == :file + File.open(destination, "w") { |f| f.puts contents } + else + FileUtils.cp_r(name, destination) + end + + thor_yaml[as][:filename] # Indicate success + end + + desc "uninstall NAME", "Uninstall a named Thor module" + def uninstall(name) + raise Error, "Can't find module '#{name}'" unless thor_yaml[name] + say "Uninstalling #{name}." + FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}")) + + thor_yaml.delete(name) + save_yaml(thor_yaml) + + puts "Done." + end + + desc "update NAME", "Update a Thor file from its original location" + def update(name) + raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] + + say "Updating '#{name}' from #{thor_yaml[name][:location]}" + + old_filename = thor_yaml[name][:filename] + self.options = self.options.merge("as" => name) + filename = install(thor_yaml[name][:location]) + + unless filename == old_filename + File.delete(File.join(thor_root, old_filename)) + end + end + + desc "installed", "List the installed Thor modules and tasks" + method_options :internal => :boolean + def installed + initialize_thorfiles(nil, true) + + klasses = Thor::Base.subclasses + klasses -= [Thor, Thor::Runner] unless options["internal"] + + display_klasses(true, klasses) + end + + desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)" + method_options :substring => :boolean, :group => :string, :all => :boolean + def list(search="") + initialize_thorfiles + + search = ".*#{search}" if options["substring"] + search = /^#{search}.*/i + group = options[:group] || "standard" + + klasses = Thor::Base.subclasses.select do |k| + (options[:all] || k.group == group) && k.namespace =~ search + end + + display_klasses(false, klasses) + end + + private + + def thor_root + Thor::Util.thor_root + end + + def thor_yaml + @thor_yaml ||= begin + yaml_file = File.join(thor_root, "thor.yml") + yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file) + yaml || {} + end + end + + # Save the yaml file. If none exists in thor root, creates one. + # + def save_yaml(yaml) + yaml_file = File.join(thor_root, "thor.yml") + + unless File.exists?(yaml_file) + FileUtils.mkdir_p(thor_root) + yaml_file = File.join(thor_root, "thor.yml") + FileUtils.touch(yaml_file) + end + + File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml } + end + + # Load the thorfiles. If relevant_to is supplied, looks for specific files + # in the thor_root instead of loading them all. + # + # By default, it also traverses the current path until find Thor files, as + # described in thorfiles. This look up can be skipped by suppliying + # skip_lookup true. + # + def initialize_thorfiles(relevant_to=nil, skip_lookup=false) + thorfiles(relevant_to, skip_lookup).each do |f| + Thor::Util.load_thorfile(f) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f)) + end + end + + # Finds Thorfiles by traversing from your current directory down to the root + # directory of your system. If at any time we find a Thor file, we stop. + # + # We also ensure that system-wide Thorfiles are loaded first, so local + # Thorfiles can override them. + # + # ==== Example + # + # If we start at /Users/wycats/dev/thor ... + # + # 1. /Users/wycats/dev/thor + # 2. /Users/wycats/dev + # 3. /Users/wycats <-- we find a Thorfile here, so we stop + # + # Suppose we start at c:\Documents and Settings\james\dev\thor ... + # + # 1. c:\Documents and Settings\james\dev\thor + # 2. c:\Documents and Settings\james\dev + # 3. c:\Documents and Settings\james + # 4. c:\Documents and Settings + # 5. c:\ <-- no Thorfiles found! + # + def thorfiles(relevant_to=nil, skip_lookup=false) + # Deal with deprecated thor when :namespaces: is available as constants + save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml) + + thorfiles = [] + + unless skip_lookup + Pathname.pwd.ascend do |path| + thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten + break unless thorfiles.empty? + end + end + + files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob) + files += thorfiles + files -= ["#{thor_root}/thor.yml"] + + files.map! do |file| + File.directory?(file) ? File.join(file, "main.thor") : file + end + end + + # Load thorfiles relevant to the given method. If you provide "foo:bar" it + # will load all thor files in the thor.yaml that has "foo" e "foo:bar" + # namespaces registered. + # + def thorfiles_relevant_to(meth) + lookup = [ meth, meth.split(":")[0...-1].join(":") ] + + files = thor_yaml.select do |k, v| + v[:namespaces] && !(v[:namespaces] & lookup).empty? + end + + files.map { |k, v| File.join(thor_root, "#{v[:filename]}") } + end + + # Display information about the given klasses. If with_module is given, + # it shows a table with information extracted from the yaml file. + # + def display_klasses(with_modules=false, klasses=Thor.subclasses) + klasses -= [Thor, Thor::Runner] unless with_modules + raise Error, "No Thor tasks available" if klasses.empty? + + if with_modules && !thor_yaml.empty? + info = [] + labels = ["Modules", "Namespaces"] + + info << labels + info << [ "-" * labels[0].size, "-" * labels[1].size ] + + thor_yaml.each do |name, hash| + info << [ name, hash[:namespaces].join(", ") ] + end + + print_table info + say "" + end + + unless klasses.empty? + klasses.dup.each do |klass| + klasses -= Thor::Util.thor_classes_in(klass) + end + + klasses.each { |k| display_tasks(k) } + else + say "\033[1;34mNo Thor tasks available\033[0m" + end + end + + # Display tasks from the given Thor class. + # + def display_tasks(klass) + unless klass.tasks.empty? + base = klass.namespace + + color = base == "default" ? :magenta : :blue + say shell.set_color(base, color, true) + say "-" * base.length + + klass.help(shell, :short => true, :ident => 0, :namespace => true) + end + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb new file mode 100644 index 0000000000..0d3f4d5951 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb @@ -0,0 +1,72 @@ +require 'thor/shell/color' + +class Thor + module Base + # Returns the shell used in all Thor classes. Default to color one. + # + def self.shell + @shell ||= Thor::Shell::Color + end + + # Sets the shell used in all Thor classes. + # + def self.shell=(klass) + @shell = klass + end + end + + module Shell + SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table] + + # Add shell to initialize config values. + # + # ==== Configuration + # shell:: An instance of the shell to be used. + # + # ==== Examples + # + # class MyScript < Thor + # argument :first, :type => :numeric + # end + # + # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new + # + def initialize(args=[], options={}, config={}) + super + self.shell = config[:shell] + self.shell.base ||= self if self.shell.respond_to?(:base) + end + + # Holds the shell for the given Thor instance. If no shell is given, + # it gets a default shell from Thor::Base.shell. + # + def shell + @shell ||= Thor::Base.shell.new + end + + # Sets the shell for this thor class. + # + def shell=(shell) + @shell = shell + end + + # Common methods that are delegated to the shell. + # + SHELL_DELEGATED_METHODS.each do |method| + module_eval <<-METHOD, __FILE__, __LINE__ + def #{method}(*args) + shell.#{method}(*args) + end + METHOD + end + + protected + + # Allow shell to be shared between invocations. + # + def _shared_configuration #:nodoc: + super.merge!(:shell => self.shell) + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb new file mode 100644 index 0000000000..3c02e47c33 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb @@ -0,0 +1,220 @@ +require 'tempfile' + +class Thor + module Shell + class Basic + attr_accessor :base, :padding + + # Initialize base and padding to nil. + # + def initialize #:nodoc: + @base, @padding = nil, 0 + end + + # Sets the output padding, not allowing less than zero values. + # + def padding=(value) + @padding = [0, value].max + end + + # Ask something to the user and receives a response. + # + # ==== Example + # ask("What is your name?") + # + def ask(statement, color=nil) + say("#{statement} ", color) + $stdin.gets.strip + end + + # Say (print) something to the user. If the sentence ends with a whitespace + # or tab character, a new line is not appended (print + flush). Otherwise + # are passed straight to puts (behavior got from Highline). + # + # ==== Example + # say("I know you knew that.") + # + def say(message="", color=nil, force_new_line=false) + message = message.to_s + new_line = force_new_line || !(message[-1, 1] == " " || message[-1, 1] == "\t") + message = set_color(message, color) if color + + if new_line + $stdout.puts(message) + else + $stdout.print(message) + $stdout.flush + end + end + + # Say a status with the given color and appends the message. Since this + # method is used frequently by actions, it allows nil or false to be given + # in log_status, avoiding the message from being shown. If a Symbol is + # given in log_status, it's used as the color. + # + def say_status(status, message, log_status=true) + return if quiet? || log_status == false + spaces = " " * (padding + 1) + color = log_status.is_a?(Symbol) ? log_status : :green + + status = status.to_s.rjust(12) + status = set_color status, color, true if color + say "#{status}#{spaces}#{message}", nil, true + end + + # Make a question the to user and returns true if the user replies "y" or + # "yes". + # + def yes?(statement, color=nil) + ask(statement, color) =~ is?(:yes) + end + + # Make a question the to user and returns true if the user replies "n" or + # "no". + # + def no?(statement, color=nil) + !yes?(statement, color) + end + + # Prints a list of items. + # + # ==== Parameters + # list + # + # ==== Options + # mode:: Can be :rows or :inline. Defaults to :rows. + # ident:: Ident each item with the value given. + # + def print_list(list, options={}) + return if list.empty? + + ident = " " * (options[:ident] || 0) + content = case options[:mode] + when :inline + last = list.pop + "#{list.join(", ")}, and #{last}" + else # rows + ident + list.join("\n#{ident}") + end + + $stdout.puts content + end + + # Prints a table. + # + # ==== Parameters + # Array[Array[String, String, ...]] + # + # ==== Options + # ident:: Ident the first column by ident value. + # + def print_table(table, options={}) + return if table.empty? + + formats = [] + 0.upto(table.first.length - 2) do |i| + maxima = table.max{ |a,b| a[i].size <=> b[i].size }[i].size + formats << "%-#{maxima + 2}s" + end + + formats[0] = formats[0].insert(0, " " * options[:ident]) if options[:ident] + formats << "%s" + + table.each do |row| + row.each_with_index do |column, i| + $stdout.print formats[i] % column.to_s + end + $stdout.puts + end + end + + # Deals with file collision and returns true if the file should be + # overwriten and false otherwise. If a block is given, it uses the block + # response as the content for the diff. + # + # ==== Parameters + # destination:: the destination file to solve conflicts + # block:: an optional block that returns the value to be used in diff + # + def file_collision(destination) + return true if @always_force + options = block_given? ? "[Ynaqdh]" : "[Ynaqh]" + + while true + answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}] + + case answer + when is?(:yes), is?(:force) + return true + when is?(:no), is?(:skip) + return false + when is?(:always) + return @always_force = true + when is?(:quit) + say 'Aborting...' + raise SystemExit + when is?(:diff) + show_diff(destination, yield) if block_given? + say 'Retrying...' + else + say file_collision_help + end + end + end + + # Called if something goes wrong during the execution. This is used by Thor + # internally and should not be used inside your scripts. If someone went + # wrong, you can always raise an exception. If you raise a Thor::Error, it + # will be rescued and wrapped in the method below. + # + def error(statement) + $stderr.puts statement + end + + # Apply color to the given string with optional bold. Disabled in the + # Thor::Shell::Basic class. + # + def set_color(string, color, bold=false) #:nodoc: + string + end + + protected + + def is?(value) #:nodoc: + value = value.to_s + + if value.size == 1 + /\A#{value}\z/i + else + /\A(#{value}|#{value[0,1]})\z/i + end + end + + def file_collision_help #:nodoc: +< e + parse_argument_error(instance, e, caller) + rescue NoMethodError => e + parse_no_method_error(instance, e) + end + + # Returns the formatted usage. If a class is given, the class arguments are + # injected in the usage. + # + def formatted_usage(klass=nil, namespace=false, show_options=true) + formatted = '' + + formatted = if namespace.is_a?(String) + "#{namespace}:" + elsif klass && namespace + "#{klass.namespace.gsub(/^default/,'')}:" + else + "" + end + + formatted << formatted_arguments(klass) + formatted << " #{formatted_options}" if show_options + formatted.strip! + formatted + end + + # Injects the class arguments into the task usage. + # + def formatted_arguments(klass) + if klass && !klass.arguments.empty? + usage.to_s.gsub(/^#{name}/) do |match| + match << " " << klass.arguments.map{ |a| a.usage }.join(' ') + end + else + usage.to_s + end + end + + # Returns the options usage for this task. + # + def formatted_options + @formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ") + end + + protected + + # Given a target, checks if this class name is not a private/protected method. + # + def public_method?(instance) #:nodoc: + collection = instance.private_methods + instance.protected_methods + !(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9 + end + + # Clean everything that comes from the Thor gempath and remove the caller. + # + def sans_backtrace(backtrace, caller) #:nodoc: + dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/ + saned = backtrace.reject { |frame| frame =~ dirname } + saned -= caller + end + + def parse_argument_error(instance, e, caller) #:nodoc: + backtrace = sans_backtrace(e.backtrace, caller) + + if backtrace.empty? && e.message =~ /wrong number of arguments/ + if instance.is_a?(Thor::Group) + raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?" + else + raise InvocationError, "'#{name}' was called incorrectly. Call as " << + "'#{formatted_usage(instance.class, true)}'" + end + else + raise e + end + end + + def parse_no_method_error(instance, e) #:nodoc: + if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ + raise UndefinedTaskError, "The #{instance.class.namespace} namespace " << + "doesn't have a '#{name}' task" + else + raise e + end + end + + end +end diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb new file mode 100644 index 0000000000..4938dc4aca --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb @@ -0,0 +1,250 @@ +require 'rbconfig' + +class Thor + module Sandbox #:nodoc: + end + + # This module holds several utilities: + # + # 1) Methods to convert thor namespaces to constants and vice-versa. + # + # Thor::Utils.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz" + # + # 2) Loading thor files and sandboxing: + # + # Thor::Utils.load_thorfile("~/.thor/foo") + # + module Util + + # Receives a namespace and search for it in the Thor::Base subclasses. + # + # ==== Parameters + # namespace:: The namespace to search for. + # + def self.find_by_namespace(namespace) + namespace = 'default' if namespace.empty? + + Thor::Base.subclasses.find do |klass| + klass.namespace == namespace + end + end + + # Receives a constant and converts it to a Thor namespace. Since Thor tasks + # can be added to a sandbox, this method is also responsable for removing + # the sandbox namespace. + # + # This method should not be used in general because it's used to deal with + # older versions of Thor. On current versions, if you need to get the + # namespace from a class, just call namespace on it. + # + # ==== Parameters + # constant:: The constant to be converted to the thor path. + # + # ==== Returns + # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz" + # + def self.namespace_from_thor_class(constant, remove_default=true) + constant = constant.to_s.gsub(/^Thor::Sandbox::/, "") + constant = snake_case(constant).squeeze(":") + constant.gsub!(/^default/, '') if remove_default + constant + end + + # Given the contents, evaluate it inside the sandbox and returns the + # namespaces defined in the sandbox. + # + # ==== Parameters + # contents + # + # ==== Returns + # Array[Object] + # + def self.namespaces_in_content(contents, file=__FILE__) + old_constants = Thor::Base.subclasses.dup + Thor::Base.subclasses.clear + + load_thorfile(file, contents) + + new_constants = Thor::Base.subclasses.dup + Thor::Base.subclasses.replace(old_constants) + + new_constants.map!{ |c| c.namespace } + new_constants.compact! + new_constants + end + + # Returns the thor classes declared inside the given class. + # + def self.thor_classes_in(klass) + Thor::Base.subclasses.select do |subclass| + klass.constants.include?(subclass.name.gsub("#{klass.name}::", '')) + end + end + + # Receives a string and convert it to snake case. SnakeCase returns snake_case. + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def self.snake_case(str) + return str.downcase if str =~ /^[A-Z_]+$/ + str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/ + return $+.downcase + end + + # Receives a string and convert it to camel case. camel_case returns CamelCase. + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def self.camel_case(str) + return str if str !~ /_/ && str =~ /[A-Z]+.*/ + str.split('_').map { |i| i.capitalize }.join + end + + # Receives a namespace and tries to retrieve a Thor or Thor::Group class + # from it. It first searches for a class using the all the given namespace, + # if it's not found, removes the highest entry and searches for the class + # again. If found, returns the highest entry as the class name. + # + # ==== Examples + # + # class Foo::Bar < Thor + # def baz + # end + # end + # + # class Baz::Foo < Thor::Group + # end + # + # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default task + # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil + # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz" + # + # ==== Parameters + # namespace + # + # ==== Errors + # Thor::Error:: raised if the namespace cannot be found. + # + # Thor::Error:: raised if the namespace evals to a class which does not + # inherit from Thor or Thor::Group. + # + def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true) + klass, task_name = Thor::Util.find_by_namespace(namespace), nil + + if klass.nil? && namespace.include?(?:) + namespace = namespace.split(":") + task_name = namespace.pop + klass = Thor::Util.find_by_namespace(namespace.join(":")) + end + + raise Error, "could not find Thor class or task '#{namespace}'" if raise_if_nil && klass.nil? + + return klass, task_name + end + + # Receives a path and load the thor file in the path. The file is evaluated + # inside the sandbox to avoid namespacing conflicts. + # + def self.load_thorfile(path, content=nil) + content ||= File.read(path) + + begin + Thor::Sandbox.class_eval(content, path) + rescue Exception => e + $stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}" + end + end + + # Receives a yaml (hash) and updates all constants entries to namespace. + # This was added to deal with deprecated versions of Thor. + # + # TODO Deprecate this method in the future. + # + # ==== Returns + # TrueClass|FalseClass:: Returns true if any change to the yaml file was made. + # + def self.convert_constants_to_namespaces(yaml) + yaml_changed = false + + yaml.each do |k, v| + next unless v[:constants] && v[:namespaces].nil? + yaml_changed = true + yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)} + end + + yaml_changed + end + + def self.user_home + @@user_home ||= if ENV["HOME"] + ENV["HOME"] + elsif ENV["USERPROFILE"] + ENV["USERPROFILE"] + elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"] + File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"]) + elsif ENV["APPDATA"] + ENV["APPDATA"] + else + begin + File.expand_path("~") + rescue + if File::ALT_SEPARATOR + "C:/" + else + "/" + end + end + end + end + + # Returns the root where thor files are located, dependending on the OS. + # + def self.thor_root + File.join(user_home, ".thor") + end + + # Returns the files in the thor root. On Windows thor_root will be something + # like this: + # + # C:\Documents and Settings\james\.thor + # + # If we don't #gsub the \ character, Dir.glob will fail. + # + def self.thor_root_glob + files = Dir["#{thor_root.gsub(/\\/, '/')}/*"] + + files.map! do |file| + File.directory?(file) ? File.join(file, "main.thor") : file + end + end + + # Where to look for Thor files. + # + def self.globs_for(path) + ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] + end + + # Return the path to the ruby interpreter taking into account multiple + # installations and windows extensions. + # + def self.ruby_command + @ruby_command ||= begin + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + ruby << Config::CONFIG['EXEEXT'] + + # escape string in case path to ruby executable contain spaces. + ruby.sub!(/.*\s.*/m, '"\&"') + ruby + end + end + + end +end -- cgit v1.2.3 From f59984cc81bd1a64a53a2480a9b4e05fe7357d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 1 Aug 2009 15:29:39 +0200 Subject: Add nagivational behavior to respond_with. --- .../lib/action_controller/metal/conditional_get.rb | 7 +- .../lib/action_controller/metal/mime_responds.rb | 157 +++++++++++++++------ .../routing/generation/polymorphic_routes.rb | 21 ++- actionpack/test/controller/mime_responds_test.rb | 89 +++++++++--- actionpack/test/controller/render_test.rb | 15 ++ .../test/fixtures/respond_with/edit.html.erb | 1 + actionpack/test/fixtures/respond_with/new.html.erb | 1 + .../fixtures/respond_with/using_resource.html.erb | 1 - .../fixtures/respond_with/using_resource.js.rjs | 1 + 9 files changed, 215 insertions(+), 78 deletions(-) create mode 100644 actionpack/test/fixtures/respond_with/edit.html.erb create mode 100644 actionpack/test/fixtures/respond_with/new.html.erb delete mode 100644 actionpack/test/fixtures/respond_with/using_resource.html.erb create mode 100644 actionpack/test/fixtures/respond_with/using_resource.js.rjs diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 6d35137428..8575d30335 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -55,14 +55,15 @@ module ActionController elsif args.empty? raise ArgumentError, "too few arguments to head" end - options = args.extract_options! - status = args.shift || options.delete(:status) || :ok + options = args.extract_options! + status = args.shift || options.delete(:status) || :ok + location = options.delete(:location) options.each do |key, value| headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s end - render :nothing => true, :status => status + render :nothing => true, :status => status, :location => location end # Sets the etag and/or last_modified on the response and checks it against diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 9a6c8aa58b..837496e477 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -198,11 +198,11 @@ module ActionController #:nodoc: end # respond_with allows you to respond an action with a given resource. It - # requires that you set your class with a :respond_to method with the + # requires that you set your class with a respond_to method with the # formats allowed: # # class PeopleController < ApplicationController - # respond_to :xml, :json + # respond_to :html, :xml, :json # # def index # @people = Person.find(:all) @@ -210,14 +210,43 @@ module ActionController #:nodoc: # end # end # - # When a request comes with format :xml, the respond_with will first search - # for a template as person/index.xml, if the template is not available, it - # will see if the given resource responds to :to_xml. + # When a request comes, for example with format :xml, three steps happen: + # + # 1) respond_with searches for a template at people/index.xml; + # + # 2) if the template is not available, it will check if the given + # resource responds to :to_xml. + # + # 3) if a :location option was provided, redirect to the location with + # redirect status if a string was given, or render an action if a + # symbol was given. + # + # If all steps fail, a missing template error will be raised. + # + # === Supported options + # + # [status] + # Sets the response status. + # + # [head] + # Tell respond_with to set the content type, status and location header, + # but do not render the object, leaving the response body empty. This + # option only has effect if the resource is being rendered. If a + # template was found, it's going to be rendered anyway. + # + # [location] + # Sets the location header with the given value. It accepts a string, + # representing the location header value, or a symbol representing an + # action name. + # + # === Builtin HTTP verb semantics # - # If neither are available, it will raise an error. + # respond_with holds semantics for each HTTP verb. Depending on the verb + # and the resource status, respond_with will automatically set the options + # above. # - # respond_with holds semantics for each HTTP verb. The example above cover - # GET requests. Let's check a POST request example: + # Above we saw an example for GET requests, where actually no option is + # configured. A create action for POST requests, could be written as: # # def create # @person = Person.new(params[:person]) @@ -225,34 +254,40 @@ module ActionController #:nodoc: # respond_with(@person) # end # - # Since the request is a POST, respond_with will check wether @people - # resource have errors or not. If it has errors, it will render the error - # object with unprocessable entity status (422). + # respond_with will inspect the @person object and check if we have any + # error. If errors are empty, it will add status and location to the options + # hash. Then the create action in case of success, is equivalent to this: # - # If no error was found, it will render the @people resource, with status - # created (201) and location header set to person_url(@people). + # respond_with(@person, :status => :created, :location => @person) # - # If you also want to provide html behavior in the method above, you can - # supply a block to customize it: + # From them on, the lookup happens as described above. Let's suppose a :xml + # request and we don't have a people/create.xml template. But since the + # @person object responds to :to_xml, it will render the newly created + # resource and set status and location. # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json # Add :html to respond_to definition - # - # def create - # @person = Person.new(params[:pe]) - # - # respond_with(@person) do |format| - # if @person.save - # flash[:notice] = 'Person was successfully created.' - # format.html { redirect_to @person } - # else - # format.html { render :action => "new" } - # end - # end + # However, if the request is :html, a template is not available and @person + # does not respond to :to_html. But since a :location options was provided, + # it will redirect to it. + # + # In case of failures (when the @person could not be saved and errors are + # not empty), respond_with can be expanded as this: + # + # respond_with(@person.errors, :status => :unprocessable_entity, :location => :new) + # + # In other words, respond_with(@person) for POST requests is expanded + # internally into this: + # + # def create + # @person = Person.new(params[:person]) + # + # if @person.save + # respond_with(@person, :status => :created, :location => @person) + # else + # respond_with(@person.errors, :status => :unprocessable_entity, :location => :new) # end # end # - # It works similarly for PUT requests: + # For an update action for PUT requests, we would have: # # def update # @person = Person.find(params[:id]) @@ -260,10 +295,23 @@ module ActionController #:nodoc: # respond_with(@person) # end # - # In case of failures, it works as POST requests, but in success failures - # it just reply status ok (200) to the client. + # Which, in face of success and failure scenarios, can be expanded as: # - # A DELETE request also works in the same way: + # def update + # @person = Person.find(params[:id]) + # @person.update_attributes(params[:person]) + # + # if @person.save + # respond_with(@person, :status => :ok, :location => @person, :head => true) + # else + # respond_with(@person.errors, :status => :unprocessable_entity, :location => :edit) + # end + # end + # + # Notice that in case of success, we just need to reply :ok to the client. + # The option :head ensures that the object is not rendered. + # + # Finally, we have the destroy action with DELETE verb: # # def destroy # @person = Person.find(params[:id]) @@ -271,21 +319,30 @@ module ActionController #:nodoc: # respond_with(@person) # end # - # It just replies with status ok, indicating the record was successfuly - # destroyed. + # Which is expanded as: + # + # def destroy + # @person = Person.find(params[:id]) + # @person.destroy + # respond_with(@person, :status => :ok, :location => @person, :head => true) + # end + # + # In this case, since @person.destroyed? returns true, polymorphic urls will + # redirect to the collection url, instead of the resource url. # def respond_with(resource, options={}, &block) respond_to(&block) rescue ActionView::MissingTemplate => e format = self.formats.first resource = normalize_resource_options_by_verb(resource, options) + action = options.delete(:location) if options[:location].is_a?(Symbol) if resource.respond_to?(:"to_#{format}") - if options.delete(:no_content) - head options - else - render options.merge(format => resource) - end + options.delete(:head) ? head(options) : render(options.merge(format => resource)) + elsif action + render :action => action + elsif options[:location] + redirect_to options[:location] else raise e end @@ -296,16 +353,22 @@ module ActionController #:nodoc: # Change respond with behavior based on the HTTP verb. # def normalize_resource_options_by_verb(resource_or_array, options) - resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array - has_errors = resource.respond_to?(:errors) && !resource.errors.empty? + resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array - if has_errors && (request.post? || request.put?) - options.reverse_merge!(:status => :unprocessable_entity) + if resource.respond_to?(:errors) && !resource.errors.empty? + options[:status] ||= :unprocessable_entity + options[:location] ||= :new if request.post? + options[:location] ||= :edit if request.put? return resource.errors - elsif request.post? - options.reverse_merge!(:status => :created, :location => resource_or_array) elsif !request.get? - options.reverse_merge!(:status => :ok, :no_content => true) + options[:location] ||= resource_or_array + + if request.post? + options[:status] ||= :created + else + options[:status] ||= :ok + options[:head] = true unless options.key?(:head) + end end return resource diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb index 159d869a54..ee44160274 100644 --- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb @@ -86,17 +86,16 @@ module ActionController else [ record_or_hash_or_array ] end - inflection = - case - when options[:action].to_s == "new" - args.pop - :singular - when record.respond_to?(:new_record?) && record.new_record? - args.pop - :plural - else - :singular - end + inflection = if options[:action].to_s == "new" + args.pop + :singular + elsif (record.respond_to?(:new_record?) && record.new_record?) || + (record.respond_to?(:destroyed?) && record.destroyed?) + args.pop + :plural + else + :singular + end args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 1d27e749ae..e9c8a3c10f 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -493,6 +493,10 @@ class RespondResource def errors [] end + + def destroyed? + false + end end class ParentResource @@ -508,7 +512,7 @@ end class RespondWithController < ActionController::Base respond_to :html, :json respond_to :xml, :except => :using_defaults - respond_to :js, :only => :using_defaults + respond_to :js, :only => [ :using_defaults, :using_resource ] def using_defaults respond_to do |format| @@ -547,12 +551,16 @@ protected self.response_body = js.respond_to?(:to_js) ? js.to_js : js end + def resources_url + request.host + "/resources" + end + def resource_url(resource) - request.host + "/resource/#{resource.to_param}" + request.host + "/resources/#{resource.to_param}" end def parent_resource_url(parent, resource) - request.host + "/parent/#{parent.to_param}/resource/#{resource.to_param}" + request.host + "/parents/#{parent.to_param}/resources/#{resource.to_param}" end end @@ -617,10 +625,10 @@ class RespondWithControllerTest < ActionController::TestCase end def test_using_resource - @request.accept = "text/html" + @request.accept = "text/javascript" get :using_resource - assert_equal "text/html", @response.content_type - assert_equal "Hello world!", @response.body + assert_equal "text/javascript", @response.content_type + assert_equal '$("body").visualEffect("highlight");', @response.body @request.accept = "application/xml" get :using_resource @@ -633,14 +641,30 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_for_post + def test_using_resource_for_post_with_html + post :using_resource + assert_equal "text/html", @response.content_type + assert_equal 302, @response.status + assert_equal "www.example.com/resources/13", @response.location + assert @response.redirect? + + errors = { :name => :invalid } + RespondResource.any_instance.stubs(:errors).returns(errors) + post :using_resource + assert_equal "text/html", @response.content_type + assert_equal 200, @response.status + assert_equal "New world!\n", @response.body + assert_nil @response.location + end + + def test_using_resource_for_post_with_xml @request.accept = "application/xml" post :using_resource assert_equal "application/xml", @response.content_type assert_equal 201, @response.status assert_equal "XML", @response.body - assert_equal "www.example.com/resource/13", @response.location + assert_equal "www.example.com/resources/13", @response.location errors = { :name => :invalid } RespondResource.any_instance.stubs(:errors).returns(errors) @@ -651,14 +675,30 @@ class RespondWithControllerTest < ActionController::TestCase assert_nil @response.location end - def test_using_resource_for_put + def test_using_resource_for_put_with_html + put :using_resource + assert_equal "text/html", @response.content_type + assert_equal 302, @response.status + assert_equal "www.example.com/resources/13", @response.location + assert @response.redirect? + + errors = { :name => :invalid } + RespondResource.any_instance.stubs(:errors).returns(errors) + put :using_resource + assert_equal "text/html", @response.content_type + assert_equal 200, @response.status + assert_equal "Edit world!\n", @response.body + assert_nil @response.location + end + + def test_using_resource_for_put_with_xml @request.accept = "application/xml" put :using_resource assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body - assert_nil @response.location + assert_equal "www.example.com/resources/13", @response.location errors = { :name => :invalid } RespondResource.any_instance.stubs(:errors).returns(errors) @@ -666,16 +706,25 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 422, @response.status assert_equal errors.to_xml, @response.body - assert_nil @response.location + assert_nil @response.location end - def test_using_resource_for_delete + def test_using_resource_for_delete_with_html + RespondResource.any_instance.stubs(:destroyed?).returns(true) + delete :using_resource + assert_equal "text/html", @response.content_type + assert_equal 302, @response.status + assert_equal "www.example.com/resources", @response.location + end + + def test_using_resource_for_delete_with_xml + RespondResource.any_instance.stubs(:destroyed?).returns(true) @request.accept = "application/xml" delete :using_resource assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body - assert_nil @response.location + assert_equal "www.example.com/resources", @response.location end def test_using_resource_with_options @@ -692,14 +741,22 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "JS", @response.body end - def test_using_resource_with_parent + def test_using_resource_with_parent_for_get + @request.accept = "application/xml" + get :using_resource_with_parent + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_equal "XML", @response.body + end + + def test_using_resource_with_parent_for_post @request.accept = "application/xml" post :using_resource_with_parent assert_equal "application/xml", @response.content_type assert_equal 201, @response.status assert_equal "XML", @response.body - assert_equal "www.example.com/parent/11/resource/13", @response.location + assert_equal "www.example.com/parents/11/resources/13", @response.location errors = { :name => :invalid } RespondResource.any_instance.stubs(:errors).returns(errors) @@ -739,7 +796,7 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal 406, @response.status @request.accept = "text/javascript" - get :using_resource + get :default_overwritten assert_equal 406, @response.status end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 947ffa9ea6..f2cd6dbb85 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -458,6 +458,10 @@ class TestController < ActionController::Base head :location => "/foo" end + def head_with_location_object + head :location => Customer.new("david") + end + def head_with_symbolic_status head :status => params[:status].intern end @@ -618,6 +622,10 @@ class TestController < ActionController::Base end private + def customer_url(customer) + request.host + "/customers/1" + end + def determine_layout case action_name when "hello_world", "layout_test", "rendering_without_layout", @@ -1084,6 +1092,13 @@ class RenderTest < ActionController::TestCase assert_response :ok end + def test_head_with_location_object + get :head_with_location_object + assert @response.body.blank? + assert_equal "www.nextangle.com/customers/1", @response.headers["Location"] + assert_response :ok + end + def test_head_with_custom_header get :head_with_custom_header assert @response.body.blank? diff --git a/actionpack/test/fixtures/respond_with/edit.html.erb b/actionpack/test/fixtures/respond_with/edit.html.erb new file mode 100644 index 0000000000..ae82dfa4fc --- /dev/null +++ b/actionpack/test/fixtures/respond_with/edit.html.erb @@ -0,0 +1 @@ +Edit world! diff --git a/actionpack/test/fixtures/respond_with/new.html.erb b/actionpack/test/fixtures/respond_with/new.html.erb new file mode 100644 index 0000000000..96c8f1b88b --- /dev/null +++ b/actionpack/test/fixtures/respond_with/new.html.erb @@ -0,0 +1 @@ +New world! diff --git a/actionpack/test/fixtures/respond_with/using_resource.html.erb b/actionpack/test/fixtures/respond_with/using_resource.html.erb deleted file mode 100644 index 6769dd60bd..0000000000 --- a/actionpack/test/fixtures/respond_with/using_resource.html.erb +++ /dev/null @@ -1 +0,0 @@ -Hello world! \ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.rjs b/actionpack/test/fixtures/respond_with/using_resource.js.rjs new file mode 100644 index 0000000000..737c175a4e --- /dev/null +++ b/actionpack/test/fixtures/respond_with/using_resource.js.rjs @@ -0,0 +1 @@ +page[:body].visual_effect :highlight -- cgit v1.2.3 From 7034272354ad41dd4d1c90138a79e7f14ebc2bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 1 Aug 2009 16:47:44 +0200 Subject: Add destroyed? to ActiveRecord, include tests for polymorphic urls for destroyed objects and refactor mime responds tests and documentation. --- .../lib/action_controller/metal/mime_responds.rb | 31 ++++++++ .../test/activerecord/polymorphic_routes_test.rb | 24 +++++- actionpack/test/controller/mime_responds_test.rb | 92 ++++++---------------- actionpack/test/controller/render_test.rb | 12 +-- actionpack/test/controller/render_xml_test.rb | 4 +- actionpack/test/lib/controller/fake_models.rb | 22 ++++++ activerecord/lib/active_record/base.rb | 7 ++ activerecord/test/cases/base_test.rb | 17 ++++ 8 files changed, 133 insertions(+), 76 deletions(-) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 837496e477..02a88437e3 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -330,6 +330,37 @@ module ActionController #:nodoc: # In this case, since @person.destroyed? returns true, polymorphic urls will # redirect to the collection url, instead of the resource url. # + # === Nested resources + # + # respond_with also works with nested resources, you just need to pass them + # as you do in form_for and polymorphic_url. Consider the project has many + # tasks example. The create action for TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # @task.save + # respond_with([@project, @task]) + # end + # + # Namespaced and singleton resources requires a symbol to be given, as in + # polymorphic urls. If a project has one manager with has many tasks, it + # should be invoked as: + # + # respond_with([@project, :manager, @task]) + # + # Be sure to check polymorphic_url documentation. The only occasion you will + # need to give clear input to respond_with is in DELETE verbs for singleton + # resources. In such cases, the collection url does not exist, so you need + # to supply the destination url after delete: + # + # def destroy + # @project = Project.find(params[:project_id]) + # @manager = @project.manager + # @manager.destroy + # respond_with([@project, @manager], :location => root_url) + # end + # def respond_with(resource, options={}, &block) respond_to(&block) rescue ActionView::MissingTemplate => e diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 2036d1eeb5..f001b0dab8 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -52,6 +52,13 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_destroyed_record + with_test_routes do + @project.destroy + assert_equal "http://example.com/projects", polymorphic_url(@project) + end + end + def test_with_record_and_action with_test_routes do assert_equal "http://example.com/projects/new", polymorphic_url(@project, :action => 'new') @@ -129,6 +136,14 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_nested_destroyed + with_test_routes do + @project.save + @task.destroy + assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task]) + end + end + def test_new_with_array_and_namespace with_admin_test_routes do assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], :action => 'new') @@ -257,6 +272,13 @@ class PolymorphicRoutesTest < ActionController::TestCase assert_equal "http://example.com/taxes", polymorphic_url(@tax) end end + + def test_with_irregular_plural_destroyed_record + with_test_routes do + @tax.destroy + assert_equal "http://example.com/taxes", polymorphic_url(@tax) + end + end def test_with_irregular_plural_record_and_action with_test_routes do @@ -395,4 +417,4 @@ class PolymorphicRoutesTest < ActionController::TestCase end end -end \ No newline at end of file +end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index e9c8a3c10f..faa84e1971 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'controller/fake_models' class RespondToController < ActionController::Base layout :set_layout @@ -471,44 +472,6 @@ class RespondToControllerTest < ActionController::TestCase end end -class RespondResource - undef_method :to_json - - def self.model_name - @_model_name ||= ActiveModel::Name.new("resource") - end - - def to_param - 13 - end - - def to_xml - "XML" - end - - def to_js - "JS" - end - - def errors - [] - end - - def destroyed? - false - end -end - -class ParentResource - def self.model_name - @_model_name ||= ActiveModel::Name.new("parent") - end - - def to_param - 11 - end -end - class RespondWithController < ActionController::Base respond_to :html, :json respond_to :xml, :except => :using_defaults @@ -531,17 +494,17 @@ class RespondWithController < ActionController::Base end def using_resource - respond_with(RespondResource.new) + respond_with(Customer.new("david", 13)) end def using_resource_with_options - respond_with(RespondResource.new, :status => :unprocessable_entity) do |format| + respond_with(Customer.new("david", 13), :status => :unprocessable_entity) do |format| format.js end end def using_resource_with_parent - respond_with([ParentResource.new, RespondResource.new]) + respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)]) end protected @@ -550,18 +513,6 @@ protected self.content_type ||= Mime::JS self.response_body = js.respond_to?(:to_js) ? js.to_js : js end - - def resources_url - request.host + "/resources" - end - - def resource_url(resource) - request.host + "/resources/#{resource.to_param}" - end - - def parent_resource_url(parent, resource) - request.host + "/parents/#{parent.to_param}/resources/#{resource.to_param}" - end end class InheritedRespondWithController < RespondWithController @@ -569,7 +520,7 @@ class InheritedRespondWithController < RespondWithController respond_to :xml, :json def index - respond_with(RespondResource.new) do |format| + respond_with(Customer.new("david", 13)) do |format| format.json { render :text => "JSON" } end end @@ -582,6 +533,11 @@ class RespondWithControllerTest < ActionController::TestCase super ActionController::Base.use_accept_header = true @request.host = "www.example.com" + + ActionController::Routing::Routes.draw do |map| + map.resources :customers + map.resources :quiz_stores, :has_many => :customers + end end def teardown @@ -645,11 +601,11 @@ class RespondWithControllerTest < ActionController::TestCase post :using_resource assert_equal "text/html", @response.content_type assert_equal 302, @response.status - assert_equal "www.example.com/resources/13", @response.location + assert_equal "http://www.example.com/customers/13", @response.location assert @response.redirect? errors = { :name => :invalid } - RespondResource.any_instance.stubs(:errors).returns(errors) + Customer.any_instance.stubs(:errors).returns(errors) post :using_resource assert_equal "text/html", @response.content_type assert_equal 200, @response.status @@ -664,10 +620,10 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 201, @response.status assert_equal "XML", @response.body - assert_equal "www.example.com/resources/13", @response.location + assert_equal "http://www.example.com/customers/13", @response.location errors = { :name => :invalid } - RespondResource.any_instance.stubs(:errors).returns(errors) + Customer.any_instance.stubs(:errors).returns(errors) post :using_resource assert_equal "application/xml", @response.content_type assert_equal 422, @response.status @@ -679,11 +635,11 @@ class RespondWithControllerTest < ActionController::TestCase put :using_resource assert_equal "text/html", @response.content_type assert_equal 302, @response.status - assert_equal "www.example.com/resources/13", @response.location + assert_equal "http://www.example.com/customers/13", @response.location assert @response.redirect? errors = { :name => :invalid } - RespondResource.any_instance.stubs(:errors).returns(errors) + Customer.any_instance.stubs(:errors).returns(errors) put :using_resource assert_equal "text/html", @response.content_type assert_equal 200, @response.status @@ -698,10 +654,10 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body - assert_equal "www.example.com/resources/13", @response.location + assert_equal "http://www.example.com/customers/13", @response.location errors = { :name => :invalid } - RespondResource.any_instance.stubs(:errors).returns(errors) + Customer.any_instance.stubs(:errors).returns(errors) put :using_resource assert_equal "application/xml", @response.content_type assert_equal 422, @response.status @@ -710,21 +666,21 @@ class RespondWithControllerTest < ActionController::TestCase end def test_using_resource_for_delete_with_html - RespondResource.any_instance.stubs(:destroyed?).returns(true) + Customer.any_instance.stubs(:destroyed?).returns(true) delete :using_resource assert_equal "text/html", @response.content_type assert_equal 302, @response.status - assert_equal "www.example.com/resources", @response.location + assert_equal "http://www.example.com/customers", @response.location end def test_using_resource_for_delete_with_xml - RespondResource.any_instance.stubs(:destroyed?).returns(true) + Customer.any_instance.stubs(:destroyed?).returns(true) @request.accept = "application/xml" delete :using_resource assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body - assert_equal "www.example.com/resources", @response.location + assert_equal "http://www.example.com/customers", @response.location end def test_using_resource_with_options @@ -756,10 +712,10 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 201, @response.status assert_equal "XML", @response.body - assert_equal "www.example.com/parents/11/resources/13", @response.location + assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location errors = { :name => :invalid } - RespondResource.any_instance.stubs(:errors).returns(errors) + Customer.any_instance.stubs(:errors).returns(errors) post :using_resource assert_equal "application/xml", @response.content_type assert_equal 422, @response.status diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index f2cd6dbb85..9546fdb50d 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -459,7 +459,7 @@ class TestController < ActionController::Base end def head_with_location_object - head :location => Customer.new("david") + head :location => Customer.new("david", 1) end def head_with_symbolic_status @@ -622,9 +622,6 @@ class TestController < ActionController::Base end private - def customer_url(customer) - request.host + "/customers/1" - end def determine_layout case action_name @@ -1093,9 +1090,14 @@ class RenderTest < ActionController::TestCase end def test_head_with_location_object + ActionController::Routing::Routes.draw do |map| + map.resources :customers + map.connect ':controller/:action/:id' + end + get :head_with_location_object assert @response.body.blank? - assert_equal "www.nextangle.com/customers/1", @response.headers["Location"] + assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] assert_response :ok end diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index 052b4f0b52..139f55d8bd 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -11,7 +11,7 @@ class TestController < ActionController::Base def render_with_object_location customer = Customer.new("Some guy", 1) - render :xml => "", :location => customer_url(customer), :status => :created + render :xml => "", :location => customer, :status => :created end def render_with_to_xml @@ -78,4 +78,4 @@ class RenderTest < ActionController::TestCase get :implicit_content_type, :format => 'atom' assert_equal Mime::ATOM, @response.content_type end -end \ No newline at end of file +end diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index c6726432ec..0faf8f3f9a 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -4,9 +4,27 @@ class Customer < Struct.new(:name, :id) extend ActiveModel::Naming include ActiveModel::Conversion + undef_method :to_json + def to_param id.to_s end + + def to_xml + "XML" + end + + def to_js + "JS" + end + + def errors + [] + end + + def destroyed? + false + end end class BadCustomer < Customer @@ -24,4 +42,8 @@ module Quiz id.to_s end end + + class Store < Question + end end + diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e358564ead..531a698f77 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2492,6 +2492,11 @@ module ActiveRecord #:nodoc: @new_record || false end + # Returns true if this object has been destroyed, otherwise returns false. + def destroyed? + @destroyed || false + end + # :call-seq: # save(perform_validation = true) # @@ -2542,6 +2547,7 @@ module ActiveRecord #:nodoc: # options, use #destroy. def delete self.class.delete(id) unless new_record? + @destroyed = true freeze end @@ -2556,6 +2562,7 @@ module ActiveRecord #:nodoc: ) end + @destroyed = true freeze end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 82eba81549..16364141df 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1271,6 +1271,23 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Topic.find(1).new_record?, false end + def test_destroyed_returns_boolean + developer = Developer.new + assert_equal developer.destroyed?, false + developer.destroy + assert_equal developer.destroyed?, true + + developer = Developer.first + assert_equal developer.destroyed?, false + developer.destroy + assert_equal developer.destroyed?, true + + developer = Developer.last + assert_equal developer.destroyed?, false + developer.delete + assert_equal developer.destroyed?, true + end + def test_clone topic = Topic.find(1) cloned_topic = nil -- cgit v1.2.3 From 1fd65c80fcdc6080b9efa27f41dbb7f7d95a17c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 5 Aug 2009 12:36:43 +0200 Subject: Encapsulate respond_with behavior in a presenter. --- .../lib/action_controller/metal/mime_responds.rb | 355 ++++++++++----------- actionpack/test/controller/mime_responds_test.rb | 40 ++- 2 files changed, 183 insertions(+), 212 deletions(-) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 02a88437e3..d43f940774 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,4 +1,146 @@ module ActionController #:nodoc: + + # Presenter is responsible to expose a resource for different mime requests, + # usually depending on the HTTP verb. The presenter is triggered when + # respond_with is called. The simplest case to study is a GET request: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@people) + # end + # end + # + # When a request comes, for example with format :xml, three steps happen: + # + # 1) respond_with searches for a template at people/index.xml; + # + # 2) if the template is not available, it will create a presenter, passing + # the controller and the resource, and invoke :to_xml on it; + # + # 3) if the presenter does not respond_to :to_xml, call to_format on it. + # + # === Builtin HTTP verb semantics + # + # Rails default presenter holds semantics for each HTTP verb. Depending on the + # content type, verb and the resource status, it will behave differently. + # + # Using Rails default presenter, a POST request could be written as: + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # Which is exactly the same as: + # + # def create + # @user = User.new(params[:user]) + # + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user, :status => :created, :location => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } + # end + # end + # end + # + # The same happens for PUT and DELETE requests. By default, it accepts just + # :location as parameter, which is used as redirect destination, in both + # POST, PUT, DELETE requests for HTML mime, as in the example below: + # + # def destroy + # @person = Person.find(params[:id]) + # @person.destroy + # respond_with(@person, :location => root_url) + # end + # + # === Nested resources + # + # You can given nested resource as you do in form_for and polymorphic_url. + # Consider the project has many tasks example. The create action for + # TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with([@project, @task]) + # end + # + # Given a nested resource, you ensure that the presenter will redirect to + # project_task_url instead of task_url. + # + # Namespaced and singleton resources requires a symbol to be given, as in + # polymorphic urls. If a project has one manager which has many tasks, it + # should be invoked as: + # + # respond_with([@project, :manager, @task]) + # + # Check polymorphic_url documentation for more examples. + # + class Presenter + attr_reader :controller, :request, :format, :resource, :resource_location, :options + + def initialize(controller, resource, options) + @controller = controller + @request = controller.request + @format = controller.formats.first + @resource = resource.is_a?(Array) ? resource.last : resource + @resource_location = options[:location] || resource + @options = options + end + + delegate :head, :render, :redirect_to, :to => :controller + delegate :get?, :post?, :put?, :delete?, :to => :request + + # Undefine :to_json since it's defined on Object + undef_method :to_json + + def to_html + if get? + render + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end + end + + def to_format + return render unless resourceful? + + if get? + render format => resource + elsif has_errors? + render format => resource.errors, :status => :unprocessable_entity + elsif post? + render format => resource, :status => :created, :location => resource_location + else + head :ok + end + end + + def resourceful? + resource.respond_to?(:"to_#{format}") + end + + def has_errors? + resource.respond_to?(:errors) && !resource.errors.empty? + end + + def default_action + request.post? ? :new : :edit + end + end + module MimeResponds #:nodoc: extend ActiveSupport::Concern @@ -197,214 +339,47 @@ module ActionController #:nodoc: end end - # respond_with allows you to respond an action with a given resource. It - # requires that you set your class with a respond_to method with the - # formats allowed: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.find(:all) - # respond_with(@people) - # end - # end - # - # When a request comes, for example with format :xml, three steps happen: - # - # 1) respond_with searches for a template at people/index.xml; - # - # 2) if the template is not available, it will check if the given - # resource responds to :to_xml. - # - # 3) if a :location option was provided, redirect to the location with - # redirect status if a string was given, or render an action if a - # symbol was given. - # - # If all steps fail, a missing template error will be raised. - # - # === Supported options - # - # [status] - # Sets the response status. - # - # [head] - # Tell respond_with to set the content type, status and location header, - # but do not render the object, leaving the response body empty. This - # option only has effect if the resource is being rendered. If a - # template was found, it's going to be rendered anyway. - # - # [location] - # Sets the location header with the given value. It accepts a string, - # representing the location header value, or a symbol representing an - # action name. - # - # === Builtin HTTP verb semantics - # - # respond_with holds semantics for each HTTP verb. Depending on the verb - # and the resource status, respond_with will automatically set the options - # above. - # - # Above we saw an example for GET requests, where actually no option is - # configured. A create action for POST requests, could be written as: - # - # def create - # @person = Person.new(params[:person]) - # @person.save - # respond_with(@person) - # end + # respond_with wraps a resource around a presenter for default representation. + # First it invokes respond_to, if a response cannot be found (ie. no block + # for the request was given and template was not available), it instantiates + # an ActionController::Presenter with the controller and resource. # - # respond_with will inspect the @person object and check if we have any - # error. If errors are empty, it will add status and location to the options - # hash. Then the create action in case of success, is equivalent to this: + # ==== Example # - # respond_with(@person, :status => :created, :location => @person) - # - # From them on, the lookup happens as described above. Let's suppose a :xml - # request and we don't have a people/create.xml template. But since the - # @person object responds to :to_xml, it will render the newly created - # resource and set status and location. - # - # However, if the request is :html, a template is not available and @person - # does not respond to :to_html. But since a :location options was provided, - # it will redirect to it. - # - # In case of failures (when the @person could not be saved and errors are - # not empty), respond_with can be expanded as this: - # - # respond_with(@person.errors, :status => :unprocessable_entity, :location => :new) - # - # In other words, respond_with(@person) for POST requests is expanded - # internally into this: - # - # def create - # @person = Person.new(params[:person]) - # - # if @person.save - # respond_with(@person, :status => :created, :location => @person) - # else - # respond_with(@person.errors, :status => :unprocessable_entity, :location => :new) - # end - # end - # - # For an update action for PUT requests, we would have: - # - # def update - # @person = Person.find(params[:id]) - # @person.update_attributes(params[:person]) - # respond_with(@person) - # end - # - # Which, in face of success and failure scenarios, can be expanded as: - # - # def update - # @person = Person.find(params[:id]) - # @person.update_attributes(params[:person]) - # - # if @person.save - # respond_with(@person, :status => :ok, :location => @person, :head => true) - # else - # respond_with(@person.errors, :status => :unprocessable_entity, :location => :edit) - # end - # end - # - # Notice that in case of success, we just need to reply :ok to the client. - # The option :head ensures that the object is not rendered. - # - # Finally, we have the destroy action with DELETE verb: - # - # def destroy - # @person = Person.find(params[:id]) - # @person.destroy - # respond_with(@person) + # def index + # @users = User.all + # respond_with(@users) # end # - # Which is expanded as: + # It also accepts a block to be given. It's used to overwrite a default + # response: # # def destroy - # @person = Person.find(params[:id]) - # @person.destroy - # respond_with(@person, :status => :ok, :location => @person, :head => true) - # end - # - # In this case, since @person.destroyed? returns true, polymorphic urls will - # redirect to the collection url, instead of the resource url. + # @user = User.find(params[:id]) + # flash[:notice] = "User was successfully created." if @user.save # - # === Nested resources - # - # respond_with also works with nested resources, you just need to pass them - # as you do in form_for and polymorphic_url. Consider the project has many - # tasks example. The create action for TasksController would be like: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) - # @task.save - # respond_with([@project, @task]) + # respond_with(@user) do |format| + # format.html { render } + # end # end # - # Namespaced and singleton resources requires a symbol to be given, as in - # polymorphic urls. If a project has one manager with has many tasks, it - # should be invoked as: - # - # respond_with([@project, :manager, @task]) - # - # Be sure to check polymorphic_url documentation. The only occasion you will - # need to give clear input to respond_with is in DELETE verbs for singleton - # resources. In such cases, the collection url does not exist, so you need - # to supply the destination url after delete: - # - # def destroy - # @project = Project.find(params[:project_id]) - # @manager = @project.manager - # @manager.destroy - # respond_with([@project, @manager], :location => root_url) - # end + # All options given to respond_with are sent to the underlying presenter. # def respond_with(resource, options={}, &block) respond_to(&block) - rescue ActionView::MissingTemplate => e - format = self.formats.first - resource = normalize_resource_options_by_verb(resource, options) - action = options.delete(:location) if options[:location].is_a?(Symbol) - - if resource.respond_to?(:"to_#{format}") - options.delete(:head) ? head(options) : render(options.merge(format => resource)) - elsif action - render :action => action - elsif options[:location] - redirect_to options[:location] + rescue ActionView::MissingTemplate + presenter = ActionController::Presenter.new(self, resource, options) + format_method = :"to_#{self.formats.first}" + + if presenter.respond_to?(format_method) + presenter.send(format_method) else - raise e + presenter.to_format end end protected - # Change respond with behavior based on the HTTP verb. - # - def normalize_resource_options_by_verb(resource_or_array, options) - resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array - - if resource.respond_to?(:errors) && !resource.errors.empty? - options[:status] ||= :unprocessable_entity - options[:location] ||= :new if request.post? - options[:location] ||= :edit if request.put? - return resource.errors - elsif !request.get? - options[:location] ||= resource_or_array - - if request.post? - options[:status] ||= :created - else - options[:status] ||= :ok - options[:head] = true unless options.key?(:head) - end - end - - return resource - end - # Collect mimes declared in the class method respond_to valid for the # current action. # diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index faa84e1971..157e837503 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -497,16 +497,14 @@ class RespondWithController < ActionController::Base respond_with(Customer.new("david", 13)) end - def using_resource_with_options - respond_with(Customer.new("david", 13), :status => :unprocessable_entity) do |format| - format.js - end - end - def using_resource_with_parent respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)]) end + def using_resource_with_location + respond_with(Customer.new("david", 13), :location => "http://test.host/") + end + protected def _render_js(js, options) @@ -654,7 +652,6 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body - assert_equal "http://www.example.com/customers/13", @response.location errors = { :name => :invalid } Customer.any_instance.stubs(:errors).returns(errors) @@ -680,21 +677,6 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "application/xml", @response.content_type assert_equal 200, @response.status assert_equal " ", @response.body - assert_equal "http://www.example.com/customers", @response.location - end - - def test_using_resource_with_options - @request.accept = "application/xml" - get :using_resource_with_options - assert_equal "application/xml", @response.content_type - assert_equal 422, @response.status - assert_equal "XML", @response.body - - @request.accept = "text/javascript" - get :using_resource_with_options - assert_equal "text/javascript", @response.content_type - assert_equal 422, @response.status - assert_equal "JS", @response.body end def test_using_resource_with_parent_for_get @@ -738,6 +720,20 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "XML", @response.body end + def test_no_double_render_is_raised + @request.accept = "text/html" + assert_raise ActionView::MissingTemplate do + get :using_resource + end + end + + def test_using_resource_with_location + @request.accept = "text/html" + post :using_resource_with_location + assert @response.redirect? + assert_equal "http://test.host/", @response.location + end + def test_not_acceptable @request.accept = "application/xml" get :using_defaults -- cgit v1.2.3 From aed135d3e261cbee153a35fcfbeb47e2e02b12e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 6 Aug 2009 23:48:48 +0200 Subject: Renamed presenter to renderer, added some documentation and defined its API. --- actionpack/examples/very_simple.rb | 4 +- actionpack/lib/abstract_controller.rb | 20 +- actionpack/lib/abstract_controller/helpers.rb | 2 +- actionpack/lib/abstract_controller/layouts.rb | 2 +- actionpack/lib/abstract_controller/renderer.rb | 156 --------------- .../abstract_controller/rendering_controller.rb | 156 +++++++++++++++ actionpack/lib/action_controller.rb | 4 +- actionpack/lib/action_controller/base.rb | 4 +- actionpack/lib/action_controller/metal/layouts.rb | 2 +- .../lib/action_controller/metal/mime_responds.rb | 162 +--------------- .../lib/action_controller/metal/render_options.rb | 10 +- actionpack/lib/action_controller/metal/renderer.rb | 215 ++++++++++++++++----- .../metal/rendering_controller.rb | 66 +++++++ .../lib/action_controller/metal/streaming.rb | 2 +- .../lib/action_controller/metal/verification.rb | 4 +- .../abstract_controller_test.rb | 6 +- actionpack/test/abstract_controller/helper_test.rb | 4 +- .../test/abstract_controller/layouts_test.rb | 2 +- actionpack/test/controller/mime_responds_test.rb | 22 ++- 19 files changed, 448 insertions(+), 395 deletions(-) delete mode 100644 actionpack/lib/abstract_controller/renderer.rb create mode 100644 actionpack/lib/abstract_controller/rendering_controller.rb create mode 100644 actionpack/lib/action_controller/metal/rendering_controller.rb diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb index 422c4c3298..6714185172 100644 --- a/actionpack/examples/very_simple.rb +++ b/actionpack/examples/very_simple.rb @@ -6,7 +6,7 @@ require "action_controller" class Kaigi < ActionController::Metal include AbstractController::Callbacks include ActionController::RackConvenience - include ActionController::Renderer + include ActionController::RenderingController include ActionController::Layouts include ActionView::Context @@ -47,4 +47,4 @@ app = Rack::Builder.new do map("/kaigi/alt") { run Kaigi.action(:alt) } end.to_app -Rack::Handler::Mongrel.run app, :Port => 3000 \ No newline at end of file +Rack::Handler::Mongrel.run app, :Port => 3000 diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index f020abaa45..cdeb55b915 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -2,15 +2,15 @@ require "active_support/core_ext/module/attr_internal" require "active_support/core_ext/module/delegation" module AbstractController - autoload :Base, "abstract_controller/base" - autoload :Benchmarker, "abstract_controller/benchmarker" - autoload :Callbacks, "abstract_controller/callbacks" - autoload :Helpers, "abstract_controller/helpers" - autoload :Layouts, "abstract_controller/layouts" - autoload :Logger, "abstract_controller/logger" - autoload :Renderer, "abstract_controller/renderer" + autoload :Base, "abstract_controller/base" + autoload :Benchmarker, "abstract_controller/benchmarker" + autoload :Callbacks, "abstract_controller/callbacks" + autoload :Helpers, "abstract_controller/helpers" + autoload :Layouts, "abstract_controller/layouts" + autoload :Logger, "abstract_controller/logger" + autoload :RenderingController, "abstract_controller/rendering_controller" # === Exceptions - autoload :ActionNotFound, "abstract_controller/exceptions" - autoload :DoubleRenderError, "abstract_controller/exceptions" - autoload :Error, "abstract_controller/exceptions" + autoload :ActionNotFound, "abstract_controller/exceptions" + autoload :DoubleRenderError, "abstract_controller/exceptions" + autoload :Error, "abstract_controller/exceptions" end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 5efa37fde3..04eaa02441 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -2,7 +2,7 @@ module AbstractController module Helpers extend ActiveSupport::Concern - include Renderer + include RenderingController included do extlib_inheritable_accessor(:_helpers) { Module.new } diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 038598a3b3..0cb3d166f7 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -2,7 +2,7 @@ module AbstractController module Layouts extend ActiveSupport::Concern - include Renderer + include RenderingController included do extlib_inheritable_accessor(:_layout_conditions) { Hash.new } diff --git a/actionpack/lib/abstract_controller/renderer.rb b/actionpack/lib/abstract_controller/renderer.rb deleted file mode 100644 index da57d0ff4d..0000000000 --- a/actionpack/lib/abstract_controller/renderer.rb +++ /dev/null @@ -1,156 +0,0 @@ -require "abstract_controller/logger" - -module AbstractController - module Renderer - extend ActiveSupport::Concern - - include AbstractController::Logger - - included do - attr_internal :formats - - extlib_inheritable_accessor :_view_paths - - self._view_paths ||= ActionView::PathSet.new - end - - # An instance of a view class. The default view class is ActionView::Base - # - # The view class must have the following methods: - # View.for_controller[controller] Create a new ActionView instance for a - # controller - # View#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options:: see _render_partial in ActionView::Base - # View#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template:: The template to render - # layout:: The layout to render around the template - # options:: See _render_template_with_layout in ActionView::Base - # partial:: Whether or not the template to render is a partial - # - # Override this method in a to change the default behavior. - def view_context - @_view_context ||= ActionView::Base.for_controller(self) - end - - # Mostly abstracts the fact that calling render twice is a DoubleRenderError. - # Delegates render_to_body and sticks the result in self.response_body. - def render(*args) - if response_body - raise AbstractController::DoubleRenderError, "OMG" - end - - self.response_body = render_to_body(*args) - end - - # Raw rendering of a template to a Rack-compatible body. - # - # ==== Options - # _partial_object:: The object that is being rendered. If this - # exists, we are in the special case of rendering an object as a partial. - # - # :api: plugin - def render_to_body(options = {}) - # TODO: Refactor so we can just use the normal template logic for this - if options.key?(:partial) - view_context.render_partial(options) - else - _determine_template(options) - _render_template(options) - end - end - - # Raw rendering of a template to a string. Just convert the results of - # render_to_body into a String. - # - # :api: plugin - def render_to_string(options = {}) - AbstractController::Renderer.body_to_s(render_to_body(options)) - end - - # Renders the template from an object. - # - # ==== Options - # _template:: The template to render - # _layout:: The layout to wrap the template in (optional) - # _partial:: Whether or not the template to be rendered is a partial - def _render_template(options) - view_context.render_template(options) - end - - # The list of view paths for this controller. See ActionView::ViewPathSet for - # more details about writing custom view paths. - def view_paths - _view_paths - end - - # Return a string representation of a Rack-compatible response body. - def self.body_to_s(body) - if body.respond_to?(:to_str) - body - else - strings = [] - body.each { |part| strings << part.to_s } - body.close if body.respond_to?(:close) - strings.join - end - end - - private - # Take in a set of options and determine the template to render - # - # ==== Options - # _template:: If this is provided, the search is over - # _template_name<#to_s>:: The name of the template to look up. Otherwise, - # use the current action name. - # _prefix:: The prefix to look inside of. In a file system, this corresponds - # to a directory. - # _partial:: Whether or not the file to look up is a partial - def _determine_template(options) - name = (options[:_template_name] || action_name).to_s - - options[:_template] ||= view_paths.find_by_parts( - name, { :formats => formats }, options[:_prefix], options[:_partial] - ) - end - - module ClassMethods - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # path:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def append_view_path(path) - self.view_paths << path - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # path:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def prepend_view_path(path) - self.view_paths.unshift(path) - end - - # A list of all of the default view paths for this controller. - def view_paths - self._view_paths - end - - # Set the view paths. - # - # ==== Parameters - # paths:: If a ViewPathSet is provided, use that; - # otherwise, process the parameter into a ViewPathSet. - def view_paths=(paths) - self._view_paths = paths.is_a?(ActionView::PathSet) ? - paths : ActionView::Base.process_view_paths(paths) - end - end - end -end diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb new file mode 100644 index 0000000000..23cd71e0bd --- /dev/null +++ b/actionpack/lib/abstract_controller/rendering_controller.rb @@ -0,0 +1,156 @@ +require "abstract_controller/logger" + +module AbstractController + module RenderingController + extend ActiveSupport::Concern + + include AbstractController::Logger + + included do + attr_internal :formats + + extlib_inheritable_accessor :_view_paths + + self._view_paths ||= ActionView::PathSet.new + end + + # An instance of a view class. The default view class is ActionView::Base + # + # The view class must have the following methods: + # View.for_controller[controller] Create a new ActionView instance for a + # controller + # View#render_partial[options] + # - responsible for setting options[:_template] + # - Returns String with the rendered partial + # options:: see _render_partial in ActionView::Base + # View#render_template[template, layout, options, partial] + # - Returns String with the rendered template + # template:: The template to render + # layout:: The layout to render around the template + # options:: See _render_template_with_layout in ActionView::Base + # partial:: Whether or not the template to render is a partial + # + # Override this method in a to change the default behavior. + def view_context + @_view_context ||= ActionView::Base.for_controller(self) + end + + # Mostly abstracts the fact that calling render twice is a DoubleRenderError. + # Delegates render_to_body and sticks the result in self.response_body. + def render(*args) + if response_body + raise AbstractController::DoubleRenderError, "OMG" + end + + self.response_body = render_to_body(*args) + end + + # Raw rendering of a template to a Rack-compatible body. + # + # ==== Options + # _partial_object:: The object that is being rendered. If this + # exists, we are in the special case of rendering an object as a partial. + # + # :api: plugin + def render_to_body(options = {}) + # TODO: Refactor so we can just use the normal template logic for this + if options.key?(:partial) + view_context.render_partial(options) + else + _determine_template(options) + _render_template(options) + end + end + + # Raw rendering of a template to a string. Just convert the results of + # render_to_body into a String. + # + # :api: plugin + def render_to_string(options = {}) + AbstractController::RenderingController.body_to_s(render_to_body(options)) + end + + # Renders the template from an object. + # + # ==== Options + # _template:: The template to render + # _layout:: The layout to wrap the template in (optional) + # _partial:: Whether or not the template to be rendered is a partial + def _render_template(options) + view_context.render_template(options) + end + + # The list of view paths for this controller. See ActionView::ViewPathSet for + # more details about writing custom view paths. + def view_paths + _view_paths + end + + # Return a string representation of a Rack-compatible response body. + def self.body_to_s(body) + if body.respond_to?(:to_str) + body + else + strings = [] + body.each { |part| strings << part.to_s } + body.close if body.respond_to?(:close) + strings.join + end + end + + private + # Take in a set of options and determine the template to render + # + # ==== Options + # _template:: If this is provided, the search is over + # _template_name<#to_s>:: The name of the template to look up. Otherwise, + # use the current action name. + # _prefix:: The prefix to look inside of. In a file system, this corresponds + # to a directory. + # _partial:: Whether or not the file to look up is a partial + def _determine_template(options) + name = (options[:_template_name] || action_name).to_s + + options[:_template] ||= view_paths.find_by_parts( + name, { :formats => formats }, options[:_prefix], options[:_partial] + ) + end + + module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # path:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def append_view_path(path) + self.view_paths << path + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # path:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def prepend_view_path(path) + self.view_paths.unshift(path) + end + + # A list of all of the default view paths for this controller. + def view_paths + self._view_paths + end + + # Set the view paths. + # + # ==== Parameters + # paths:: If a ViewPathSet is provided, use that; + # otherwise, process the parameter into a ViewPathSet. + def view_paths=(paths) + self._view_paths = paths.is_a?(ActionView::PathSet) ? + paths : ActionView::Base.process_view_paths(paths) + end + end + end +end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 22c2c4f499..8343a87936 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -8,8 +8,8 @@ module ActionController autoload :Rails2Compatibility, "action_controller/metal/compatibility" autoload :Redirector, "action_controller/metal/redirector" autoload :Renderer, "action_controller/metal/renderer" + autoload :RenderingController, "action_controller/metal/rendering_controller" autoload :RenderOptions, "action_controller/metal/render_options" - autoload :Renderers, "action_controller/metal/render_options" autoload :Rescue, "action_controller/metal/rescuable" autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" @@ -69,4 +69,4 @@ require 'active_support/core_ext/load_error' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/name_error' -require 'active_support/inflector' \ No newline at end of file +require 'active_support/inflector' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9d9f735e27..40f4802b74 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -10,8 +10,8 @@ module ActionController include ActionController::HideActions include ActionController::UrlFor include ActionController::Redirector - include ActionController::Renderer - include ActionController::Renderers::All + include ActionController::RenderingController + include ActionController::RenderOptions::All include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackConvenience diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb index 365351b421..cac529b1ae 100644 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -158,7 +158,7 @@ module ActionController module Layouts extend ActiveSupport::Concern - include ActionController::Renderer + include ActionController::RenderingController include AbstractController::Layouts module ClassMethods diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d43f940774..d823dd424a 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,146 +1,4 @@ module ActionController #:nodoc: - - # Presenter is responsible to expose a resource for different mime requests, - # usually depending on the HTTP verb. The presenter is triggered when - # respond_with is called. The simplest case to study is a GET request: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.find(:all) - # respond_with(@people) - # end - # end - # - # When a request comes, for example with format :xml, three steps happen: - # - # 1) respond_with searches for a template at people/index.xml; - # - # 2) if the template is not available, it will create a presenter, passing - # the controller and the resource, and invoke :to_xml on it; - # - # 3) if the presenter does not respond_to :to_xml, call to_format on it. - # - # === Builtin HTTP verb semantics - # - # Rails default presenter holds semantics for each HTTP verb. Depending on the - # content type, verb and the resource status, it will behave differently. - # - # Using Rails default presenter, a POST request could be written as: - # - # def create - # @user = User.new(params[:user]) - # flash[:notice] = 'User was successfully created.' if @user.save - # respond_with(@user) - # end - # - # Which is exactly the same as: - # - # def create - # @user = User.new(params[:user]) - # - # respond_to do |format| - # if @user.save - # flash[:notice] = 'User was successfully created.' - # format.html { redirect_to(@user) } - # format.xml { render :xml => @user, :status => :created, :location => @user } - # else - # format.html { render :action => "new" } - # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } - # end - # end - # end - # - # The same happens for PUT and DELETE requests. By default, it accepts just - # :location as parameter, which is used as redirect destination, in both - # POST, PUT, DELETE requests for HTML mime, as in the example below: - # - # def destroy - # @person = Person.find(params[:id]) - # @person.destroy - # respond_with(@person, :location => root_url) - # end - # - # === Nested resources - # - # You can given nested resource as you do in form_for and polymorphic_url. - # Consider the project has many tasks example. The create action for - # TasksController would be like: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) - # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with([@project, @task]) - # end - # - # Given a nested resource, you ensure that the presenter will redirect to - # project_task_url instead of task_url. - # - # Namespaced and singleton resources requires a symbol to be given, as in - # polymorphic urls. If a project has one manager which has many tasks, it - # should be invoked as: - # - # respond_with([@project, :manager, @task]) - # - # Check polymorphic_url documentation for more examples. - # - class Presenter - attr_reader :controller, :request, :format, :resource, :resource_location, :options - - def initialize(controller, resource, options) - @controller = controller - @request = controller.request - @format = controller.formats.first - @resource = resource.is_a?(Array) ? resource.last : resource - @resource_location = options[:location] || resource - @options = options - end - - delegate :head, :render, :redirect_to, :to => :controller - delegate :get?, :post?, :put?, :delete?, :to => :request - - # Undefine :to_json since it's defined on Object - undef_method :to_json - - def to_html - if get? - render - elsif has_errors? - render :action => default_action - else - redirect_to resource_location - end - end - - def to_format - return render unless resourceful? - - if get? - render format => resource - elsif has_errors? - render format => resource.errors, :status => :unprocessable_entity - elsif post? - render format => resource, :status => :created, :location => resource_location - else - head :ok - end - end - - def resourceful? - resource.respond_to?(:"to_#{format}") - end - - def has_errors? - resource.respond_to?(:errors) && !resource.errors.empty? - end - - def default_action - request.post? ? :new : :edit - end - end - module MimeResponds #:nodoc: extend ActiveSupport::Concern @@ -339,10 +197,10 @@ module ActionController #:nodoc: end end - # respond_with wraps a resource around a presenter for default representation. + # respond_with wraps a resource around a renderer for default representation. # First it invokes respond_to, if a response cannot be found (ie. no block # for the request was given and template was not available), it instantiates - # an ActionController::Presenter with the controller and resource. + # an ActionController::Renderer with the controller and resource. # # ==== Example # @@ -363,19 +221,19 @@ module ActionController #:nodoc: # end # end # - # All options given to respond_with are sent to the underlying presenter. + # All options given to respond_with are sent to the underlying renderer, + # except for the option :renderer itself. Since the renderer interface + # is quite simple (it just needs to respond to call), you can even give + # a proc to it. # def respond_with(resource, options={}, &block) respond_to(&block) rescue ActionView::MissingTemplate - presenter = ActionController::Presenter.new(self, resource, options) - format_method = :"to_#{self.formats.first}" + (options.delete(:renderer) || renderer).call(self, resource, options) + end - if presenter.respond_to?(format_method) - presenter.send(format_method) - else - presenter.to_format - end + def renderer + ActionController::Renderer end protected diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb index c6a965472f..0d69ca10df 100644 --- a/actionpack/lib/action_controller/metal/render_options.rb +++ b/actionpack/lib/action_controller/metal/render_options.rb @@ -47,7 +47,7 @@ module ActionController end end - module Renderers + module RenderOptions module Json extend RenderOption register_renderer :json @@ -94,10 +94,10 @@ module ActionController module All extend ActiveSupport::Concern - include ActionController::Renderers::Json - include ActionController::Renderers::Js - include ActionController::Renderers::Xml - include ActionController::Renderers::RJS + include ActionController::RenderOptions::Json + include ActionController::RenderOptions::Js + include ActionController::RenderOptions::Xml + include ActionController::RenderOptions::RJS end end end diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/renderer.rb index 31ba7e582a..39ab2407aa 100644 --- a/actionpack/lib/action_controller/metal/renderer.rb +++ b/actionpack/lib/action_controller/metal/renderer.rb @@ -1,66 +1,181 @@ -module ActionController - module Renderer - extend ActiveSupport::Concern +module ActionController #:nodoc: + # Renderer is responsible to expose a resource for different mime requests, + # usually depending on the HTTP verb. The renderer is triggered when + # respond_with is called. The simplest case to study is a GET request: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@people) + # end + # end + # + # When a request comes, for example with format :xml, three steps happen: + # + # 1) respond_with searches for a template at people/index.xml; + # + # 2) if the template is not available, it will create a renderer, passing + # the controller and the resource and invoke :to_xml on it; + # + # 3) if the renderer does not respond_to :to_xml, call to_format on it. + # + # === Builtin HTTP verb semantics + # + # Rails default renderer holds semantics for each HTTP verb. Depending on the + # content type, verb and the resource status, it will behave differently. + # + # Using Rails default renderer, a POST request for creating an object could + # be written as: + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # Which is exactly the same as: + # + # def create + # @user = User.new(params[:user]) + # + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user, :status => :created, :location => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } + # end + # end + # end + # + # The same happens for PUT and DELETE requests. + # + # === Nested resources + # + # You can given nested resource as you do in form_for and polymorphic_url. + # Consider the project has many tasks example. The create action for + # TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with([@project, @task]) + # end + # + # Giving an array of resources, you ensure that the renderer will redirect to + # project_task_url instead of task_url. + # + # Namespaced and singleton resources requires a symbol to be given, as in + # polymorphic urls. If a project has one manager which has many tasks, it + # should be invoked as: + # + # respond_with([@project, :manager, @task]) + # + # Check polymorphic_url documentation for more examples. + # + class Renderer + attr_reader :controller, :request, :format, :resource, :resource_location, :options - include AbstractController::Renderer + def initialize(controller, resource, options={}) + @controller = controller + @request = controller.request + @format = controller.formats.first + @resource = resource.is_a?(Array) ? resource.last : resource + @resource_location = options[:location] || resource + @options = options + end + + delegate :head, :render, :redirect_to, :to => :controller + delegate :get?, :post?, :put?, :delete?, :to => :request - def process_action(*) - self.formats = request.formats.map {|x| x.to_sym} - super + # Undefine :to_json since it's defined on Object + undef_method :to_json + + # Initializes a new renderer an invoke the proper format. If the format is + # not defined, call to_format. + # + def self.call(*args) + renderer = new(*args) + method = :"to_#{renderer.format}" + renderer.respond_to?(method) ? renderer.send(method) : renderer.to_format end - def render(options) - super - self.content_type ||= begin - mime = options[:_template].mime_type - formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first) - end.to_s - response_body + # HTML format does not render the resource, it always attempt to render a + # template. + # + def to_html + if get? + render + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end end - def render_to_body(options) - _process_options(options) + # All others formats try to render the resource given instead. For this + # purpose a helper called display as a shortcut to render a resource with + # the current format. + # + def to_format + return render unless resourceful? - if options.key?(:partial) - options[:partial] = action_name if options[:partial] == true - options[:_details] = {:formats => formats} + if get? + display resource + elsif has_errors? + display resource.errors, :status => :unprocessable_entity + elsif post? + display resource, :status => :created, :location => resource_location + else + head :ok end - - super end - private - def _prefix - controller_path - end + protected - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] - elsif !options.key?(:partial) - options[:_template_name] = (options[:action] || action_name).to_s - options[:_prefix] = _prefix - end + # Checks whether the resource responds to the current format or not. + # + def resourceful? + resource.respond_to?(:"to_#{format}") + end - super - end + # display is just a shortcut to render a resource with the current format. + # + # display @user, :status => :ok + # + # For xml request is equivalent to: + # + # render :xml => @user, :status => :ok + # + # Options sent by the user are also used: + # + # respond_with(@user, :status => :created) + # display(@user, :status => :ok) + # + # Results in: + # + # render :xml => @user, :status => :created + # + def display(resource, given_options={}) + render given_options.merge!(options).merge!(format => resource) + end - def _render_partial(partial, options) - end + # Check if the resource has errors or not. + # + def has_errors? + resource.respond_to?(:errors) && !resource.errors.empty? + end - def _process_options(options) - status, content_type, location = options.values_at(:status, :content_type, :location) - self.status = status if status - self.content_type = content_type if content_type - self.headers["Location"] = url_for(location) if location - end + # By default, render the :edit action for html requests with failure, unless + # the verb is post. + # + def default_action + request.post? ? :new : :edit + end end end diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb new file mode 100644 index 0000000000..c8922290f3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/rendering_controller.rb @@ -0,0 +1,66 @@ +module ActionController + module RenderingController + extend ActiveSupport::Concern + + include AbstractController::RenderingController + + def process_action(*) + self.formats = request.formats.map {|x| x.to_sym} + super + end + + def render(options) + super + self.content_type ||= begin + mime = options[:_template].mime_type + formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first) + end.to_s + response_body + end + + def render_to_body(options) + _process_options(options) + + if options.key?(:partial) + options[:partial] = action_name if options[:partial] == true + options[:_details] = {:formats => formats} + end + + super + end + + private + def _prefix + controller_path + end + + def _determine_template(options) + if options.key?(:text) + options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) + elsif options.key?(:inline) + handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") + template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) + options[:_template] = template + elsif options.key?(:template) + options[:_template_name] = options[:template] + elsif options.key?(:file) + options[:_template_name] = options[:file] + elsif !options.key?(:partial) + options[:_template_name] = (options[:action] || action_name).to_s + options[:_prefix] = _prefix + end + + super + end + + def _render_partial(partial, options) + end + + def _process_options(options) + status, content_type, location = options.values_at(:status, :content_type, :location) + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location + end + end +end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index f0317c6e99..57318e8747 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -6,7 +6,7 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - include ActionController::Renderer + include ActionController::RenderingController DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb index 951ae1bee1..d3d78e3749 100644 --- a/actionpack/lib/action_controller/metal/verification.rb +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -2,7 +2,7 @@ module ActionController #:nodoc: module Verification #:nodoc: extend ActiveSupport::Concern - include AbstractController::Callbacks, Session, Flash, Renderer + include AbstractController::Callbacks, Session, Flash, RenderingController # This module provides a class-level method for specifying that certain # actions are guarded against being called without certain prerequisites @@ -127,4 +127,4 @@ module ActionController #:nodoc: end end end -end \ No newline at end of file +end diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb index 56ec6a6a31..10b97f4eb4 100644 --- a/actionpack/test/abstract_controller/abstract_controller_test.rb +++ b/actionpack/test/abstract_controller/abstract_controller_test.rb @@ -27,7 +27,7 @@ module AbstractController # Test Render mixin # ==== class RenderingController < AbstractController::Base - include Renderer + include ::AbstractController::RenderingController def _prefix() end @@ -65,8 +65,8 @@ module AbstractController self.response_body = render_to_string :_template_name => "naked_render.erb" end end - - class TestRenderer < ActiveSupport::TestCase + + class TestRenderingController < ActiveSupport::TestCase test "rendering templates works" do result = Me2.new.process(:index) assert_equal "Hello from index.erb", result.response_body diff --git a/actionpack/test/abstract_controller/helper_test.rb b/actionpack/test/abstract_controller/helper_test.rb index 0a2535f834..cd6d4ee90e 100644 --- a/actionpack/test/abstract_controller/helper_test.rb +++ b/actionpack/test/abstract_controller/helper_test.rb @@ -4,7 +4,7 @@ module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base - include Renderer + include RenderingController include Helpers def render(string) @@ -40,4 +40,4 @@ module AbstractController end end -end \ No newline at end of file +end diff --git a/actionpack/test/abstract_controller/layouts_test.rb b/actionpack/test/abstract_controller/layouts_test.rb index b28df7743f..42f73faa61 100644 --- a/actionpack/test/abstract_controller/layouts_test.rb +++ b/actionpack/test/abstract_controller/layouts_test.rb @@ -6,7 +6,7 @@ module AbstractControllerTests # Base controller for these tests class Base < AbstractController::Base - include AbstractController::Renderer + include AbstractController::RenderingController include AbstractController::Layouts self.view_paths = [ActionView::FixtureResolver.new( diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 157e837503..00ad90ff68 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -501,8 +501,13 @@ class RespondWithController < ActionController::Base respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)]) end - def using_resource_with_location - respond_with(Customer.new("david", 13), :location => "http://test.host/") + def using_resource_with_status_and_location + respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created) + end + + def using_resource_with_renderer + renderer = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" } + respond_with(Customer.new("david", 13), :renderer => renderer) end protected @@ -727,11 +732,20 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_with_location + def test_using_resource_with_status_and_location @request.accept = "text/html" - post :using_resource_with_location + post :using_resource_with_status_and_location assert @response.redirect? assert_equal "http://test.host/", @response.location + + @request.accept = "application/xml" + get :using_resource_with_status_and_location + assert_equal 201, @response.status + end + + def test_using_resource_with_renderer + get :using_resource_with_renderer + assert_equal "Resource name is david", @response.body end def test_not_acceptable -- cgit v1.2.3 From 55575e21655023259d0dae22bc1b148b34168d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 7 Aug 2009 15:34:10 +0200 Subject: Allow rails templates relative to the current path to be given. --- railties/lib/generators/rails/app/app_generator.rb | 21 ++++++++++++++++++--- railties/test/fixtures/lib/template.rb | 1 + railties/test/generators/app_generator_test.rb | 5 +++++ railties/test/generators/generators_test_helper.rb | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 railties/test/fixtures/lib/template.rb diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb index c8044d13b1..c80a344e0d 100644 --- a/railties/lib/generators/rails/app/app_generator.rb +++ b/railties/lib/generators/rails/app/app_generator.rb @@ -49,7 +49,7 @@ module Rails::Generators self.destination_root = File.expand_path(app_path, destination_root) empty_directory '.' - app_name # Sets the app name + set_default_accessors! FileUtils.cd(destination_root) end @@ -164,9 +164,9 @@ module Rails::Generators end def apply_rails_template - apply options[:template] if options[:template] + apply rails_template if rails_template rescue Thor::Error, LoadError, Errno::ENOENT => e - raise Error, "The template [#{options[:template]}] could not be loaded. Error: #{e}" + raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" end def freeze? @@ -175,6 +175,21 @@ module Rails::Generators protected + attr_accessor :rails_template + + def set_default_accessors! + app_name # Cache app name + + 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) diff --git a/railties/test/fixtures/lib/template.rb b/railties/test/fixtures/lib/template.rb new file mode 100644 index 0000000000..c14a1a8784 --- /dev/null +++ b/railties/test/fixtures/lib/template.rb @@ -0,0 +1 @@ +say "It works from file!" diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c794a2ade6..19e41c15c8 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -131,6 +131,11 @@ class AppGeneratorTest < GeneratorsTestCase assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/ end + def test_template_from_dir_pwd + FileUtils.cd(RAILS_ROOT) + assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"]) + end + def test_template_raises_an_error_with_invalid_path content = capture(:stderr){ run_generator(["-m", "non/existant/path"]) } assert_match /The template \[.*\] could not be loaded/, content diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 011bd518f8..9444a9ed4b 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -20,7 +20,7 @@ class GeneratorsTestCase < Test::Unit::TestCase def destination_root @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__), - '..', '..', 'fixtures', 'tmp')) + '..', 'fixtures', 'tmp')) end def setup -- cgit v1.2.3 From 010a0c92eb573cd4c216c51371356adddfde11cf Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Fri, 7 Aug 2009 15:00:12 -0300 Subject: Rename find_by_parts and find_by_parts? to find and exists? --- actionmailer/lib/action_mailer/base.rb | 4 ++-- actionpack/lib/abstract_controller/layouts.rb | 4 ++-- actionpack/lib/abstract_controller/rendering_controller.rb | 2 +- actionpack/lib/action_controller/base.rb | 2 +- actionpack/lib/action_controller/legacy/layout.rb | 2 +- actionpack/lib/action_view/base.rb | 2 +- actionpack/lib/action_view/paths.rb | 10 +++++----- actionpack/lib/action_view/render/partials.rb | 2 +- actionpack/lib/action_view/render/rendering.rb | 4 ++-- actionpack/lib/action_view/template/resolver.rb | 2 +- .../test/abstract_controller/abstract_controller_test.rb | 4 ++-- actionpack/test/abstract_controller/helper_test.rb | 2 +- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 7b03a7f9ff..e36ee72298 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -499,7 +499,7 @@ module ActionMailer #:nodoc: # ==== # TODO: Revisit this # template_exists = @parts.empty? - # template_exists ||= template_root.find_by_parts("#{mailer_name}/#{@template}") + # template_exists ||= template_root.find("#{mailer_name}/#{@template}") # @body = render_message(@template, @body) if template_exists # Finally, if there are other message parts and a textual body exists, @@ -585,7 +585,7 @@ module ActionMailer #:nodoc: if file prefix = mailer_name unless file =~ /\// - template = view_paths.find_by_parts(file, {:formats => formats}, prefix) + template = view_paths.find(file, {:formats => formats}, prefix) end layout = _pick_layout(layout, diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 0cb3d166f7..c1fb80415b 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -76,7 +76,7 @@ module AbstractController when nil self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def _layout(details) - if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts") + if view_paths.exists?("#{_implied_layout_name}", details, "layouts") "#{_implied_layout_name}" else super @@ -131,7 +131,7 @@ module AbstractController def _find_layout(name, details) # TODO: Make prefix actually part of details in ViewPath#find_by_parts prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" - view_paths.find_by_parts(name, details, prefix) + view_paths.find(name, details, prefix) end # Returns the default layout for this controller and a given set of details. diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb index 23cd71e0bd..bb7891fbfd 100644 --- a/actionpack/lib/abstract_controller/rendering_controller.rb +++ b/actionpack/lib/abstract_controller/rendering_controller.rb @@ -111,7 +111,7 @@ module AbstractController def _determine_template(options) name = (options[:_template_name] || action_name).to_s - options[:_template] ||= view_paths.find_by_parts( + options[:_template] ||= view_paths.find( name, { :formats => formats }, options[:_prefix], options[:_partial] ) end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 40f4802b74..61f1c715c8 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -51,7 +51,7 @@ module ActionController def method_for_action(action_name) super || begin - if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) + if view_paths.exists?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) "default_render" end end diff --git a/actionpack/lib/action_controller/legacy/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb index 3f3d20b95f..43aea0eba2 100644 --- a/actionpack/lib/action_controller/legacy/layout.rb +++ b/actionpack/lib/action_controller/legacy/layout.rb @@ -191,7 +191,7 @@ module ActionController #:nodoc: def memoized_find_layout(layout, formats) #:nodoc: return layout if layout.nil? || layout.respond_to?(:render) prefix = layout.to_s =~ /layouts\// ? nil : "layouts" - view_paths.find_by_parts(layout.to_s, {:formats => formats}, prefix) + view_paths.find(layout.to_s, {:formats => formats}, prefix) end def find_layout(*args) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 9e696af83b..7932f01ebb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -202,7 +202,7 @@ module ActionView #:nodoc: delegate :logger, :to => :controller, :allow_nil => true - delegate :find_by_parts, :to => :view_paths + delegate :find, :to => :view_paths include Context diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 074b475819..4001757a9b 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -33,12 +33,12 @@ module ActionView #:nodoc: super(*objs.map { |obj| self.class.type_cast(obj) }) end - def find_by_parts(path, details = {}, prefix = nil, partial = false) + def find(path, details = {}, prefix = nil, partial = false) # template_path = path.sub(/^\//, '') template_path = path each do |load_path| - if template = load_path.find_by_parts(template_path, details, prefix, partial) + if template = load_path.find(template_path, details, prefix, partial) return template end end @@ -48,11 +48,11 @@ module ActionView #:nodoc: raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}") end - def find_by_parts?(path, extension = nil, prefix = nil, partial = false) + def exists?(path, extension = nil, prefix = nil, partial = false) template_path = path.sub(/^\//, '') each do |load_path| - return true if template = load_path.find_by_parts(template_path, extension, prefix, partial) + return true if template = load_path.find(template_path, extension, prefix, partial) end false end @@ -62,7 +62,7 @@ module ActionView #:nodoc: template_path = original_template_path.sub(/^\//, '') each do |load_path| - if template = load_path.find_by_parts(template_path, format) + if template = load_path.find(template_path, format) return template # Try to find html version if the format is javascript elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"] diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 48cba9c02b..c559a572b0 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -258,7 +258,7 @@ module ActionView def _pick_partial_template(partial_path) #:nodoc: prefix = controller_path unless partial_path.include?(?/) - find_by_parts(partial_path, {:formats => formats}, prefix, true) + find(partial_path, {:formats => formats}, prefix, true) end end end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 9c25fab6bb..742b965556 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -24,10 +24,10 @@ module ActionView return _render_content(_render_partial(options), layout, options[:locals]) end - layout = find_by_parts(layout, {:formats => formats}) if layout + layout = find(layout, {:formats => formats}) if layout if file = options[:file] - template = find_by_parts(file, {:formats => formats}) + template = find(file, {:formats => formats}) _render_template(template, layout, :locals => options[:locals] || {}) elsif inline = options[:inline] _render_inline(inline, layout, options) diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index d15f53a11b..ebfc6cc8ce 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -10,7 +10,7 @@ module ActionView end # Normalizes the arguments and passes it on to find_template - def find_by_parts(*args) + def find(*args) find_all_by_parts(*args).first end diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb index 10b97f4eb4..9438a4dfc9 100644 --- a/actionpack/test/abstract_controller/abstract_controller_test.rb +++ b/actionpack/test/abstract_controller/abstract_controller_test.rb @@ -139,10 +139,10 @@ module AbstractController private def self.layout(formats) begin - view_paths.find_by_parts(name.underscore, {:formats => formats}, "layouts") + view_paths.find(name.underscore, {:formats => formats}, "layouts") rescue ActionView::MissingTemplate begin - view_paths.find_by_parts("application", {:formats => formats}, "layouts") + view_paths.find("application", {:formats => formats}, "layouts") rescue ActionView::MissingTemplate end end diff --git a/actionpack/test/abstract_controller/helper_test.rb b/actionpack/test/abstract_controller/helper_test.rb index cd6d4ee90e..e9a60c0307 100644 --- a/actionpack/test/abstract_controller/helper_test.rb +++ b/actionpack/test/abstract_controller/helper_test.rb @@ -4,7 +4,7 @@ module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base - include RenderingController + include AbstractController::RenderingController include Helpers def render(string) -- cgit v1.2.3 From 201387496e057d4853520be9837b2a807c66cc48 Mon Sep 17 00:00:00 2001 From: Brendan Schwartz Date: Wed, 25 Mar 2009 15:14:52 -0400 Subject: Ruby 1.9 compat: fix for SSL in Active Resource [#1272 state:committed] Signed-off-by: Jeremy Kemper --- activeresource/lib/active_resource/connection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index fb3fde59d6..99d4b8f2ca 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -134,7 +134,7 @@ module ActiveResource def http http = Net::HTTP.new(@site.host, @site.port) http.use_ssl = @site.is_a?(URI::HTTPS) - http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl + http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used. http end -- cgit v1.2.3 From 482a6f716fab5ea6431e15ee2603b62a1b2f0790 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Aug 2009 16:41:04 -0700 Subject: Ruby 1.9.2: Object#id is gone now --- activerecord/lib/active_record/attribute_methods/read.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 0b7d6d9094..3da3d9d8cc 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -12,7 +12,7 @@ module ActiveRecord self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT # Undefine id so it can be used as an attribute name - undef_method :id + undef_method(:id) if method_defined?(:id) end module ClassMethods -- cgit v1.2.3 From 019ed5a7c0aac4f1a5924b4a575438f12be18f11 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Aug 2009 17:04:19 -0700 Subject: Don't rely on T::U::TestCase#name --- activerecord/test/cases/migration_test.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 03788bf69e..72d4892435 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -737,19 +737,20 @@ if ActiveRecord::Base.connection.supports_migrations? def test_change_column Person.connection.add_column 'people', 'age', :integer - old_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + label = "test_change_column Columns" + old_columns = Person.connection.columns(Person.table_name, label) assert old_columns.find { |c| c.name == 'age' and c.type == :integer } assert_nothing_raised { Person.connection.change_column "people", "age", :string } - new_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + new_columns = Person.connection.columns(Person.table_name, label) assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer } assert new_columns.find { |c| c.name == 'age' and c.type == :string } - old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + old_columns = Topic.connection.columns(Topic.table_name, label) assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } - new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + new_columns = Topic.connection.columns(Topic.table_name, label) assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } -- cgit v1.2.3 From 12c271d1d297203a0cb4c09ef6da5be9954e87b6 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Aug 2009 17:04:56 -0700 Subject: Ruby 1.9.2: implicit argument passing of super from method defined by define_method() is not supported --- actionpack/lib/action_view/helpers/active_model_helper.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 4fd7f7d83c..3e6e62237d 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -278,9 +278,7 @@ module ActionView end %w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth| - define_method meth do |*| - error_wrapping(super) - end + module_eval "def #{meth}(*) error_wrapping(super) end" end def error_wrapping(html_tag) -- cgit v1.2.3 From 43b406bdb0a747392f30d3da404da3aa6fb29776 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Aug 2009 17:16:34 -0700 Subject: Ruby 1.9 compat: fix route recognition encoding test --- actionpack/test/controller/routing_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index fb83dba395..5f9ae6292c 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'abstract_unit' require 'controller/fake_controllers' require 'active_support/dependencies' @@ -1179,7 +1180,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo")) token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian - token.force_encoding("UTF-8") if token.respond_to?(:force_encoding) escaped_token = CGI::escape(token) assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token) -- cgit v1.2.3 From 98450fd168673c6bc698b3b2d3c264a9cd70a464 Mon Sep 17 00:00:00 2001 From: wmoxam Date: Fri, 7 Aug 2009 17:52:08 -0400 Subject: Fix number_to_precision rounding error [#2071 state:resolved] Signed-off-by: Pratik Naik --- actionpack/lib/action_view/helpers/number_helper.rb | 2 +- actionpack/test/template/number_helper_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 999d5b34fc..897a7cc348 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -215,7 +215,7 @@ module ActionView delimiter ||= (options[:delimiter] || defaults[:delimiter]) begin - rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision + rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision number_with_delimiter("%01.#{precision}f" % rounded_number, :separator => separator, :delimiter => delimiter) diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 57b740032e..85a97d570c 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -88,6 +88,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("111.00", number_with_precision(111, :precision => 2)) assert_equal("111.235", number_with_precision("111.2346")) assert_equal("31.83", number_with_precision("31.825", :precision => 2)) + assert_equal("3268", number_with_precision((32.675 * 100.00), :precision => 0)) assert_equal("112", number_with_precision(111.50, :precision => 0)) assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) -- cgit v1.2.3 From 5fdc33c1a37b4c321135753ed546e8afc2dbaf3e Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 7 Aug 2009 21:37:21 -0400 Subject: Default sent_on time to now in ActionMailer Signed-off-by: Michael Koziarski [#2607 state:committed] --- actionmailer/lib/action_mailer/base.rb | 1 + actionmailer/test/mail_service_test.rb | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index e36ee72298..d01f561f50 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -556,6 +556,7 @@ module ActionMailer #:nodoc: @headers ||= {} @body ||= {} @mime_version = @@default_mime_version.dup if @@default_mime_version + @sent_on ||= Time.now end def render_template(template, body) diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index f2a8c2303c..828661c16d 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base @recipients = recipient @subject = "[Signed up] Welcome #{recipient}" @from = "system@loudthinking.com" - @sent_on = Time.local(2004, 12, 12) @body["recipient"] = recipient end @@ -357,12 +356,14 @@ class ActionMailerTest < Test::Unit::TestCase end def test_signed_up + Time.stubs(:now => Time.now) + expected = new_mail expected.to = @recipient expected.subject = "[Signed up] Welcome #{@recipient}" expected.body = "Hello there, \n\nMr. #{@recipient}" expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) + expected.date = Time.now created = nil assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) } -- cgit v1.2.3 From e8dc151396b6b92e243ac48b58abbe66be6f09b4 Mon Sep 17 00:00:00 2001 From: Mike Breen Date: Mon, 20 Jul 2009 16:48:08 -0400 Subject: Add option to routes task to target a specific controller with CONTROLLER=x. Signed-off-by: Michael Koziarski [#2928 state:committed] --- railties/lib/tasks/routes.rake | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/railties/lib/tasks/routes.rake b/railties/lib/tasks/routes.rake index 39b7139167..abbf3258c1 100644 --- a/railties/lib/tasks/routes.rake +++ b/railties/lib/tasks/routes.rake @@ -1,6 +1,7 @@ -desc 'Print out all defined routes in match order, with names.' +desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' task :routes => :environment do - routes = ActionController::Routing::Routes.routes.collect do |route| + all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes + routes = all_routes.collect do |route| name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s verb = route.conditions[:method].to_s.upcase segs = route.segments.inject("") { |str,s| str << s.to_s } @@ -14,4 +15,4 @@ task :routes => :environment do routes.each do |r| puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}" end -end \ No newline at end of file +end -- cgit v1.2.3 From 54e7f5ba435b7573c68c7acd65c9d2537427de7e Mon Sep 17 00:00:00 2001 From: Josh Sharpe Date: Fri, 7 Aug 2009 21:12:27 -0400 Subject: Tidy up the AR tests, removing duplicates and making tests clearer / more focussed. Signed-off-by: Michael Koziarski [#2774 state:committed] --- activerecord/test/cases/finder_test.rb | 57 +++++++--------------------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7f3be1fa67..7b6bf597a8 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -156,10 +156,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_all_with_limit - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2) - - assert_equal(2, entrants.size) - assert_equal(entrants(:first).name, entrants.first.name) + assert_equal(2, Entrant.find(:all, :limit => 2).size) + assert_equal(0, Entrant.find(:all, :limit => 0).size) end def test_find_all_with_prepared_limit_and_offset @@ -168,22 +166,23 @@ class FinderTest < ActiveRecord::TestCase assert_equal(2, entrants.size) assert_equal(entrants(:second).name, entrants.first.name) + assert_equal 3, Entrant.count entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2) assert_equal(1, entrants.size) assert_equal(entrants(:third).name, entrants.first.name) end - def test_find_all_with_limit_and_offset_and_multiple_orderings - developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1) - assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name} - end + def test_find_all_with_limit_and_offset_and_multiple_order_clauses + first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 + second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 + last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - def test_find_with_limit_and_condition - developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7) - assert_equal(1, developers.size) - assert_equal("fixture_3", developers.first.name) + assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } end + def test_find_with_group developers = Developer.find(:all, :group => "salary", :select => "salary") assert_equal 4, developers.size @@ -978,40 +977,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end - def test_find_all_with_limit - first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5 - assert_equal 5, first_five_developers.length - assert_equal 'David', first_five_developers.first.name - assert_equal 'fixture_5', first_five_developers.last.name - - no_developers = Developer.find :all, :order => 'id ASC', :limit => 0 - assert_equal 0, no_developers.length - end - - def test_find_all_with_limit_and_offset - first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0 - second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3 - last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8 - - assert_equal 3, first_three_developers.length - assert_equal 3, second_three_developers.length - assert_equal 2, last_two_developers.length - - assert_equal 'David', first_three_developers.first.name - assert_equal 'fixture_4', second_three_developers.first.name - assert_equal 'fixture_9', last_two_developers.first.name - end - - def test_find_all_with_limit_and_offset_and_multiple_order_clauses - first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 - second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - - assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } - end - def test_find_all_with_join developers_on_project_one = Developer.find( :all, -- cgit v1.2.3 From fbe6c3c19553fd05edc904af62fbfc8aee1d907d Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 7 Aug 2009 20:56:54 -0700 Subject: Adds a :file delivery_method to save email to a file on disk Signed-off-by: Michael Koziarski [#2438 state:committed] --- actionmailer/lib/action_mailer/base.rb | 22 +++++++++++++++++++++- actionmailer/test/delivery_method_test.rb | 22 ++++++++++++++++++++++ actionmailer/test/mail_service_test.rb | 12 ++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index d01f561f50..5ecefe7c09 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,3 +1,5 @@ +require 'tmpdir' + require "active_support/core_ext/class" # Use the old layouts until actionmailer gets refactored require "action_controller/legacy/layout" @@ -224,9 +226,13 @@ module ActionMailer #:nodoc: # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. # * :arguments - The command line arguments. Defaults to -i -t. # + # * file_settings - Allows you to override options for the :file delivery method. + # * :location - The directory into which emails will be written. Defaults to the application tmp/mails. + # # * 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, and :test. + # * delivery_method - Defines a delivery method. Possible values are :smtp (default), :sendmail, :test, + # and :file. # # * perform_deliveries - Determines whether deliver_* methods are actually carried out. By default they are, # but this can be turned off to help functional testing. @@ -279,6 +285,12 @@ module ActionMailer #:nodoc: } cattr_accessor :sendmail_settings + @@file_settings = { + :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + } + + cattr_accessor :file_settings + @@raise_delivery_errors = true cattr_accessor :raise_delivery_errors @@ -724,6 +736,14 @@ module ActionMailer #:nodoc: def perform_delivery_test(mail) deliveries << mail end + + def perform_delivery_file(mail) + FileUtils.mkdir_p file_settings[:location] + + (mail.to + mail.cc + mail.bcc).uniq.each do |to| + File.open(File.join(file_settings[:location], to), 'a') { |f| f.write(mail) } + end + end end Base.class_eval do diff --git a/actionmailer/test/delivery_method_test.rb b/actionmailer/test/delivery_method_test.rb index 0731512ea4..1b8c3ba523 100644 --- a/actionmailer/test/delivery_method_test.rb +++ b/actionmailer/test/delivery_method_test.rb @@ -7,6 +7,10 @@ class NonDefaultDeliveryMethodMailer < ActionMailer::Base self.delivery_method = :sendmail end +class FileDeliveryMethodMailer < ActionMailer::Base + self.delivery_method = :file +end + class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase def setup set_delivery_method :smtp @@ -49,3 +53,21 @@ class NonDefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end end +class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase + def setup + set_delivery_method :smtp + end + + def teardown + restore_delivery_method + end + + def test_should_be_the_set_delivery_method + assert_equal :file, FileDeliveryMethodMailer.delivery_method + end + + def test_should_default_location_to_the_tmpdir + assert_equal "#{Dir.tmpdir}/mails", ActionMailer::Base.file_settings[:location] + end +end + diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 828661c16d..008ca498b1 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -889,6 +889,18 @@ EOF assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0] end + def test_file_delivery_should_create_a_file + ActionMailer::Base.delivery_method = :file + tmp_location = ActionMailer::Base.file_settings[:location] + + TestMailer.deliver_cc_bcc(@recipient) + assert File.exists? tmp_location + assert File.directory? tmp_location + assert File.exists? File.join(tmp_location, @recipient) + assert File.exists? File.join(tmp_location, 'nobody@loudthinking.com') + assert File.exists? File.join(tmp_location, 'root@loudthinking.com') + end + def test_recursive_multipart_processing fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7") mail = TMail::Mail.parse(fixture) -- cgit v1.2.3 From 73f2d37505025a446bb5314a090f412d0fceb8ca Mon Sep 17 00:00:00 2001 From: Wolfram Arnold Date: Mon, 29 Jun 2009 14:20:15 -0700 Subject: Add test to verify that the new :inverse_of association option will indeed fix the validation problem for a belongs_to relationship that validates_presence_of the parent, when both the parent and the child are new (in-memory) records. Also check that this works when the parents adds child via nested_attributes_for. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lastly, add a require 'models/pet' to association_validation_test.rb, so that test can be run independently (was failing due to that missing dependency). [#2815 status:resolved] Signed-off-by: José Valim --- activerecord/test/cases/nested_attributes_test.rb | 37 ++++++++++++++++++++++ .../validations/association_validation_test.rb | 23 ++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index f31275163d..d033c1e760 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -4,6 +4,8 @@ require "models/ship" require "models/bird" require "models/parrot" require "models/treasure" +require "models/man" +require "models/interest" require 'active_support/hash_with_indifferent_access' module AssertRaiseWithMessage @@ -470,6 +472,41 @@ module NestedAttributesOnACollectionAssociationTests assert Pirate.reflect_on_association(@association_name).options[:autosave] end + def test_validate_presence_of_parent__works_with_inverse_of + Man.accepts_nested_attributes_for(:interests) + assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of] + assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of] + + repair_validations(Interest) do + Interest.validates_presence_of(:man) + assert_difference 'Man.count' do + assert_difference 'Interest.count', 2 do + man = Man.create!(:name => 'John', + :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) + assert_equal 2, man.interests.count + end + end + end + end + + def test_validate_presence_of_parent__fails_without_inverse_of + Man.accepts_nested_attributes_for(:interests) + Man.reflect_on_association(:interests).options.delete(:inverse_of) + Interest.reflect_on_association(:man).options.delete(:inverse_of) + + repair_validations(Interest) do + Interest.validates_presence_of(:man) + assert_no_difference ['Man.count', 'Interest.count'] do + man = Man.create(:name => 'John', + :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) + assert !man.errors[:interests_man].empty? + end + end + # restore :inverse_of + Man.reflect_on_association(:interests).options[:inverse_of] = :man + Interest.reflect_on_association(:man).options[:inverse_of] = :interests + end + private def association_setter diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index b1203c12ed..278a7a6a06 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -3,6 +3,9 @@ require "cases/helper" require 'models/topic' require 'models/reply' require 'models/owner' +require 'models/pet' +require 'models/man' +require 'models/interest' class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics, :owners @@ -98,4 +101,24 @@ class AssociationValidationTest < ActiveRecord::TestCase end end end + + def test_validates_presence_of_belongs_to_association__parent_is_new_record + repair_validations(Interest) do + # Note that Interest and Man have the :inverse_of option set + Interest.validates_presence_of(:man) + man = Man.new(:name => 'John') + interest = man.interests.build(:topic => 'Airplanes') + assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" + end + end + + def test_validates_presence_of_belongs_to_association__existing_parent + repair_validations(Interest) do + Interest.validates_presence_of(:man) + man = Man.create!(:name => 'John') + interest = man.interests.build(:topic => 'Airplanes') + assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" + end + end + end -- cgit v1.2.3 From a8645593a4446a89b2e699e153adca968340581a Mon Sep 17 00:00:00 2001 From: "Steve St. Martin" Date: Fri, 7 Aug 2009 20:05:02 -0400 Subject: remove duplicate call to stringify_keys [#2587 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/lib/action_view/helpers/form_tag_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e126b35e90..1abe7775e0 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -263,7 +263,7 @@ module ActionView escape = options.key?("escape") ? options.delete("escape") : true content = html_escape(content) if escape - content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) + content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options) end # Creates a check box form input tag. -- cgit v1.2.3 From d0301e13f4d6e2ddf956ecf80e85384c1ea5346c Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Sat, 8 Aug 2009 12:26:58 -0300 Subject: First pass at making partial rendering an Object. More cleanup to come. --- actionpack/lib/abstract_controller/layouts.rb | 18 +-- .../metal/rendering_controller.rb | 3 - actionpack/lib/action_view/render/partials.rb | 142 ++++++++++++--------- actionpack/lib/action_view/render/rendering.rb | 3 +- 4 files changed, 93 insertions(+), 73 deletions(-) diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index c1fb80415b..0063d54149 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -89,16 +89,18 @@ module AbstractController end def render_to_body(options = {}) + # In the case of a partial with a layout, handle the layout + # here, and make sure the view does not try to handle it + layout = options.delete(:layout) if options.key?(:partial) + response = super - if options.key?(:partial) - # This is a little bit messy. We need to explicitly handle partial - # layouts here since the core lookup logic is in the view, but - # we need to determine the layout based on the controller - if options.key?(:layout) - layout = _layout_for_option(options[:layout], options[:_template].details) - response = layout.render(view_context, options[:locals]) { response } - end + # This is a little bit messy. We need to explicitly handle partial + # layouts here since the core lookup logic is in the view, but + # we need to determine the layout based on the controller + if layout + layout = _layout_for_option(layout, options[:_template].details) + response = layout.render(view_context, options[:locals] || {}) { response } end response diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb index c8922290f3..5b1be763ad 100644 --- a/actionpack/lib/action_controller/metal/rendering_controller.rb +++ b/actionpack/lib/action_controller/metal/rendering_controller.rb @@ -53,9 +53,6 @@ module ActionController super end - def _render_partial(partial, options) - end - def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index c559a572b0..986d3af454 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -170,95 +170,117 @@ module ActionView # <%- end -%> # <% end %> module Partials - extend ActiveSupport::Memoizable extend ActiveSupport::Concern - included do - attr_accessor :_partial - end - - module ClassMethods - def _partial_names - @_partial_names ||= ActiveSupport::ConcurrentHash.new + class PartialRenderer + def self.partial_names + @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new } end - end - def render_partial(options) - @assigns_added = false - # TODO: Handle other details here. - self.formats = options[:_details][:formats] - _render_partial(options) - end + def initialize(view_context, options, formats) + object = options[:partial] - def _render_partial(options, &block) #:nodoc: - options[:locals] ||= {} + @view, @formats = view_context, formats + @options = options || {} - path = partial = options[:partial] + if object.is_a?(String) + @path = object + elsif + @object = object + @path = partial_path unless collection + end - if partial.respond_to?(:to_ary) - return _render_partial_collection(partial, options, &block) - elsif !partial.is_a?(String) - options[:object] = object = partial - path = _partial_path(object) + @locals = options[:locals] || {} + @object ||= @options[:object] || @locals[:object] + @layout = options[:layout] end - _render_partial_object(_pick_partial_template(path), options, &block) - end + def render(&block) + template = find if @path - private - def _partial_path(object) - self.class._partial_names[[controller.class, object.class]] ||= begin - name = object.class.model_name - if controller_path && controller_path.include?("/") - File.join(File.dirname(controller_path), name.partial_path) - else - name.partial_path - end + if collection + render_collection(template, &block) + else + render_object(template, &block) end end - def _render_partial_template(template, locals, object, options = {}, &block) - options[:_template] = template - locals[:object] = locals[template.variable_name] = object - locals[options[:as]] = object if options[:as] + def render_template(template, &block) + @options[:_template] = template + @locals[:object] = @locals[template.variable_name] = @object + @locals[@options[:as]] = @object if @options[:as] - _render_single_template(template, locals, &block) + content = @view._render_single_template(template, @locals, &block) + return content if block_given? || !@layout + find(@layout).render(@view, @locals) { content } end - def _render_partial_object(template, options, &block) - if options.key?(:collection) - _render_partial_collection(options.delete(:collection), options, template, &block) - else - locals = (options[:locals] ||= {}) - object = options[:object] || locals[:object] || locals[template.variable_name] - - _render_partial_template(template, locals, object, options, &block) - end + def render_object(template, &block) + @object ||= @locals[template.variable_name] + render_template(template, &block) end - def _render_partial_collection(collection, options = {}, template = nil, &block) #:nodoc: - options[:_template] ||= template + def render_collection(passed_template = nil, &block) + @options[:_template] = passed_template return nil if collection.blank? - if options.key?(:spacer_template) - spacer = _render_partial(:partial => options[:spacer_template]) + if @options.key?(:spacer_template) + spacer = @view.render_partial( + :partial => @options[:spacer_template], :_details => @options[:_details]) end - locals, index = options[:locals] || {}, 0 + index = 0 - collection.map do |object| - tmp = template || _pick_partial_template(_partial_path(object)) - locals[tmp.counter_name] = index + collection.map do |@object| + @path = partial_path + template = passed_template || find + @locals[template.counter_name] = index index += 1 - _render_partial_template(tmp, locals, object, options, &block) + render_template(template, &block) end.join(spacer) end - def _pick_partial_template(partial_path) #:nodoc: - prefix = controller_path unless partial_path.include?(?/) - find(partial_path, {:formats => formats}, prefix, true) + private + def collection + @collection ||= if @object.respond_to?(:to_ary) + @object + elsif @options.key?(:collection) + @options[:collection] || [] + end end + + def find(path = @path) + prefix = @view.controller.controller_path unless path.include?(?/) + @view.find(path, {:formats => @view.formats}, prefix, true) + end + + def partial_path(object = @object) + self.class.partial_names[@view.controller.class][object.class] ||= begin + return nil unless object.class.respond_to?(:model_name) + + name = object.class.model_name + path = @view.controller_path + if path && path.include?(?/) + File.join(File.dirname(path), name.partial_path) + else + name.partial_path + end + end + end + end + + def render_partial(options) + @assigns_added = false + # TODO: Handle other details here. + self.formats = options[:_details][:formats] if options[:_details] + _render_partial(options) + end + + def _render_partial(options, &block) #:nodoc: + PartialRenderer.new(self, options, formats).render(&block) + end + end end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 742b965556..c7afc56e3b 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -20,8 +20,7 @@ module ActionView if block_given? return concat(_render_partial(options.merge(:partial => layout), &block)) elsif options.key?(:partial) - layout = _pick_partial_template(layout) if layout - return _render_content(_render_partial(options), layout, options[:locals]) + return _render_partial(options) end layout = find(layout, {:formats => formats}) if layout -- cgit v1.2.3 From 0fbeaa98e4e60ca0949be298dae8545807407e1d Mon Sep 17 00:00:00 2001 From: Bas Van Klinkenberg Date: Sat, 1 Aug 2009 02:24:40 +0200 Subject: Fixed a bug in JSON decoding with Yaml backend, where a combination of dates, escaped or unicode encoded data and arrays would make the parser fail with a ParseError exception. [#2831 state:resolved] Signed-off-by: Yehuda Katz --- activesupport/lib/active_support/json/backends/yaml.rb | 17 ++++++++++------- activesupport/test/json/decoding_test.rb | 8 +++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb index 667016f45d..92dd31cfbc 100644 --- a/activesupport/lib/active_support/json/backends/yaml.rb +++ b/activesupport/lib/active_support/json/backends/yaml.rb @@ -34,11 +34,9 @@ module ActiveSupport pos = scanner.pos elsif quoting == char if json[pos..scanner.pos-2] =~ DATE_REGEX - # found a date, track the exact positions of the quotes so we can remove them later. - # oh, and increment them for each current mark, each one is an extra padded space that bumps - # the position in the final YAML output - total_marks = marks.size - times << pos+total_marks << scanner.pos+total_marks + # found a date, track the exact positions of the quotes so we can + # overwrite them with spaces later. + times << pos << scanner.pos end quoting = false end @@ -64,7 +62,12 @@ module ActiveSupport output = [] left_pos.each_with_index do |left, i| scanner.pos = left.succ - output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do + chunk = scanner.peek(right_pos[i] - scanner.pos + 1) + # overwrite the quotes found around the dates with spaces + while times.size > 0 && times[0] <= right_pos[i] + chunk[times.shift - scanner.pos - 1] = ' ' + end + chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do ustr = $1 if ustr.start_with?('u') [ustr[1..-1].to_i(16)].pack("U") @@ -74,10 +77,10 @@ module ActiveSupport ustr end end + output << chunk end output = output * " " - times.each { |i| output[i-1] = ' ' } output.gsub!(/\\\//, '/') output end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 09fd0d09ba..4129a4fab6 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -33,7 +33,13 @@ class TestJSONDecoding < ActiveSupport::TestCase %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => ""}, %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, %q({"a": "\u003cbr /\u003e"}) => {'a' => "
"}, - %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["","",""]} + %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["","",""]}, + # test combination of dates and escaped or unicode encoded data in arrays + %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => + [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}], + %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => + [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}, + {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}] } # load the default JSON backend -- cgit v1.2.3 From efcfce50c417975ee038a107755a1542a690d39b Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Sat, 8 Aug 2009 12:46:44 -0300 Subject: Fixes "Cached fragment hit" written to log even if fragment is not cached (Erik Andrejko) [#2917 state:resolved] --- actionpack/lib/action_controller/caching/fragments.rb | 4 ++-- actionpack/test/controller/caching_test.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 95cba0e411..4ef600bea0 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -36,8 +36,8 @@ module ActionController #:nodoc: def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc: if perform_caching - if cache = read_fragment(name, options) - buffer.concat(cache) + if fragment_exist?(name,options) + buffer.concat(read_fragment(name, options)) else pos = buffer.length block.call diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index c286976315..68529cc8f7 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -625,6 +625,21 @@ class FragmentCachingTest < ActionController::TestCase assert !fragment_computed assert_equal 'generated till now -> fragment content', buffer end + + def test_fragment_for_logging + fragment_computed = false + + @controller.class.expects(:benchmark).with('Cached fragment exists?: views/expensive') + @controller.class.expects(:benchmark).with('Cached fragment miss: views/expensive') + @controller.class.expects(:benchmark).with('Cached fragment hit: views/expensive').never + + buffer = 'generated till now -> ' + @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } + + assert fragment_computed + assert_equal 'generated till now -> ', buffer + end + end class FunctionalCachingController < ActionController::Base -- cgit v1.2.3 From 6e0ac748e41e8de8111095f7188c75f673779f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Aug 2009 11:07:07 +0200 Subject: Renamed ActionController::Renderer to ActionController::Responder and ActionController::MimeResponds::Responder to ActionController::MimeResponds::Collector. --- actionpack/lib/action_controller.rb | 2 +- .../lib/action_controller/metal/mime_responds.rb | 26 +-- actionpack/lib/action_controller/metal/renderer.rb | 181 --------------------- .../lib/action_controller/metal/responder.rb | 181 +++++++++++++++++++++ actionpack/test/controller/mime_responds_test.rb | 10 +- 5 files changed, 200 insertions(+), 200 deletions(-) delete mode 100644 actionpack/lib/action_controller/metal/renderer.rb create mode 100644 actionpack/lib/action_controller/metal/responder.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 8343a87936..37ff618edd 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -7,10 +7,10 @@ module ActionController autoload :RackConvenience, "action_controller/metal/rack_convenience" autoload :Rails2Compatibility, "action_controller/metal/compatibility" autoload :Redirector, "action_controller/metal/redirector" - autoload :Renderer, "action_controller/metal/renderer" autoload :RenderingController, "action_controller/metal/rendering_controller" autoload :RenderOptions, "action_controller/metal/render_options" autoload :Rescue, "action_controller/metal/rescuable" + autoload :Responder, "action_controller/metal/responder" autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" autoload :Session, "action_controller/metal/session" diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d823dd424a..c8d042acb5 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -179,15 +179,15 @@ module ActionController #:nodoc: def respond_to(*mimes, &block) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? - responder = Responder.new + collector = Collector.new mimes = collect_mimes_from_class_level if mimes.empty? - mimes.each { |mime| responder.send(mime) } - block.call(responder) if block_given? + mimes.each { |mime| collector.send(mime) } + block.call(collector) if block_given? - if format = request.negotiate_mime(responder.order) + if format = request.negotiate_mime(collector.order) self.formats = [format.to_sym] - if response = responder.response_for(format) + if response = collector.response_for(format) response.call else default_render @@ -197,10 +197,10 @@ module ActionController #:nodoc: end end - # respond_with wraps a resource around a renderer for default representation. + # respond_with wraps a resource around a responder for default representation. # First it invokes respond_to, if a response cannot be found (ie. no block # for the request was given and template was not available), it instantiates - # an ActionController::Renderer with the controller and resource. + # an ActionController::Responder with the controller and resource. # # ==== Example # @@ -221,19 +221,19 @@ module ActionController #:nodoc: # end # end # - # All options given to respond_with are sent to the underlying renderer, - # except for the option :renderer itself. Since the renderer interface + # All options given to respond_with are sent to the underlying responder, + # except for the option :responder itself. Since the responder interface # is quite simple (it just needs to respond to call), you can even give # a proc to it. # def respond_with(resource, options={}, &block) respond_to(&block) rescue ActionView::MissingTemplate - (options.delete(:renderer) || renderer).call(self, resource, options) + (options.delete(:responder) || responder).call(self, resource, options) end - def renderer - ActionController::Renderer + def responder + ActionController::Responder end protected @@ -257,7 +257,7 @@ module ActionController #:nodoc: end end - class Responder #:nodoc: + class Collector #:nodoc: attr_accessor :order def initialize diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/renderer.rb deleted file mode 100644 index 39ab2407aa..0000000000 --- a/actionpack/lib/action_controller/metal/renderer.rb +++ /dev/null @@ -1,181 +0,0 @@ -module ActionController #:nodoc: - # Renderer is responsible to expose a resource for different mime requests, - # usually depending on the HTTP verb. The renderer is triggered when - # respond_with is called. The simplest case to study is a GET request: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.find(:all) - # respond_with(@people) - # end - # end - # - # When a request comes, for example with format :xml, three steps happen: - # - # 1) respond_with searches for a template at people/index.xml; - # - # 2) if the template is not available, it will create a renderer, passing - # the controller and the resource and invoke :to_xml on it; - # - # 3) if the renderer does not respond_to :to_xml, call to_format on it. - # - # === Builtin HTTP verb semantics - # - # Rails default renderer holds semantics for each HTTP verb. Depending on the - # content type, verb and the resource status, it will behave differently. - # - # Using Rails default renderer, a POST request for creating an object could - # be written as: - # - # def create - # @user = User.new(params[:user]) - # flash[:notice] = 'User was successfully created.' if @user.save - # respond_with(@user) - # end - # - # Which is exactly the same as: - # - # def create - # @user = User.new(params[:user]) - # - # respond_to do |format| - # if @user.save - # flash[:notice] = 'User was successfully created.' - # format.html { redirect_to(@user) } - # format.xml { render :xml => @user, :status => :created, :location => @user } - # else - # format.html { render :action => "new" } - # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } - # end - # end - # end - # - # The same happens for PUT and DELETE requests. - # - # === Nested resources - # - # You can given nested resource as you do in form_for and polymorphic_url. - # Consider the project has many tasks example. The create action for - # TasksController would be like: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) - # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with([@project, @task]) - # end - # - # Giving an array of resources, you ensure that the renderer will redirect to - # project_task_url instead of task_url. - # - # Namespaced and singleton resources requires a symbol to be given, as in - # polymorphic urls. If a project has one manager which has many tasks, it - # should be invoked as: - # - # respond_with([@project, :manager, @task]) - # - # Check polymorphic_url documentation for more examples. - # - class Renderer - attr_reader :controller, :request, :format, :resource, :resource_location, :options - - def initialize(controller, resource, options={}) - @controller = controller - @request = controller.request - @format = controller.formats.first - @resource = resource.is_a?(Array) ? resource.last : resource - @resource_location = options[:location] || resource - @options = options - end - - delegate :head, :render, :redirect_to, :to => :controller - delegate :get?, :post?, :put?, :delete?, :to => :request - - # Undefine :to_json since it's defined on Object - undef_method :to_json - - # Initializes a new renderer an invoke the proper format. If the format is - # not defined, call to_format. - # - def self.call(*args) - renderer = new(*args) - method = :"to_#{renderer.format}" - renderer.respond_to?(method) ? renderer.send(method) : renderer.to_format - end - - # HTML format does not render the resource, it always attempt to render a - # template. - # - def to_html - if get? - render - elsif has_errors? - render :action => default_action - else - redirect_to resource_location - end - end - - # All others formats try to render the resource given instead. For this - # purpose a helper called display as a shortcut to render a resource with - # the current format. - # - def to_format - return render unless resourceful? - - if get? - display resource - elsif has_errors? - display resource.errors, :status => :unprocessable_entity - elsif post? - display resource, :status => :created, :location => resource_location - else - head :ok - end - end - - protected - - # Checks whether the resource responds to the current format or not. - # - def resourceful? - resource.respond_to?(:"to_#{format}") - end - - # display is just a shortcut to render a resource with the current format. - # - # display @user, :status => :ok - # - # For xml request is equivalent to: - # - # render :xml => @user, :status => :ok - # - # Options sent by the user are also used: - # - # respond_with(@user, :status => :created) - # display(@user, :status => :ok) - # - # Results in: - # - # render :xml => @user, :status => :created - # - def display(resource, given_options={}) - render given_options.merge!(options).merge!(format => resource) - end - - # Check if the resource has errors or not. - # - def has_errors? - resource.respond_to?(:errors) && !resource.errors.empty? - end - - # By default, render the :edit action for html requests with failure, unless - # the verb is post. - # - def default_action - request.post? ? :new : :edit - end - end -end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb new file mode 100644 index 0000000000..9ed99ca623 --- /dev/null +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -0,0 +1,181 @@ +module ActionController #:nodoc: + # Responder is responsible to expose a resource for different mime requests, + # usually depending on the HTTP verb. The responder is triggered when + # respond_with is called. The simplest case to study is a GET request: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@people) + # end + # end + # + # When a request comes, for example with format :xml, three steps happen: + # + # 1) respond_with searches for a template at people/index.xml; + # + # 2) if the template is not available, it will create a responder, passing + # the controller and the resource and invoke :to_xml on it; + # + # 3) if the responder does not respond_to :to_xml, call to_format on it. + # + # === Builtin HTTP verb semantics + # + # Rails default responder holds semantics for each HTTP verb. Depending on the + # content type, verb and the resource status, it will behave differently. + # + # Using Rails default responder, a POST request for creating an object could + # be written as: + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # Which is exactly the same as: + # + # def create + # @user = User.new(params[:user]) + # + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user, :status => :created, :location => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } + # end + # end + # end + # + # The same happens for PUT and DELETE requests. + # + # === Nested resources + # + # You can given nested resource as you do in form_for and polymorphic_url. + # Consider the project has many tasks example. The create action for + # TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with([@project, @task]) + # end + # + # Giving an array of resources, you ensure that the responder will redirect to + # project_task_url instead of task_url. + # + # Namespaced and singleton resources requires a symbol to be given, as in + # polymorphic urls. If a project has one manager which has many tasks, it + # should be invoked as: + # + # respond_with([@project, :manager, @task]) + # + # Check polymorphic_url documentation for more examples. + # + class Responder + attr_reader :controller, :request, :format, :resource, :resource_location, :options + + def initialize(controller, resource, options={}) + @controller = controller + @request = controller.request + @format = controller.formats.first + @resource = resource.is_a?(Array) ? resource.last : resource + @resource_location = options[:location] || resource + @options = options + end + + delegate :head, :render, :redirect_to, :to => :controller + delegate :get?, :post?, :put?, :delete?, :to => :request + + # Undefine :to_json since it's defined on Object + undef_method :to_json + + # Initializes a new responder an invoke the proper format. If the format is + # not defined, call to_format. + # + def self.call(*args) + responder = new(*args) + method = :"to_#{responder.format}" + responder.respond_to?(method) ? responder.send(method) : responder.to_format + end + + # HTML format does not render the resource, it always attempt to render a + # template. + # + def to_html + if get? + render + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end + end + + # All others formats try to render the resource given instead. For this + # purpose a helper called display as a shortcut to render a resource with + # the current format. + # + def to_format + return render unless resourceful? + + if get? + display resource + elsif has_errors? + display resource.errors, :status => :unprocessable_entity + elsif post? + display resource, :status => :created, :location => resource_location + else + head :ok + end + end + + protected + + # Checks whether the resource responds to the current format or not. + # + def resourceful? + resource.respond_to?(:"to_#{format}") + end + + # display is just a shortcut to render a resource with the current format. + # + # display @user, :status => :ok + # + # For xml request is equivalent to: + # + # render :xml => @user, :status => :ok + # + # Options sent by the user are also used: + # + # respond_with(@user, :status => :created) + # display(@user, :status => :ok) + # + # Results in: + # + # render :xml => @user, :status => :created + # + def display(resource, given_options={}) + render given_options.merge!(options).merge!(format => resource) + end + + # Check if the resource has errors or not. + # + def has_errors? + resource.respond_to?(:errors) && !resource.errors.empty? + end + + # By default, render the :edit action for html requests with failure, unless + # the verb is post. + # + def default_action + request.post? ? :new : :edit + end + end +end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 00ad90ff68..8319b5c573 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -505,9 +505,9 @@ class RespondWithController < ActionController::Base respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created) end - def using_resource_with_renderer - renderer = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" } - respond_with(Customer.new("david", 13), :renderer => renderer) + def using_resource_with_responder + responder = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" } + respond_with(Customer.new("david", 13), :responder => responder) end protected @@ -743,8 +743,8 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal 201, @response.status end - def test_using_resource_with_renderer - get :using_resource_with_renderer + def test_using_resource_with_responder + get :using_resource_with_responder assert_equal "Resource name is david", @response.body end -- cgit v1.2.3 From 469d89dd442dc8590575daad4ae9052c229c917f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Aug 2009 11:22:47 +0200 Subject: Rename Rails::Generators::ActionORM to Rails::Generators::ActiveModel. --- railties/lib/generators/action_orm.rb | 74 ---------------------- railties/lib/generators/active_model.rb | 74 ++++++++++++++++++++++ railties/lib/generators/active_record.rb | 4 +- railties/lib/generators/named_base.rb | 10 +-- .../scaffold_controller_generator_test.rb | 2 +- 5 files changed, 82 insertions(+), 82 deletions(-) delete mode 100644 railties/lib/generators/action_orm.rb create mode 100644 railties/lib/generators/active_model.rb diff --git a/railties/lib/generators/action_orm.rb b/railties/lib/generators/action_orm.rb deleted file mode 100644 index 69cf227fd7..0000000000 --- a/railties/lib/generators/action_orm.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Rails - module Generators - # ActionORM is a class to be implemented by each ORM to allow Rails to - # generate customized controller code. - # - # The API has the same methods as ActiveRecord, but each method returns a - # string that matches the ORM API. - # - # For example: - # - # ActiveRecord::Generators::ActionORM.find(Foo, "params[:id]") - # #=> "Foo.find(params[:id])" - # - # Datamapper::Generators::ActionORM.find(Foo, "params[:id]") - # #=> "Foo.get(params[:id])" - # - # On initialization, the ActionORM accepts the instance name that will - # receive the calls: - # - # builder = ActiveRecord::Generators::ActionORM.new "@foo" - # builder.save #=> "@foo.save" - # - # The only exception in ActionORM for ActiveRecord is the use of self.build - # instead of self.new. - # - class ActionORM - attr_reader :name - - def initialize(name) - @name = name - end - - # GET index - def self.all(klass) - raise NotImplementedError - end - - # GET show - # GET edit - # PUT update - # DELETE destroy - def self.find(klass, params=nil) - raise NotImplementedError - end - - # GET new - # POST create - def self.build(klass, params=nil) - raise NotImplementedError - end - - # POST create - def save - raise NotImplementedError - end - - # PUT update - def update_attributes(params=nil) - raise NotImplementedError - end - - # POST create - # PUT update - def errors - raise NotImplementedError - end - - # DELETE destroy - def destroy - raise NotImplementedError - end - end - end -end diff --git a/railties/lib/generators/active_model.rb b/railties/lib/generators/active_model.rb new file mode 100644 index 0000000000..1a849a0e02 --- /dev/null +++ b/railties/lib/generators/active_model.rb @@ -0,0 +1,74 @@ +module Rails + module Generators + # ActiveModel is a class to be implemented by each ORM to allow Rails to + # generate customized controller code. + # + # The API has the same methods as ActiveRecord, but each method returns a + # string that matches the ORM API. + # + # For example: + # + # ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]") + # #=> "Foo.find(params[:id])" + # + # Datamapper::Generators::ActiveModel.find(Foo, "params[:id]") + # #=> "Foo.get(params[:id])" + # + # On initialization, the ActiveModel accepts the instance name that will + # receive the calls: + # + # builder = ActiveRecord::Generators::ActiveModel.new "@foo" + # builder.save #=> "@foo.save" + # + # The only exception in ActiveModel for ActiveRecord is the use of self.build + # instead of self.new. + # + class ActiveModel + attr_reader :name + + def initialize(name) + @name = name + end + + # GET index + def self.all(klass) + raise NotImplementedError + end + + # GET show + # GET edit + # PUT update + # DELETE destroy + def self.find(klass, params=nil) + raise NotImplementedError + end + + # GET new + # POST create + def self.build(klass, params=nil) + raise NotImplementedError + end + + # POST create + def save + raise NotImplementedError + end + + # PUT update + def update_attributes(params=nil) + raise NotImplementedError + end + + # POST create + # PUT update + def errors + raise NotImplementedError + end + + # DELETE destroy + def destroy + raise NotImplementedError + end + end + end +end diff --git a/railties/lib/generators/active_record.rb b/railties/lib/generators/active_record.rb index 64bee3904e..924b70881a 100644 --- a/railties/lib/generators/active_record.rb +++ b/railties/lib/generators/active_record.rb @@ -1,6 +1,6 @@ require 'generators/named_base' require 'generators/migration' -require 'generators/action_orm' +require 'generators/active_model' require 'active_record' module ActiveRecord @@ -20,7 +20,7 @@ module ActiveRecord end end - class ActionORM < Rails::Generators::ActionORM #:nodoc: + class ActiveModel < Rails::Generators::ActiveModel #:nodoc: def self.all(klass) "#{klass}.all" end diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb index 699b8ed651..9632e6806c 100644 --- a/railties/lib/generators/named_base.rb +++ b/railties/lib/generators/named_base.rb @@ -124,18 +124,18 @@ module Rails protected - # Loads the ORM::Generators::ActionORM class. This class is responsable + # Loads the ORM::Generators::ActiveModel class. This class is responsable # to tell scaffold entities how to generate an specific method for the - # ORM. Check Rails::Generators::ActionORM for more information. + # ORM. Check Rails::Generators::ActiveModel for more information. # def orm_class @orm_class ||= begin # Raise an error if the class_option :orm was not defined. unless self.class.class_options[:orm] - raise "You need to have :orm as class option to invoke orm_class and orm_instance" + raise "You need to have :orm as class option to invoke orm_class and orm_instance" end - action_orm = "#{options[:orm].to_s.classify}::Generators::ActionORM" + action_orm = "#{options[:orm].to_s.classify}::Generators::ActiveModel" # If the orm was not loaded, try to load it at "generators/orm", # for example "generators/active_record" or "generators/sequel". @@ -152,7 +152,7 @@ module Rails end end - # Initialize ORM::Generators::ActionORM to access instance methods. + # Initialize ORM::Generators::ActiveModel to access instance methods. # def orm_instance(name=file_name) @orm_instance ||= @orm_class.new(name) diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 024ea439ef..834e43e776 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -99,7 +99,7 @@ class ScaffoldControllerGeneratorTest < GeneratorsTestCase def test_error_is_shown_if_orm_does_not_provide_interface error = capture(:stderr){ run_generator ["User", "--orm=unknown"] } - assert_equal "Could not load Unknown::Generators::ActionORM, skipping controller. " << + assert_equal "Could not load Unknown::Generators::ActiveModel, skipping controller. " << "Error: no such file to load -- generators/unknown.\n", error end -- cgit v1.2.3 From 016ed9b4298d187182d05340baddbe98f4eb8fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 8 Aug 2009 12:12:56 +0200 Subject: Added rubygems to generators load_paths, but we only load generators from gems that are alraedy activated. This fixes the version problem and avoid silly conflicts. --- railties/lib/generators.rb | 64 +++++++++++++++------- .../gems/mspec/lib/generators/mspec_generator.rb | 2 + .../gems/wrong/lib/generators/wrong_generator.rb | 3 + .../gems/mspec/lib/generators/mspec_generator.rb | 2 - .../gems/wrong/lib/generators/wrong_generator.rb | 3 - 5 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb create mode 100644 railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb delete mode 100644 railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb delete mode 100644 railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb diff --git a/railties/lib/generators.rb b/railties/lib/generators.rb index ef837e1488..c97c61507a 100644 --- a/railties/lib/generators.rb +++ b/railties/lib/generators.rb @@ -83,6 +83,34 @@ module Rails @@options ||= DEFAULT_OPTIONS.dup end + # Get paths only from loaded rubygems. In other words, to use rspec + # generators, you first have to ensure that rspec gem was already loaded. + # + def self.rubygems_generators_paths + paths = [] + return paths unless defined?(Gem) + + Gem.loaded_specs.each do |name, spec| + generator_path = File.join(spec.full_gem_path, "lib/generators") + paths << generator_path if File.exist?(generator_path) + end + + paths + end + + # If RAILS_ROOT is defined, add vendor/gems, vendor/plugins and lib/generators + # paths. + # + def self.rails_root_generators_paths + paths = [] + if defined?(RAILS_ROOT) + paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")] + paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] + paths << File.join(RAILS_ROOT, "lib", "generators") + end + paths + end + # Hold configured generators fallbacks. If a plugin developer wants a # generator group to fallback to another group in case of missing generators, # they can add a fallback. @@ -108,30 +136,25 @@ module Rails # Generators load paths used on lookup. The lookup happens as: # - # 1) builtin generators - # 2) frozen gems generators - # 3) rubygems gems generators (not available yet) - # 4) plugin generators - # 5) lib generators - # 6) ~/rails/generators + # 1) lib generators + # 2) vendor/plugin generators + # 3) vendor/gems generators + # 4) ~/rails/generators + # 5) rubygems generators + # 6) builtin generators # - # TODO Add Rubygems generators (depends on dependencies system rework) # TODO Remove hardcoded paths for all, except (1). # - def self.load_path - @@load_path ||= begin - paths = [] - paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) - if defined?(RAILS_ROOT) - paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "*", "lib", "generators")] - paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] - paths << File.join(RAILS_ROOT, "lib", "generators") - end + def self.load_paths + @@load_paths ||= begin + paths = self.rails_root_generators_paths paths << File.join(Thor::Util.user_home, ".rails", "generators") + paths += self.rubygems_generators_paths + paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) paths end end - load_path # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. + load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. # Receives a namespace and tries different combinations to find a generator. # @@ -204,8 +227,8 @@ module Rails puts "Builtin: #{rails.join(', ')}." # Load paths and remove builtin - paths, others = load_path.dup, [] - paths.shift + paths, others = load_paths.dup, [] + paths.pop paths.each do |path| tail = [ "*", "*", "*_generator.rb" ] @@ -242,7 +265,6 @@ module Rails # def self.invoke_fallbacks_for(name, base) return nil unless base && fallbacks[base.to_sym] - invoked_fallbacks = [] Array(fallbacks[base.to_sym]).each do |fallback| @@ -283,7 +305,7 @@ module Rails generators_path.uniq! generators_path = "{#{generators_path.join(',')}}" - self.load_path.each do |path| + self.load_paths.each do |path| Dir[File.join(path, generators_path, name)].each do |file| begin require file diff --git a/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb new file mode 100644 index 0000000000..191bdbf2fc --- /dev/null +++ b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb @@ -0,0 +1,2 @@ +class MspecGenerator < Rails::Generators::NamedBase +end diff --git a/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb b/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb new file mode 100644 index 0000000000..6aa7cb052e --- /dev/null +++ b/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb @@ -0,0 +1,3 @@ +# Old generator version +class WrongGenerator < Rails::Generator::NamedBase +end diff --git a/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb deleted file mode 100644 index 191bdbf2fc..0000000000 --- a/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb +++ /dev/null @@ -1,2 +0,0 @@ -class MspecGenerator < Rails::Generators::NamedBase -end diff --git a/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb b/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb deleted file mode 100644 index 6aa7cb052e..0000000000 --- a/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Old generator version -class WrongGenerator < Rails::Generator::NamedBase -end -- cgit v1.2.3 From c284412b149e03f46144ef566bcd6a16750961b1 Mon Sep 17 00:00:00 2001 From: Niklas Holmgren Date: Sat, 8 Aug 2009 11:43:24 -0400 Subject: Polymorphic routes generates collection URL from model class [#1089 state:resolved] Signed-off-by: Dan Pickett Signed-off-by: Pratik Naik --- .../routing/generation/polymorphic_routes.rb | 7 +++++ .../test/activerecord/polymorphic_routes_test.rb | 31 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb index ee44160274..2adf3575a7 100644 --- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb @@ -50,6 +50,7 @@ module ActionController # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" + # polymorphic_url(Comment) # => "http://example.com/comments" # # ==== Options # @@ -70,6 +71,9 @@ module ActionController # record = Comment.new # polymorphic_url(record) # same as comments_url() # + # # the class of a record will also map to the collection + # polymorphic_url(Comment) # same as comments_url() + # def polymorphic_url(record_or_hash_or_array, options = {}) if record_or_hash_or_array.kind_of?(Array) record_or_hash_or_array = record_or_hash_or_array.compact @@ -93,6 +97,9 @@ module ActionController (record.respond_to?(:destroyed?) && record.destroyed?) args.pop :plural + elsif record.is_a?(Class) + args.pop + :plural else :singular end diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index f001b0dab8..37f1f6dff8 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -45,6 +45,12 @@ class PolymorphicRoutesTest < ActionController::TestCase assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(@project) end end + + def test_with_class + with_test_routes do + assert_equal "http://example.com/projects", polymorphic_url(@project.class) + end + end def test_with_new_record with_test_routes do @@ -144,6 +150,19 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_nested_class + with_test_routes do + @project.save + assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task.class]) + end + end + + def test_class_with_array_and_namespace + with_admin_test_routes do + assert_equal "http://example.com/admin/projects", polymorphic_url([:admin, @project.class]) + end + end + def test_new_with_array_and_namespace with_admin_test_routes do assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], :action => 'new') @@ -267,6 +286,12 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_irregular_plural_class + with_test_routes do + assert_equal "http://example.com/taxes", polymorphic_url(@tax.class) + end + end + def test_with_irregular_plural_new_record with_test_routes do assert_equal "http://example.com/taxes", polymorphic_url(@tax) @@ -320,6 +345,12 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_class_with_irregular_plural_array_and_namespace + with_admin_test_routes do + assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax.class]) + end + end + def test_unsaved_with_irregular_plural_array_and_namespace with_admin_test_routes do assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax]) -- cgit v1.2.3 From 7f84f14efabf3e342a231b8aa9650cb484c802d6 Mon Sep 17 00:00:00 2001 From: Cristi Balan Date: Sat, 8 Aug 2009 17:39:31 +0100 Subject: Add rake db:forward - opposite of db:rollback [#768 state:resolved] Example: rake db:forward # performs the next migration rake db:forward STEP=4 # performs the next 4 migrations Signed-off-by: Pratik Naik --- activerecord/lib/active_record/migration.rb | 10 ++++++++++ activerecord/test/cases/migration_test.rb | 11 +++++++++++ railties/lib/tasks/databases.rake | 7 +++++++ 3 files changed, 28 insertions(+) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 3963baa6b8..d205e57db3 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -397,6 +397,16 @@ module ActiveRecord down(migrations_path, finish ? finish.version : 0) end + def forward(migrations_path, steps=1) + migrator = self.new(:up, migrations_path) + start_index = migrator.migrations.index(migrator.current_migration) + + return unless start_index + + finish = migrator.migrations[start_index + steps] + up(migrations_path, finish ? finish.version : 0) + end + def up(migrations_path, target_version = nil) self.new(:up, migrations_path, target_version).migrate end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 72d4892435..f0f21615e0 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1136,6 +1136,17 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal(0, ActiveRecord::Migrator.current_version) end + def test_migrator_forward + ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) + assert_equal(1, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2) + assert_equal(3, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + end + def test_schema_migrations_table_name ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 0d4d658315..b00a789974 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -172,6 +172,13 @@ namespace :db do Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end + desc 'Pushes the schema to the next version. Specify the number of steps with STEP=n' + task :forward => :environment do + step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + ActiveRecord::Migrator.forward('db/migrate/', step) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' task :reset => [ 'db:drop', 'db:setup' ] -- cgit v1.2.3 From ed8a0a1c2370ab8715434ba824b2826d807401d5 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 8 Aug 2009 17:53:47 +0100 Subject: Ensure db:drop:all doesn't error out on exception [#2997 state:resolved] [Anthony Caliendo] --- railties/lib/tasks/databases.rake | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index b00a789974..23a3a73a7f 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -101,8 +101,12 @@ namespace :db do ActiveRecord::Base.configurations.each_value do |config| # Skip entries that don't have a database key next unless config['database'] - # Only connect to local databases - local_database?(config) { drop_database(config) } + begin + # Only connect to local databases + local_database?(config) { drop_database(config) } + rescue Exception => e + puts "Couldn't drop #{config['database']} : #{e.inspect}" + end end end end -- cgit v1.2.3