diff options
104 files changed, 1044 insertions, 748 deletions
@@ -9,6 +9,7 @@ else end gem "rack", :git => "git://github.com/rack/rack.git" +gem "rack-test", :git => "git://github.com/brynary/rack-test.git" gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -47,7 +48,7 @@ platforms :ruby do gem "nokogiri", ">= 1.4.4" # AR - gem "sqlite3-ruby", "~> 1.3.1", :require => 'sqlite3' + gem "sqlite3", "~> 1.3.3" group :db do gem "pg", ">= 0.9.0" diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 29b5813785..2ae85f8b57 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -20,5 +20,5 @@ Gem::Specification.new do |s| s.has_rdoc = true s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.2.9') + s.add_dependency('mail', '~> 2.2.15') end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index d67d563181..6ae1eac42a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -246,7 +246,7 @@ module ActionMailer #:nodoc: # but Action Mailer translates them appropriately and sets the correct values. # # As you can pass in any header, you need to either quote the header as a string, or pass it in as - # an underscorised symbol, so the following will work: + # an underscored symbol, so the following will work: # # class Notifier < ActionMailer::Base # default 'Content-Transfer-Encoding' => '7bit', @@ -298,7 +298,7 @@ module ActionMailer #:nodoc: # # * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method. # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>. - # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@addres</tt> + # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt> # added automatically before the message is sent. # # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method. @@ -404,7 +404,7 @@ module ActionMailer #:nodoc: end end - def respond_to?(method, *args) #:nodoc: + def respond_to?(method, include_private = false) #:nodoc: super || action_methods.include?(method.to_s) end diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb index dedb447ced..04728cafb0 100644 --- a/actionmailer/lib/action_mailer/old_api.rb +++ b/actionmailer/lib/action_mailer/old_api.rb @@ -242,12 +242,12 @@ module ActionMailer ct.to_s.split("/") end - def parse_content_type(defaults=nil) + def parse_content_type if @content_type.blank? [ nil, {} ] else ctype, *attrs = @content_type.split(/;\s*/) - attrs = Hash[attrs.map { |attr| attr.split(/\=/, 2) }] + attrs = Hash[attrs.map { |attr| attr.split(/=/, 2) }] [ctype, {"charset" => @charset}.merge!(attrs)] end end diff --git a/actionmailer/lib/action_mailer/tmail_compat.rb b/actionmailer/lib/action_mailer/tmail_compat.rb index 26cc474e91..1b2cdcfb27 100644 --- a/actionmailer/lib/action_mailer/tmail_compat.rb +++ b/actionmailer/lib/action_mailer/tmail_compat.rb @@ -2,16 +2,18 @@ module Mail class Message def set_content_type(*args) - ActiveSupport::Deprecation.warn('Message#set_content_type is deprecated, please just call ' << - 'Message#content_type with the same arguments', caller[0,2]) + message = 'Message#set_content_type is deprecated, please just call ' << + 'Message#content_type with the same arguments' + ActiveSupport::Deprecation.warn(message, caller[0,2]) content_type(*args) end alias :old_transfer_encoding :transfer_encoding def transfer_encoding(value = nil) if value - ActiveSupport::Deprecation.warn('Message#transfer_encoding is deprecated, please call ' << - 'Message#content_transfer_encoding with the same arguments', caller[0,2]) + message = 'Message#transfer_encoding is deprecated, ' << + 'please call Message#content_transfer_encoding with the same arguments' + ActiveSupport::Deprecation.warn(message, caller[0,2]) content_transfer_encoding(value) else old_transfer_encoding @@ -19,16 +21,17 @@ module Mail end def transfer_encoding=(value) - ActiveSupport::Deprecation.warn('Message#transfer_encoding= is deprecated, please call ' << - 'Message#content_transfer_encoding= with the same arguments', caller[0,2]) + message = 'Message#transfer_encoding= is deprecated, ' << + 'please call Message#content_transfer_encoding= with the same arguments' + ActiveSupport::Deprecation.warn(message, caller[0,2]) self.content_transfer_encoding = value end def original_filename - ActiveSupport::Deprecation.warn('Message#original_filename is deprecated, ' << - 'please call Message#filename', caller[0,2]) + message = 'Message#original_filename is deprecated, please call Message#filename' + ActiveSupport::Deprecation.warn(message, caller[0,2]) filename end end -end
\ No newline at end of file +end diff --git a/actionmailer/test/fixtures/i18n_test_mailer/mail_with_i18n_subject.erb b/actionmailer/test/fixtures/i18n_test_mailer/mail_with_i18n_subject.erb new file mode 100644 index 0000000000..f5340283f1 --- /dev/null +++ b/actionmailer/test/fixtures/i18n_test_mailer/mail_with_i18n_subject.erb @@ -0,0 +1,4 @@ +Hello there, + +Mr. <%= @recipient %>. Be greeted, new member! + diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb new file mode 100644 index 0000000000..7040ae6f8d --- /dev/null +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -0,0 +1,46 @@ +require 'abstract_unit' +require 'action_controller' + +class I18nTestMailer < ActionMailer::Base + configure do |c| + c.assets_dir = '' + end + + def mail_with_i18n_subject(recipient) + @recipient = recipient + I18n.locale = :de + mail(:to => recipient, :subject => "#{I18n.t :email_subject} #{recipient}", + :from => "system@loudthinking.com", :date => Time.local(2004, 12, 12)) + end +end + +class TestController < ActionController::Base + def send_mail + I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver + render :text => 'Mail sent' + end +end + +class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + match ':controller(/:action(/:id))' + end + + def app + Routes + end + + def setup + I18n.backend.store_translations('de', :email_subject => '[Signed up] Welcome') + end + + def teardown + I18n.locale = :en + end + + def test_send_mail + get '/test/send_mail' + assert_equal "Mail sent", @response.body + end +end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 9b912ea988..691310d5d2 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -13,14 +13,15 @@ module AbstractController # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, # it will trigger the lookup_context and consequently expire the cache. class I18nProxy < ::I18n::Config #:nodoc: - attr_reader :i18n_config, :lookup_context + attr_reader :original_config, :lookup_context - def initialize(i18n_config, lookup_context) - @i18n_config, @lookup_context = i18n_config, lookup_context + def initialize(original_config, lookup_context) + original_config = original_config.original_config if original_config.respond_to?(:original_config) + @original_config, @lookup_context = original_config, lookup_context end def locale - @i18n_config.locale + @original_config.locale end def locale=(value) @@ -112,7 +113,7 @@ module AbstractController def render_to_string(*args, &block) options = _normalize_args(*args, &block) _normalize_options(options) - render_to_body(options) + render_to_body(options).tap { self.response_body = nil } end # Raw rendering of a template to a Rack-compatible body. diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 3b19310a69..3fae697cc3 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -16,7 +16,11 @@ module ActionController payload = event.payload additions = ActionController::Base.log_process_action(payload) - message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration + status = payload[:status] + if status.nil? && payload[:exception].present? + status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil + end + message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? info(message) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 329798e84f..3df3c341bb 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -133,7 +133,7 @@ module ActionController end def response_body=(val) - body = val.respond_to?(:each) ? val : [val] + body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val]) super body end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 1d2f7e4f19..4f4cb96a74 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -43,7 +43,7 @@ module ActionDispatch alias :etag? :etag def initialize(*) - status, header, body = super + super @cache_control = {} @etag = self["ETag"] diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index f369d2d3c2..7ac608f0a8 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -90,17 +90,14 @@ module ActionDispatch # **.**, ***.** style TLDs like co.uk or com.au # # www.example.co.uk gives: - # $1 => example - # $2 => co.uk + # $& => example.co.uk # # example.com gives: - # $1 => example - # $2 => com + # $& => example.com # # lots.of.subdomains.example.local gives: - # $1 => example - # $2 => local - DOMAIN_REGEXP = /([^.]*)\.([^.]*|..\...|...\...)$/ + # $& => example.local + DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ def self.build(request) secret = request.env[TOKEN_KEY] @@ -131,11 +128,17 @@ module ActionDispatch options[:path] ||= "/" if options[:domain] == :all + # if there is a provided tld length then we use it otherwise default domain regexp + domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP + # if host is not ip and matches domain regexp # (ip confirms to domain regexp so we explicitly check for ip) - options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ DOMAIN_REGEXP) - ".#{$1}.#{$2}" + options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp) + ".#{$&}" end + elsif options[:domain].is_a? Array + # if host matches one of the supplied domains without a dot in front of it + options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] } end end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 30e6c078b1..385378ea29 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -44,8 +44,8 @@ module ActionView private # TODO: Create an object that has caching read/write on it def fragment_for(name = {}, options = nil, &block) #:nodoc: - if controller.fragment_exist?(name, options) - controller.read_fragment(name, options) + if fragment = controller.read_fragment(name, options) + fragment else # VIEW TODO: Make #capture usable outside of ERB # This dance is needed because Builder can't use capture diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 1365048724..e434f3b059 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -186,11 +186,11 @@ module ActionView end # Overload locale= to also set the I18n.locale. If the current I18n.config object responds - # to i18n_config, it means that it's has a copy of the original I18n configuration and it's + # to original_config, it means that it's has a copy of the original I18n configuration and it's # acting as proxy, which we need to skip. def locale=(value) if value - config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config + config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config config.locale = value end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index d7d98e1dd5..e246646963 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -56,6 +56,7 @@ module ActionView attr_reader :original_exception, :backtrace def initialize(template, assigns, original_exception) + super(original_exception.message) @template, @assigns, @original_exception = template, assigns.dup, original_exception @sub_templates = nil @backtrace = original_exception.backtrace @@ -65,10 +66,6 @@ module ActionView @template.identifier end - def message - ActiveSupport::Deprecation.silence { original_exception.message } - end - def sub_template_message if @sub_templates "Trace of template inclusion: " + diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index d4f16d4b36..2ce109ea99 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -169,6 +169,7 @@ module ActionView INTERNAL_IVARS = %w{ @__name__ + @__io__ @_assertion_wrapped @_assertions @_result diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 5308fc849b..3bdde86291 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -22,7 +22,7 @@ module AbstractController class TestCallbacks1 < ActiveSupport::TestCase test "basic callbacks work" do controller = Callback1.new - result = controller.process(:index) + controller.process(:index) assert_equal "Hello world", controller.response_body end end @@ -62,7 +62,7 @@ module AbstractController end test "before_filter works" do - result = @controller.process(:index) + @controller.process(:index) assert_equal "Hello world", @controller.response_body end @@ -78,7 +78,7 @@ module AbstractController test "before_filter with overwritten condition" do @controller = Callback2Overwrite.new - result = @controller.process(:index) + @controller.process(:index) assert_equal "", @controller.response_body end end @@ -103,12 +103,12 @@ module AbstractController end test "before_filter works with procs" do - result = @controller.process(:index) + @controller.process(:index) assert_equal "Hello world", @controller.response_body end test "after_filter works with procs" do - result = @controller.process(:index) + @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end end @@ -152,7 +152,7 @@ module AbstractController end test "when :except is specified, an after filter is not triggered on that action" do - result = @controller.process(:index) + @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end @@ -186,17 +186,17 @@ module AbstractController end test "when :only is specified with an array, a before filter is triggered on that action" do - result = @controller.process(:index) + @controller.process(:index) assert_equal "Hello, World", @controller.response_body end test "when :only is specified with an array, a before filter is not triggered on other actions" do - result = @controller.process(:sekrit_data) + @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end test "when :except is specified with an array, an after filter is not triggered on that action" do - result = @controller.process(:index) + @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end @@ -216,12 +216,12 @@ module AbstractController end test "when a callback is modified in a child with :only, it works for the :only action" do - result = @controller.process(:index) + @controller.process(:index) assert_equal "Hello world", @controller.response_body end test "when a callback is modified in a child with :only, it does not work for other actions" do - result = @controller.process(:not_index) + @controller.process(:not_index) assert_equal "", @controller.response_body end end @@ -245,14 +245,14 @@ module AbstractController assert_equal "Success", controller.response_body end end - + class CallbacksWithArgs < ControllerWithCallbacks set_callback :process_action, :before, :first def first @text = "Hello world" end - + def index(text) self.response_body = @text + text end @@ -261,7 +261,7 @@ module AbstractController class TestCallbacksWithArgs < ActiveSupport::TestCase test "callbacks still work when invoking process with multiple args" do controller = CallbacksWithArgs.new - result = controller.process(:index, " Howdy!") + controller.process(:index, " Howdy!") assert_equal "Hello world Howdy!", controller.response_body end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index c7b54eb0ba..af02c7f9fa 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -257,7 +257,6 @@ class ActionCachingMockController end def request - mocked_path = @mock_path Object.new.instance_eval(<<-EVAL) def path; '#{@mock_path}' end def format; 'all' end @@ -416,7 +415,6 @@ class ActionCacheTest < ActionController::TestCase get :index assert_response :success - new_cached_time = content_to_cache assert_not_equal cached_time, @response.body end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index e6fe0b1f04..21bbd83653 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -32,6 +32,11 @@ module Another cache_page("Super soaker", "/index.html") render :nothing => true end + + def with_exception + raise Exception + end + end end @@ -139,20 +144,20 @@ class ACLogSubscriberTest < ActionController::TestCase wait assert_equal 4, logs.size - assert_match(/Exist fragment\? views\/foo/, logs[1]) + assert_match /Read fragment views\/foo/, logs[1] assert_match(/Write fragment views\/foo/, logs[2]) ensure @controller.config.perform_caching = true end - + def test_with_fragment_cache_and_percent_in_key @controller.config.perform_caching = true get :with_fragment_cache_and_percent_in_key wait assert_equal 4, logs.size - assert_match(/Exist fragment\? views\/foo%bar/, logs[1]) - assert_match(/Write fragment views\/foo%bar/, logs[2]) + assert_match /Read fragment views\/foo/, logs[1] + assert_match /Write fragment views\/foo/, logs[2] ensure @controller.config.perform_caching = true end @@ -168,6 +173,16 @@ class ACLogSubscriberTest < ActionController::TestCase ensure @controller.config.perform_caching = true end + + def test_process_action_with_exception_includes_http_status_code + begin + get :with_exception + wait + rescue Exception => e + end + assert_equal 2, logs.size + assert_match(/Completed 500/, logs.last) + end def logs @logs ||= @logger.logged(:info) diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index fe14d24327..5debf96232 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -658,7 +658,7 @@ class RespondWithControllerTest < ActionController::TestCase @request.accept = "application/json" get :using_hash_resource assert_equal "application/json", @response.content_type - assert_equal %Q[{"result":["david",13]}], @response.body + assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body end def test_using_resource_with_block diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 6dd2a9f23d..fc604a2db3 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -26,6 +26,10 @@ class RenderJsonTest < ActionController::TestCase render :json => nil end + def render_json_render_to_string + render :text => render_to_string(:json => '[]') + end + def render_json_hello_world render :json => ActiveSupport::JSON.encode(:hello => 'world') end @@ -76,6 +80,12 @@ class RenderJsonTest < ActionController::TestCase assert_equal 'application/json', @response.content_type end + def test_render_json_render_to_string + get :render_json_render_to_string + assert_equal '[]', @response.body + end + + def test_render_json get :render_json_hello_world assert_equal '{"hello":"world"}', @response.body diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 6ba4c6c48d..621fb79915 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -216,7 +216,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_typecast_as_yaml with_test_route_set do with_params_parsers Mime::YAML => :yaml do - yaml = <<-YAML + yaml = (<<-YAML).strip --- data: a: 15 diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index e2040401c7..1cfea6aa12 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -95,6 +95,26 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_cookie_with_domain_and_tld + cookies[:user_name] = {:value => "rizwanreza", :domain => :all, :tld_length => 2} + head :ok + end + + def delete_cookie_with_domain_and_tld + cookies.delete(:user_name, :domain => :all, :tld_length => 2) + head :ok + end + + def set_cookie_with_domains + cookies[:user_name] = {:value => "rizwanreza", :domain => %w(example1.com example2.com .example3.com)} + head :ok + end + + def delete_cookie_with_domains + cookies.delete(:user_name, :domain => %w(example1.com example2.com .example3.com)) + head :ok + end + def symbol_key cookies[:user_name] = "david" head :ok @@ -322,6 +342,67 @@ class CookiesTest < ActionController::TestCase assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" end + def test_cookie_with_all_domain_option_and_tld_length + get :set_cookie_with_domain_and_tld + assert_response :success + assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com; path=/" + end + + def test_cookie_with_all_domain_option_using_a_non_standard_tld_and_tld_length + @request.host = "two.subdomains.nextangle.local" + get :set_cookie_with_domain_and_tld + assert_response :success + assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/" + end + + def test_cookie_with_all_domain_option_using_host_with_port_and_tld_length + @request.host = "nextangle.local:3000" + get :set_cookie_with_domain_and_tld + assert_response :success + assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/" + end + + def test_deleting_cookie_with_all_domain_option_and_tld_length + get :delete_cookie_with_domain_and_tld + assert_response :success + assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + end + + def test_cookie_with_several_preset_domains_using_one_of_these_domains + @request.host = "example1.com" + get :set_cookie_with_domains + assert_response :success + assert_cookie_header "user_name=rizwanreza; domain=example1.com; path=/" + end + + def test_cookie_with_several_preset_domains_using_other_domain + @request.host = "other-domain.com" + get :set_cookie_with_domains + assert_response :success + assert_cookie_header "user_name=rizwanreza; path=/" + end + + def test_cookie_with_several_preset_domains_using_shared_domain + @request.host = "example3.com" + get :set_cookie_with_domains + assert_response :success + assert_cookie_header "user_name=rizwanreza; domain=.example3.com; path=/" + end + + def test_deletings_cookie_with_several_preset_domains_using_one_of_these_domains + @request.host = "example2.com" + get :delete_cookie_with_domains + assert_response :success + assert_cookie_header "user_name=; domain=example2.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + end + + def test_deletings_cookie_with_several_preset_domains_using_other_domain + @request.host = "other-domain.com" + get :delete_cookie_with_domains + assert_response :success + assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" + end + def test_cookies_hash_is_indifferent_access [:symbol_key, :string_key].each do |cookie_key| get cookie_key diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index f7a684779c..f3b1335000 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -73,7 +73,7 @@ class LookupContextTest < ActiveSupport::TestCase assert_equal :pt, I18n.locale assert_equal :pt, @lookup_context.locale ensure - I18n.config = I18n.config.i18n_config + I18n.config = I18n.config.original_config end assert_equal :pt, I18n.locale diff --git a/actionpack/test/template/template_error_test.rb b/actionpack/test/template/template_error_test.rb new file mode 100644 index 0000000000..3a874082d9 --- /dev/null +++ b/actionpack/test/template/template_error_test.rb @@ -0,0 +1,13 @@ +require "abstract_unit" + +class TemplateErrorTest < ActiveSupport::TestCase + def test_provides_original_message + error = ActionView::Template::Error.new("test", {}, Exception.new("original")) + assert_equal "original", error.message + end + + def test_provides_useful_inspect + error = ActionView::Template::Error.new("test", {}, Exception.new("original")) + assert_equal "#<ActionView::Template::Error: original>", error.inspect + end +end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index 203515d508..11c355dc6d 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -167,7 +167,7 @@ module ActionView end end end - + class HelperExposureTest < ActionView::TestCase helper(Module.new do def render_from_helper diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 66cd9fdde6..97e31d4243 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -20,32 +20,32 @@ module ActiveModel # For example, a logged in user may need to assign additional attributes depending # on their role: # - # class AccountsController < ApplicationController - # include ActiveModel::MassAssignmentSecurity + # class AccountsController < ApplicationController + # include ActiveModel::MassAssignmentSecurity # - # attr_accessible :first_name, :last_name + # attr_accessible :first_name, :last_name # - # def self.admin_accessible_attributes - # accessible_attributes + [ :plan_id ] - # end + # def self.admin_accessible_attributes + # accessible_attributes + [ :plan_id ] + # end # - # def update - # ... - # @account.update_attributes(account_params) - # ... - # end + # def update + # ... + # @account.update_attributes(account_params) + # ... + # end # - # protected + # protected # - # def account_params - # sanitize_for_mass_assignment(params[:account]) - # end + # def account_params + # sanitize_for_mass_assignment(params[:account]) + # end # - # def mass_assignment_authorizer - # admin ? admin_accessible_attributes : super - # end + # def mass_assignment_authorizer + # admin ? admin_accessible_attributes : super + # end # - # end + # end # module ClassMethods # Attributes named in this macro are protected from mass-assignment diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 0d2dd36e59..bf4fd0740c 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -13,14 +13,18 @@ module ActiveModel # # Activates the observers assigned. Examples: # + # class ORM + # include ActiveModel::Observing + # end + # # # Calls PersonObserver.instance - # ActiveRecord::Base.observers = :person_observer + # ORM.observers = :person_observer # # # Calls Cacher.instance and GarbageCollector.instance - # ActiveRecord::Base.observers = :cacher, :garbage_collector + # ORM.observers = :cacher, :garbage_collector # # # Same as above, just using explicit class references - # ActiveRecord::Base.observers = Cacher, GarbageCollector + # ORM.observers = Cacher, GarbageCollector # # Note: Setting this does not instantiate the observers yet. # +instantiate_observers+ is called during startup, and before diff --git a/activemodel/test/cases/serializeration/xml_serialization_test.rb b/activemodel/test/cases/serializeration/xml_serialization_test.rb index cc19d322b3..b6a2f88667 100644 --- a/activemodel/test/cases/serializeration/xml_serialization_test.rb +++ b/activemodel/test/cases/serializeration/xml_serialization_test.rb @@ -114,7 +114,7 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize yaml" do - assert_match %r{<preferences type=\"yaml\">--- !ruby/struct:Customer \nname: John\n</preferences>}, @contact.to_xml + assert_match %r{<preferences type=\"yaml\">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml end test "should call proc on object" do diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index aa447ce992..d84fe655a7 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,15 @@ *Rails 3.1.0 (unreleased)* +* Removed support for accessing attributes on a has_and_belongs_to_many join table. This has been + documented as deprecated behaviour since April 2006. Please use has_many :through instead. + [Jon Leighton] + +* Added a create_association! method for has_one and belongs_to associations. [Jon Leighton] + +* Migration files generated from model and constructive migration generators + (for example, add_name_to_users) use the reversible migration's `change` + method instead of the ordinary `up` and `down` methods. [Prem Sichanugrist] + * Removed support for interpolated SQL conditions. Please use scoping along with attribute conditionals as a replacement. @@ -22,7 +32,7 @@ along with attribute conditionals as a replacement. User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user -* When a model is generated add_index is added by default for belongs_to or references columns +* When a model is generated add_index is added by default for belongs_to or references columns rails g model post user:belongs_to will generate the following: diff --git a/activerecord/Rakefile b/activerecord/Rakefile index ef99e0b26f..e414c4fb1c 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -72,6 +72,15 @@ end end end +rule '.sqlite3' do |t| + sh %Q{sqlite3 "#{t.name}" "create table a (a integer); drop table a;"} +end + +task :test_sqlite3 => [ + 'test/fixtures/fixture_database.sqlite3', + 'test/fixtures/fixture_database_2.sqlite3' +] + namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 0bc26cc672..90d3b58c78 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -220,37 +220,32 @@ module ActiveRecord private def reader_method(name, class_name, mapping, allow_nil, constructor) - module_eval do - define_method(name) do - if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) - attrs = mapping.collect {|pair| read_attribute(pair.first)} - object = constructor.respond_to?(:call) ? - constructor.call(*attrs) : - class_name.constantize.send(constructor, *attrs) - @aggregation_cache[name] = object - end - @aggregation_cache[name] + define_method(name) do + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) + attrs = mapping.collect {|pair| read_attribute(pair.first)} + object = constructor.respond_to?(:call) ? + constructor.call(*attrs) : + class_name.constantize.send(constructor, *attrs) + @aggregation_cache[name] = object end + @aggregation_cache[name] end - end def writer_method(name, class_name, mapping, allow_nil, converter) - module_eval do - define_method("#{name}=") do |part| - if part.nil? && allow_nil - mapping.each { |pair| self[pair.first] = nil } - @aggregation_cache[name] = nil - else - unless part.is_a?(class_name.constantize) || converter.nil? - part = converter.respond_to?(:call) ? - converter.call(part) : - class_name.constantize.send(converter, part) - end - - mapping.each { |pair| self[pair.first] = part.send(pair.last) } - @aggregation_cache[name] = part.freeze + define_method("#{name}=") do |part| + if part.nil? && allow_nil + mapping.each { |pair| self[pair.first] = nil } + @aggregation_cache[name] = nil + else + unless part.is_a?(class_name.constantize) || converter.nil? + part = converter.respond_to?(:call) ? + converter.call(part) : + class_name.constantize.send(converter, part) end + + mapping.each { |pair| self[pair.first] = part.send(pair.last) } + @aggregation_cache[name] = part.freeze end end end diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 68aaff175a..cba4bab3ef 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -165,10 +165,8 @@ module ActiveRecord end id_to_record_map.each do |id, records| - next if seen_keys.include?(id.to_s) - records.each do |record| - record.send(:association_proxy, reflection_name).target = nil - end + next if seen_keys.include?(id) + add_preloaded_record_to_collection(records, reflection_name, nil) end end @@ -177,23 +175,18 @@ module ActiveRecord # <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash, # and +ids+ is an Array of record IDs. def construct_id_map(records, primary_key=nil) - id_to_record_map = {} - ids = [] - records.each do |record| + records.group_by do |record| primary_key ||= record.class.primary_key - ids << record[primary_key] - mapped_records = (id_to_record_map[ids.last.to_s] ||= []) - mapped_records << record + record[primary_key].to_s end - ids.uniq! - return id_to_record_map, ids end def preload_has_and_belongs_to_many_association(records, reflection, preload_options={}) left = reflection.klass.arel_table - id_to_record_map, ids = construct_id_map(records) + id_to_record_map = construct_id_map(records) + records.each {|record| record.send(reflection.name).loaded} options = reflection.options @@ -222,12 +215,11 @@ module ActiveRecord klass = associated_records_proxy.klass - associated_records(ids) { |some_ids| + associated_records(id_to_record_map.keys) { |some_ids| method = in_or_equal(some_ids) - conditions = right[reflection.foreign_key].send(*method) - conditions = custom_conditions.inject(conditions) do |ast, cond| - ast.and cond - end + conditions = right.create_and( + [right[reflection.foreign_key].send(*method)] + + custom_conditions) relation = associated_records_proxy.where(conditions) klass.connection.select_all(relation.arel.to_sql, 'SQL', relation.bind_values) @@ -242,12 +234,10 @@ module ActiveRecord def preload_has_one_association(records, reflection, preload_options={}) return if records.first.send(:association_proxy, reflection.name).loaded? - id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key]) + id_to_record_map = construct_id_map(records, reflection.options[:primary_key]) options = reflection.options - records.each do |record| - record.send(:association_proxy, reflection.name).target = nil - end + add_preloaded_record_to_collection(records, reflection.name, nil) if options[:through] through_records = preload_through_records(records, reflection, options[:through]) @@ -258,7 +248,7 @@ module ActiveRecord source = reflection.source_reflection.name through_records.first.class.preload_associations(through_records, source) if through_reflection.macro == :belongs_to - id_to_record_map = construct_id_map(records, through_primary_key).first + id_to_record_map = construct_id_map(records, through_primary_key) through_primary_key = through_reflection.klass.primary_key end @@ -268,7 +258,7 @@ module ActiveRecord end end else - set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.foreign_key) + set_association_single_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options), reflection.foreign_key) end end @@ -277,7 +267,7 @@ module ActiveRecord options = reflection.options foreign_key = reflection.through_reflection_foreign_key - id_to_record_map, ids = construct_id_map(records, foreign_key || reflection.options[:primary_key]) + id_to_record_map = construct_id_map(records, foreign_key || reflection.options[:primary_key]) records.each {|record| record.send(reflection.name).loaded} if options[:through] @@ -293,7 +283,7 @@ module ActiveRecord end else - set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), + set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options), reflection.foreign_key) end end @@ -391,7 +381,7 @@ module ActiveRecord conditions << table["#{interface}_type"].eq(base_class.sti_name) end - conditions += append_conditions(reflection, preload_options) + conditions.concat append_conditions(reflection, preload_options) find_options = { :select => preload_options[:select] || options[:select] || table[Arel.star], @@ -403,9 +393,7 @@ module ActiveRecord associated_records(ids) do |some_ids| method = in_or_equal(some_ids) - where = conditions.inject(table[key].send(*method)) do |ast, cond| - ast.and cond - end + where = table.create_and(conditions + [table[key].send(*method)]) reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => where)).to_a end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a03d1bbb06..891ac52f8a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -119,6 +119,7 @@ module ActiveRecord # These classes will be loaded when associations are created. # So there is no need to eager load them. autoload :AssociationCollection, 'active_record/associations/association_collection' + autoload :SingularAssociation, 'active_record/associations/singular_association' autoload :AssociationProxy, 'active_record/associations/association_proxy' autoload :ThroughAssociation, 'active_record/associations/through_association' autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association' @@ -208,7 +209,7 @@ module ActiveRecord # other=(other) | X | X | X # build_other(attributes={}) | X | | X # create_other(attributes={}) | X | | X - # other.create!(attributes={}) | | | X + # create_other!(attributes={}) | X | | X # # ===Collection associations (one-to-many / many-to-many) # | | | has_many @@ -1042,6 +1043,9 @@ module ActiveRecord # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt> + # if the record is invalid. # # (+association+ is replaced with the symbol passed as the first argument, so # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.) @@ -1053,6 +1057,7 @@ module ActiveRecord # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>) # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>) # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>) + # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>) # # === Options # @@ -1157,6 +1162,9 @@ module ActiveRecord # Returns a new object of the associated type that has been instantiated # with +attributes+, linked to this object through a foreign key, and that # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt> + # if the record is invalid. # # (+association+ is replaced with the symbol passed as the first argument, so # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.) @@ -1168,6 +1176,7 @@ module ActiveRecord # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>) # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>) # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>) + # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>) # The declaration can also include an options hash to specialize the behavior of the association. # # === Options @@ -1292,12 +1301,6 @@ module ActiveRecord # end # end # - # Deprecated: Any additional fields added to the join table will be placed as attributes when - # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join - # tables with additional attributes will be marked as readonly (because we can't save changes - # to the additional attributes). It's strongly recommended that you upgrade any - # associations with attributes to a real join model (see introduction). - # # Adds the following methods for retrieval and query: # # [collection(force_reload = false)] @@ -1533,10 +1536,10 @@ module ActiveRecord def association_constructor_methods(reflection) constructors = { - "build_#{reflection.name}" => "build", - "create_#{reflection.name}" => "create" + "build_#{reflection.name}" => "build", + "create_#{reflection.name}" => "create", + "create_#{reflection.name}!" => "create!" } - constructors["create_#{reflection.name}!"] = "create!" if reflection.macro == :has_one constructors.each do |name, proxy_name| redefine_method(name) do |*params| diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index e65ef2b768..24fb49a65d 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -349,35 +349,19 @@ module ActiveRecord end def load_target - if !@owner.new_record? || foreign_key_present? + if (!@owner.new_record? || foreign_key_present?) && !loaded? + targets = [] + begin - unless loaded? - if @target.is_a?(Array) && @target.any? - @target = find_target.map do |f| - i = @target.index(f) - if i - @target.delete_at(i).tap do |t| - keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names) - # FIXME: this call to attributes causes many NoMethodErrors - attributes = f.attributes - (attributes.keys - keys).each do |k| - t.send("#{k}=", attributes[k]) - end - end - else - f - end - end + @target - else - @target = find_target - end - end + targets = find_target rescue ActiveRecord::RecordNotFound reset end + + @target = merge_target_lists(targets, @target) end - loaded if target + loaded target end @@ -415,7 +399,7 @@ module ActiveRecord end def reset_target! - @target = Array.new + @target = [] end def reset_scopes_cache! @@ -450,6 +434,27 @@ module ActiveRecord end private + def merge_target_lists(loaded, existing) + return loaded if existing.empty? + return existing if loaded.empty? + + loaded.map do |f| + i = existing.index(f) + if i + existing.delete_at(i).tap do |t| + keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names) + # FIXME: this call to attributes causes many NoMethodErrors + attributes = f.attributes + (attributes.keys - keys).each do |k| + t.send("#{k}=", attributes[k]) + end + end + else + f + end + end + existing + end + # Do the relevant stuff to insert the given record into the association collection. The # force param specifies whether or not an exception should be raised on failure. The # validate param specifies whether validation should be performed (if force is false). @@ -513,7 +518,7 @@ module ActiveRecord def fetch_first_or_last_using_find?(args) (args.first.kind_of?(Hash) && !args.first.empty?) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || - @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer)) + @target.any? { |record| record.new_record? || record.changed? } || args.first.kind_of?(Integer)) end def include_in_memory?(record) diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index f2de788522..addc64cb42 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -7,14 +7,15 @@ module ActiveRecord # This is the root class of all association proxies ('+ Foo' signifies an included module Foo): # # AssociationProxy - # BelongsToAssociation - # BelongsToPolymorphicAssociation + # SingularAssociaton + # HasOneAssociation + # HasOneThroughAssociation + ThroughAssociation + # BelongsToAssociation + # BelongsToPolymorphicAssociation # AssociationCollection # HasAndBelongsToManyAssociation # HasManyAssociation # HasManyThroughAssociation + ThroughAssociation - # HasOneAssociation - # HasOneThroughAssociation + ThroughAssociation # # Association proxies in Active Record are middlemen between the object that # holds the association, known as the <tt>@owner</tt>, and the actual associated @@ -51,7 +52,7 @@ module ActiveRecord # instantiation of the actual post records. class AssociationProxy #:nodoc: alias_method :proxy_extend, :extend - delegate :to_param, :to => :proxy_target + instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } def initialize(owner, reflection) @@ -63,6 +64,10 @@ module ActiveRecord construct_scope end + def to_param + proxy_target.to_param + end + # Returns the owner of the proxy. def proxy_owner @owner @@ -185,9 +190,8 @@ module ActiveRecord scope = target_klass.unscoped scope = scope.create_with(creation_attributes) scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include)) - scope = scope.where(construct_owner_conditions) scope = scope.select(select_value) if select_value = self.select_value - scope + scope.where(construct_owner_conditions) end def select_value diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 391471849c..e80b945dda 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -1,28 +1,17 @@ module ActiveRecord # = Active Record Belongs To Associations module Associations - class BelongsToAssociation < AssociationProxy #:nodoc: - def create(attributes = {}) - replace(@reflection.create_association(attributes)) - end - - def build(attributes = {}) - replace(@reflection.build_association(attributes)) - end - + class BelongsToAssociation < SingularAssociation #:nodoc: def replace(record) - record = record.target if AssociationProxy === record - raise_on_type_mismatch(record) unless record.nil? + record = check_record(record) update_counters(record) replace_keys(record) set_inverse_instance(record) - @target = record @updated = true if record - loaded - record + self.target = record end def updated? @@ -54,12 +43,8 @@ module ActiveRecord @owner[@reflection.foreign_key] = record && record[@reflection.association_primary_key] end - def find_target - scoped.first.tap { |record| set_inverse_instance(record) } - end - def foreign_key_present? - !@owner[@reflection.foreign_key].nil? + @owner[@reflection.foreign_key] end # NOTE - for now, we're only supporting inverse setting from belongs_to back onto diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index bc7894173d..b28554dce1 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -10,18 +10,6 @@ module ActiveRecord super end - def columns - @reflection.columns(@join_table_name, "#{@join_table_name} Columns") - end - - def reset_column_information - @reflection.reset_column_information - end - - def has_primary_key? - @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@join_table_name) - end - protected def count_records @@ -36,26 +24,11 @@ module ActiveRecord if @reflection.options[:insert_sql] @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record)) else - relation = join_table - timestamps = record_timestamp_columns(record) - timezone = record.send(:current_time_from_proper_timezone) if timestamps.any? - - attributes = columns.map do |column| - name = column.name - value = case name.to_s - when @reflection.foreign_key.to_s - @owner.id - when @reflection.association_foreign_key.to_s - record.id - when *timestamps - timezone - else - @owner.send(:quote_value, record[name], column) if record.has_attribute?(name) - end - [relation[name], value] unless value.nil? - end + stmt = join_table.compile_insert( + join_table[@reflection.foreign_key] => @owner.id, + join_table[@reflection.association_foreign_key] => record.id + ) - stmt = relation.compile_insert Hash[attributes] @owner.connection.insert stmt.to_sql end @@ -89,46 +62,17 @@ module ActiveRecord end def association_scope - scope = super.joins(construct_joins) - scope = scope.readonly if ambiguous_select?(@reflection.options[:select]) - scope + super.joins(construct_joins) end def select_value - super || [@reflection.klass.arel_table[Arel.star], join_table[Arel.star]] - end - - # Join tables with additional columns on top of the two foreign keys must be considered - # ambiguous unless a select clause has been explicitly defined. Otherwise you can get - # broken records back, if, for example, the join column also has an id column. This will - # then overwrite the id column of the records coming back. - def ambiguous_select?(select) - extra_join_columns? && select.nil? - end - - def extra_join_columns? - columns.size > 2 + super || @reflection.klass.arel_table[Arel.star] end private - def record_timestamp_columns(record) - if record.record_timestamps - record.send(:all_timestamp_attributes).map { |x| x.to_s } - else - [] - end - end - def invertible_for?(record) false end - - def find_by_sql(*args) - options = args.extract_options! - ambiguous = ambiguous_select?(@reflection.options[:select] || options[:select]) - - scoped.readonly(ambiguous).find(*(args << options)) - end end end end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index c29ab8dcec..6614cbbf18 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,22 +1,9 @@ module ActiveRecord # = Active Record Belongs To Has One Association module Associations - class HasOneAssociation < AssociationProxy #:nodoc: - def create(attributes = {}) - new_record(:create_association, attributes) - end - - def create!(attributes = {}) - build(attributes).tap { |record| record.save! } - end - - def build(attributes = {}) - new_record(:build_association, attributes) - end - + class HasOneAssociation < SingularAssociation #:nodoc: def replace(record, save = true) - record = record.target if AssociationProxy === record - raise_on_type_mismatch(record) unless record.nil? + record = check_record(record) load_target @reflection.klass.transaction do @@ -36,15 +23,10 @@ module ActiveRecord end end - @target = record - loaded + self.target = record end private - def find_target - scoped.first.tap { |record| set_inverse_instance(record) } - end - def association_scope super.order(@reflection.options[:order]) end @@ -52,12 +34,11 @@ module ActiveRecord alias creation_attributes construct_owner_attributes # The reason that the save param for replace is false, if for create (not just build), - # is because the setting of the foreign keys is actually handled by the scoping, and - # so they are set straight away and do not need to be updated within replace. - def new_record(method, attributes) - record = scoped.scoping { @reflection.send(method, attributes) } + # is because the setting of the foreign keys is actually handled by the scoping when + # the record is instantiated, and so they are set straight away and do not need to be + # updated within replace. + def set_new_record(record) replace(record, false) - record end def remove_target!(method) diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 59a704b7bf..dcd74e7346 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -4,29 +4,28 @@ module ActiveRecord class HasOneThroughAssociation < HasOneAssociation include ThroughAssociation - def replace(new_value) - create_through_record(new_value) - @target = new_value - loaded + def replace(record) + create_through_record(record) + self.target = record end private - def create_through_record(new_value) - proxy = @owner.send(:association_proxy, @reflection.through_reflection.name) - record = proxy.send(:load_target) + def create_through_record(record) + through_proxy = @owner.send(:association_proxy, @reflection.through_reflection.name) + through_record = through_proxy.send(:load_target) - if record && !new_value - record.destroy - elsif new_value - attributes = construct_join_attributes(new_value) + if through_record && !record + through_record.destroy + elsif record + attributes = construct_join_attributes(record) - if record - record.update_attributes(attributes) + if through_record + through_record.update_attributes(attributes) elsif @owner.new_record? - proxy.build(attributes) + through_proxy.build(attributes) else - proxy.create(attributes) + through_proxy.create(attributes) end end end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb new file mode 100644 index 0000000000..b6f49c6f36 --- /dev/null +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -0,0 +1,41 @@ +module ActiveRecord + module Associations + class SingularAssociation < AssociationProxy #:nodoc: + def create(attributes = {}) + record = scoped.scoping { @reflection.create_association(attributes) } + set_new_record(record) + record + end + + def create!(attributes = {}) + build(attributes).tap { |record| record.save! } + end + + def build(attributes = {}) + record = scoped.scoping { @reflection.build_association(attributes) } + set_new_record(record) + record + end + + private + def find_target + scoped.first.tap { |record| set_inverse_instance(record) } + end + + # Implemented by subclasses + def replace(record) + raise NotImplementedError + end + + def set_new_record(record) + replace(record) + end + + def check_record(record) + record = record.target if AssociationProxy === record + raise_on_type_mismatch(record) if record + record + end + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 660fa9a564..ff088200a1 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -60,8 +60,8 @@ module ActiveRecord # Define an attribute reader method. Cope with nil column. def define_read_method(symbol, attr_name, column) - cast_code = column.type_cast_code('v') if column - access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" + cast_code = column.type_cast_code('v') + access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}" unless attr_name.to_s == self.primary_key.to_s access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index dde52269d4..00324b14f2 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1,3 +1,8 @@ +begin + require 'psych' +rescue LoadError +end + require 'yaml' require 'set' require 'active_support/benchmarkable' @@ -829,7 +834,7 @@ module ActiveRecord #:nodoc: def arel_engine @arel_engine ||= begin if self == ActiveRecord::Base - Arel::Table.engine + ActiveRecord::Base else connection_handler.connection_pools[name] ? self : superclass.arel_engine end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 60ccf9edf3..bc8a6e9cd6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -87,8 +87,8 @@ module ActiveRecord def type_cast_code(var_name) case type - when :string then nil - when :text then nil + when :string then var_name + when :text then var_name when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)" when :float then "#{var_name}.to_f" when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})" @@ -98,7 +98,7 @@ module ActiveRecord when :date then "#{self.class.name}.string_to_date(#{var_name})" when :binary then "#{self.class.name}.binary_to_string(#{var_name})" when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})" - else nil + else var_name end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index b6f0511b9a..216c691833 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -1,4 +1,10 @@ require 'erb' + +begin + require 'psych' +rescue LoadError +end + require 'yaml' require 'csv' require 'zlib' diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index db5f1af5ca..69a7642ec5 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -63,6 +63,13 @@ module ActiveRecord alias :& :merge + # Removes from the query the condition(s) specified in +skips+. + # + # Example: + # + # Post.order('id asc').except(:order) # discards the order condition + # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order + # def except(*skips) result = self.class.new(@klass, table) @@ -77,6 +84,13 @@ module ActiveRecord result end + # Removes any condition from the query other than the one(s) specified in +onlies+. + # + # Example: + # + # Post.order('id asc').only(:where) # discards the order condition + # Post.order('id asc').only(:where, :order) # uses the specified order + # def only(*onlies) result = self.class.new(@klass, table) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 868f761a33..48d2f7c9a9 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -325,16 +325,14 @@ module ActiveRecord @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 if @_start_transaction_state[:level] < 1 restore_state = remove_instance_variable(:@_start_transaction_state) - if restore_state - @attributes = @attributes.dup if @attributes.frozen? - @new_record = restore_state[:new_record] - @destroyed = restore_state[:destroyed] - if restore_state.has_key?(:id) - self.id = restore_state[:id] - else - @attributes.delete(self.class.primary_key) - @attributes_cache.delete(self.class.primary_key) - end + @attributes = @attributes.dup if @attributes.frozen? + @new_record = restore_state[:new_record] + @destroyed = restore_state[:destroyed] + if restore_state.has_key?(:id) + self.id = restore_state[:id] + else + @attributes.delete(self.class.primary_key) + @attributes_cache.delete(self.class.primary_key) end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index f367315b22..26c1a9db93 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -37,7 +37,7 @@ module ActiveRecord end end - # The validation process on save can be skipped by passing false. The regular Base#save method is + # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. def save(options={}) perform_validations(options) ? super : false diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 853808eebf..e6a2b40403 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -85,11 +85,16 @@ module ActiveRecord # can be named "davidhh". # # class Person < ActiveRecord::Base - # validates_uniqueness_of :user_name, :scope => :account_id + # validates_uniqueness_of :user_name # end # - # It can also validate whether the value of the specified attributes are unique based on multiple - # scope parameters. For example, making sure that a teacher can only be on the schedule once + # It can also validate whether the value of the specified attributes are unique based on a scope parameter: + # + # class Person < ActiveRecord::Base + # validates_uniqueness_of :user_name, :scope => :account_id + # end + # + # Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once # per semester for a particular class. # # class TeacherSchedule < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 90d8b0d923..b5c938b14a 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -105,9 +105,9 @@ class MysqlReservedWordTest < ActiveRecord::TestCase assert_nothing_raised { x.save } x.order = 'y' assert_nothing_raised { x.save } - assert_nothing_raised { y = Group.find_by_order('y') } - assert_nothing_raised { y = Group.find(1) } - x = Group.find(1) + assert_nothing_raised { Group.find_by_order('y') } + assert_nothing_raised { Group.find(1) } + Group.find(1) end # has_one association with reserved-word table name diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 4c4891dcaf..01073bca3d 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -120,6 +120,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal apple.name, client.firm_name end + def test_create! + client = Client.create!(:name => "Jimmy") + account = client.create_account!(:credit_limit => 10) + assert_equal account, client.account + assert account.persisted? + client.save + client.reload + assert_equal account, client.account + end + + def test_failing_create! + client = Client.create!(:name => "Jimmy") + assert_raise(ActiveRecord::RecordInvalid) { client.create_account! } + assert_not_nil client.account + assert client.account.new_record? + end + def test_natural_assignment_to_nil client = Client.find(3) client.firm = nil @@ -591,4 +608,25 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal groucho, sponsor.sponsorable assert_equal groucho, sponsor.thing end + + def test_build_with_conditions + client = companies(:second_client) + firm = client.build_bob_firm + + assert_equal "Bob", firm.name + end + + def test_create_with_conditions + client = companies(:second_client) + firm = client.create_bob_firm + + assert_equal "Bob", firm.name + end + + def test_create_bang_with_conditions + client = companies(:second_client) + firm = client.create_bob_firm! + + assert_equal "Bob", firm.name + end end diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index c7671a8c22..8957586189 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -4,6 +4,7 @@ require 'models/author' require 'models/comment' require 'models/category' require 'models/categorization' +require 'models/tagging' require 'active_support/core_ext/array/random_access' module Remembered 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 705550216c..30730c7094 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 @@ -101,38 +101,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 't1', record[1] end - def test_should_record_timestamp_for_join_table - setup_data_for_habtm_case - - con = ActiveRecord::Base.connection - sql = 'select * from countries_treaties' - record = con.select_rows(sql).last - assert_not_nil record[2] - assert_not_nil record[3] - if current_adapter?(:Mysql2Adapter, :OracleAdapter) - assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db) - assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db) - else - assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2] - assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3] - end - end - - def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded - begin - Treaty.record_timestamps = false - setup_data_for_habtm_case - - con = ActiveRecord::Base.connection - sql = 'select * from countries_treaties' - record = con.select_rows(sql).last - assert_nil record[2] - assert_nil record[3] - ensure - Treaty.record_timestamps = true - end - end - def test_has_and_belongs_to_many david = Developer.find(1) @@ -218,34 +186,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 2, aredridel.projects(true).size end - def test_adding_uses_default_values_on_join_table - ac = projects(:action_controller) - assert !developers(:jamis).projects.include?(ac) - developers(:jamis).projects << ac - - assert developers(:jamis, :reload).projects.include?(ac) - project = developers(:jamis).projects.detect { |p| p == ac } - assert_equal 1, project.access_level.to_i - end - - def test_habtm_attribute_access_and_respond_to - project = developers(:jamis).projects[0] - assert project.has_attribute?("name") - assert project.has_attribute?("joined_on") - assert project.has_attribute?("access_level") - assert project.respond_to?("name") - assert project.respond_to?("name=") - assert project.respond_to?("name?") - assert project.respond_to?("joined_on") - # given that the 'join attribute' won't be persisted, I don't - # think we should define the mutators - #assert project.respond_to?("joined_on=") - assert project.respond_to?("joined_on?") - assert project.respond_to?("access_level") - #assert project.respond_to?("access_level=") - assert project.respond_to?("access_level?") - end - def test_habtm_adding_before_save no_of_devels = Developer.count no_of_projects = Project.count @@ -430,10 +370,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? end - def test_additional_columns_from_join_table - assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on.to_date - end - def test_destroying david = Developer.find(1) active_record = Project.find(1) @@ -675,25 +611,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker end - def test_updating_attributes_on_rich_associations - david = projects(:action_controller).developers.first - david.name = "DHH" - assert_raise(ActiveRecord::ReadOnlyRecord) { david.save! } - end - - def test_updating_attributes_on_rich_associations_with_limited_find_from_reflection - david = projects(:action_controller).selected_developers.first - david.name = "DHH" - assert_nothing_raised { david.save! } - end - - - def test_updating_attributes_on_rich_associations_with_limited_find - david = projects(:action_controller).developers.find(:all, :select => "developers.*").first - david.name = "DHH" - assert david.save! - end - def test_join_table_alias assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index dcc49e12ca..6f65fb96d1 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -7,6 +7,7 @@ class EachTest < ActiveRecord::TestCase def setup @posts = Post.order("id asc") @total = Post.count + Post.count('id') # preheat arel's table cache end def test_each_should_excecute_one_query_per_batch diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index c5a204b335..2a72838d06 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -258,7 +258,7 @@ end unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? class PessimisticLockingTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false unless supports_savepoints? + self.use_transactional_fixtures = false fixtures :people, :readers def setup diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 36e2c62fbb..bd51388e05 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -6,7 +6,7 @@ require 'models/bird' require_dependency 'models/course' class MultipleDbTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false unless supports_savepoints? + self.use_transactional_fixtures = false def setup @courses = create_fixtures("courses") { Course.retrieve_connection } diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index e1f938be84..d1afe7376a 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -147,6 +147,15 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase pirate.ship_attributes = { :id => "" } assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! } end + + def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record + Man.accepts_nested_attributes_for(:interests) + man = Man.create(:name => "John") + interest = man.interests.create :topic => 'gardning' + man = Man.find man.id + man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}] + assert_equal man.interests.first.topic, man.interests[0].topic + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 8d694900ff..e21109baae 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -44,15 +44,6 @@ class ReadOnlyTest < ActiveRecord::TestCase Developer.joins(', projects').readonly(false).each { |d| assert !d.readonly? } end - - def test_habtm_find_readonly - dev = Developer.find(1) - assert !dev.projects.empty? - assert dev.projects.all?(&:readonly?) - assert dev.projects.find(:all).all?(&:readonly?) - assert dev.projects.readonly(true).all?(&:readonly?) - end - def test_has_many_find_readonly post = Post.find(1) assert !post.comments.empty? diff --git a/activerecord/test/connections/native_sqlite3/connection.rb b/activerecord/test/connections/native_sqlite3/connection.rb index c517c2375e..c2aff5551f 100644 --- a/activerecord/test/connections/native_sqlite3/connection.rb +++ b/activerecord/test/connections/native_sqlite3/connection.rb @@ -3,21 +3,12 @@ require_dependency 'models/course' require 'logger' ActiveRecord::Base.logger = Logger.new("debug.log") -class SqliteError < StandardError -end - BASE_DIR = FIXTURES_ROOT sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3" sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3" def make_connection(clazz, db_file) ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => db_file, :timeout => 5000 } } - unless File.exist?(db_file) - puts "SQLite3 database not found at #{db_file}. Rebuilding it." - sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"} - puts "Executing '#{sqlite_command}'" - raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command) - end clazz.establish_connection(clazz.name) end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index f6e7a5ccf7..3e219fbe4a 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -123,7 +123,9 @@ class Client < Company belongs_to :firm_with_primary_key, :class_name => "Firm", :primary_key => "name", :foreign_key => "firm_name" belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name belongs_to :readonly_firm, :class_name => "Firm", :foreign_key => "firm_id", :readonly => true + belongs_to :bob_firm, :class_name => "Firm", :foreign_key => "client_of", :conditions => { :name => "Bob" } has_many :accounts, :through => :firm + belongs_to :account # Record destruction so we can test whether firm.clients.clear has # is calling client.destroy, deleting from the database, or setting diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 7f366b2c91..5f9bb7ee41 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -154,6 +154,7 @@ ActiveRecord::Schema.define do t.string :name t.integer :client_of t.integer :rating, :default => 1 + t.integer :account_id end add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index" diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 32ebea8571..96ce79e896 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -5,27 +5,28 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' module ActiveSupport - # Callbacks are hooks into the life cycle of an object that allow you to trigger logic - # before or after an alteration of the object state. + # \Callbacks are code hooks that are run at key points in an object's lifecycle. + # The typical use case is to have a base class define a set of callbacks relevant + # to the other functionality it supplies, so that subclasses can install callbacks + # that enhance or modify the base functionality without needing to override + # or redefine methods of the base class. # - # Mixing in this module allows you to define callbacks in your class. + # Mixing in this module allows you to define the events in the object's lifecycle + # that will support callbacks (via +ClassMethods.define_callbacks+), set the instance + # methods, procs, or callback objects to be called (via +ClassMethods.set_callback+), + # and run the installed callbacks at the appropriate times (via +run_callbacks+). # - # Example: - # class Storage - # include ActiveSupport::Callbacks + # Three kinds of callbacks are supported: before callbacks, run before a certain event; + # after callbacks, run after the event; and around callbacks, blocks that surround the + # event, triggering it when they yield. Callback code can be contained in instance + # methods, procs or lambdas, or callback objects that respond to certain predetermined + # methods. See +ClassMethods.set_callback+ for details. # - # define_callbacks :save - # end + # ==== Example # - # class ConfigStorage < Storage - # set_callback :save, :before, :saving_message - # def saving_message - # puts "saving..." - # end - # - # set_callback :save, :after do |object| - # puts "saved" - # end + # class Record + # include ActiveSupport::Callbacks + # define_callbacks :save # # def save # run_callbacks :save do @@ -34,29 +35,7 @@ module ActiveSupport # end # end # - # config = ConfigStorage.new - # config.save - # - # Output: - # saving... - # - save - # saved - # - # Callbacks from parent classes are inherited. - # - # Example: - # class Storage - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # - # set_callback :save, :before, :prepare - # def prepare - # puts "preparing save" - # end - # end - # - # class ConfigStorage < Storage + # class PersonRecord < Record # set_callback :save, :before, :saving_message # def saving_message # puts "saving..." @@ -65,19 +44,12 @@ module ActiveSupport # set_callback :save, :after do |object| # puts "saved" # end - # - # def save - # run_callbacks :save do - # puts "- save" - # end - # end # end # - # config = ConfigStorage.new - # config.save + # person = PersonRecord.new + # person.save # # Output: - # preparing save # saving... # - save # saved @@ -89,11 +61,25 @@ module ActiveSupport extend ActiveSupport::DescendantsTracker end + # Runs the callbacks for the given event. + # + # Calls the before and around callbacks in the order they were set, yields + # the block (if given one), and then runs the after callbacks in reverse order. + # Optionally accepts a key, which will be used to compile an optimized callback + # method for each key. See +ClassMethods.define_callbacks+ for more information. + # + # If the callback chain was halted, returns +false+. Otherwise returns the result + # of the block, or +true+ if no block is given. + # + # run_callbacks :save do + # save + # end + # def run_callbacks(kind, *args, &block) send("_run_#{kind}_callbacks", *args, &block) end - class Callback + class Callback #:nodoc:# @@_callback_sequence = 0 attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter @@ -185,7 +171,11 @@ module ActiveSupport # end filter = <<-RUBY_EVAL unless halted - result = #{@filter} + # This double assignment is to prevent warnings in 1.9.3. I would + # remove the `result` variable, but apparently some other + # generated code is depending on this variable being set sometimes + # and sometimes not. + result = result = #{@filter} halted = (#{chain.config[:terminator]}) end RUBY_EVAL @@ -328,7 +318,7 @@ module ActiveSupport end # An Array with a compile method - class CallbackChain < Array + class CallbackChain < Array #:nodoc:# attr_reader :name, :config def initialize(name, config) @@ -373,18 +363,7 @@ module ActiveSupport end module ClassMethods - # Make the run_callbacks :save method. The generated method takes - # a block that it'll yield to. It'll call the before and around filters - # in order, yield the block, and then run the after filters. - # - # run_callbacks :save do - # save - # end - # - # The run_callbacks :save method can optionally take a key, which - # will be used to compile an optimized callback method for each - # key. See #define_callbacks for more information. - # + # Generate the internal runner method called by +run_callbacks+. def __define_runner(symbol) #:nodoc: body = send("_#{symbol}_callbacks").compile @@ -440,18 +419,42 @@ module ActiveSupport end end - # Set callbacks for a previously defined callback. + # Install a callback for the given event. # - # Syntax: # set_callback :save, :before, :before_meth # set_callback :save, :after, :after_meth, :if => :condition # set_callback :save, :around, lambda { |r| stuff; yield; stuff } # - # If the second argument is not :before, :after or :around then an implicit :before is assumed. - # It means the first example mentioned above can also be written as: + # The second arguments indicates whether the callback is to be run +:before+, + # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This + # means the first example above can also be written as: + # # set_callback :save, :before_meth # - # Use skip_callback to skip any defined one. + # The callback can specified as a symbol naming an instance method; as a proc, + # lambda, or block; as a string to be instance evaluated; or as an object that + # responds to a certain method determined by the <tt>:scope</tt> argument to + # +define_callback+. + # + # If a proc, lambda, or block is given, its body is evaluated in the context + # of the current object. It can also optionally accept the current object as + # an argument. + # + # Before and around callbacks are called in the order that they are set; after + # callbacks are called in the reverse order. + # + # ===== Options + # + # * <tt>:if</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a true value. + # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a false value. + # * <tt>:prepend</tt> - If true, the callback will be prepended to the existing + # chain rather than appended. + # * <tt>:per_key</tt> - A hash with <tt>:if</tt> and <tt>:unless</tt> options; + # see "Per-key conditions" below. + # + # ===== Per-key conditions # # When creating or skipping callbacks, you can specify conditions that # are always the same for a given key. For instance, in Action Pack, @@ -463,7 +466,7 @@ module ActiveSupport # # set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}} # - # Per-Key conditions are evaluated only once per use of a given key. + # Per-key conditions are evaluated only once per use of a given key. # In the case of the above example, you would do: # # run_callbacks(:process_action, action_name) { ... dispatch stuff ... } @@ -490,7 +493,8 @@ module ActiveSupport end end - # Skip a previously defined callback. + # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or <tt>:unless</tt> + # options may be passed in order to control when the callback is skipped. # # class Writer < Person # skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 } @@ -513,7 +517,7 @@ module ActiveSupport end end - # Reset callbacks for a given type. + # Remove all set callbacks for the given event. # def reset_callbacks(symbol) callbacks = send("_#{symbol}_callbacks") @@ -530,68 +534,71 @@ module ActiveSupport __define_runner(symbol) end - # Defines callbacks types: + # Define sets of events in the object lifecycle that support callbacks. # # define_callbacks :validate + # define_callbacks :initialize, :save, :destroy # - # This macro accepts the following options: + # ===== Options # - # * <tt>:terminator</tt> - Indicates when a before filter is considered - # to halted. This is a string to be eval'ed and has the result of the - # very filter available in the <tt>result</tt> variable: + # * <tt>:terminator</tt> - Determines when a before filter will halt the callback + # chain, preventing following callbacks from being called and the event from being + # triggered. This is a string to be eval'ed. The result of the callback is available + # in the <tt>result</tt> variable. # - # define_callbacks :validate, :terminator => "result == false" + # define_callbacks :validate, :terminator => "result == false" # - # In the example above, if any before validate callbacks returns +false+, - # other callbacks are not executed. Defaults to "false", meaning no value - # halts the chain. + # In this example, if any before validate callbacks returns +false+, + # other callbacks are not executed. Defaults to "false", meaning no value + # halts the chain. # # * <tt>:rescuable</tt> - By default, after filters are not executed if - # the given block or a before filter raises an error. Set this option to - # true to change this behavior. + # the given block or a before filter raises an error. By setting this option + # to <tt>true</tt> exception raised by given block is stored and after + # executing all the after callbacks the stored exception is raised. # - # * <tt>:scope</tt> - Indicates which methods should be executed when a class - # is given as callback. Defaults to <tt>[:kind]</tt>. + # * <tt>:scope</tt> - Indicates which methods should be executed when an object + # is used as a callback. # - # class Audit - # def before(caller) - # puts 'Audit: before is called' - # end + # class Audit + # def before(caller) + # puts 'Audit: before is called' + # end # - # def before_save(caller) - # puts 'Audit: before_save is called' - # end - # end + # def before_save(caller) + # puts 'Audit: before_save is called' + # end + # end # - # class Account - # include ActiveSupport::Callbacks + # class Account + # include ActiveSupport::Callbacks # - # define_callbacks :save - # set_callback :save, :before, Audit.new + # define_callbacks :save + # set_callback :save, :before, Audit.new # - # def save - # run_callbacks :save do - # puts 'save in main' - # end - # end - # end + # def save + # run_callbacks :save do + # puts 'save in main' + # end + # end + # end # - # In the above case whenever you save an account the method <tt>Audit#before</tt> will - # be called. On the other hand + # In the above case whenever you save an account the method <tt>Audit#before</tt> will + # be called. On the other hand # - # define_callbacks :save, :scope => [:kind, :name] + # define_callbacks :save, :scope => [:kind, :name] # - # would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling - # <tt>"#{kind}_#{name}"</tt> on the given instance. In this case "kind" is "before" and - # "name" is "save". In this context ":kind" and ":name" have special meanings: ":kind" - # refers to the kind of callback (before/after/around) and ":name" refers to the - # method on which callbacks are being defined. + # would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling + # <tt>#{kind}_#{name}</tt> on the given instance. In this case "kind" is "before" and + # "name" is "save". In this context +:kind+ and +:name+ have special meanings: +:kind+ + # refers to the kind of callback (before/after/around) and +:name+ refers to the + # method on which callbacks are being defined. # - # A declaration like + # A declaration like # - # define_callbacks :save, :scope => [:name] + # define_callbacks :save, :scope => [:name] # - # would call <tt>Audit#save</tt>. + # would call <tt>Audit#save</tt>. # def define_callbacks(*callbacks) config = callbacks.last.is_a?(Hash) ? callbacks.pop : {} diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 3720dbb8b8..080604147d 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,23 +1,34 @@ require 'bigdecimal' + +begin + require 'psych' +rescue LoadError +end + require 'yaml' class BigDecimal YAML_TAG = 'tag:yaml.org,2002:float' YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - yaml_as YAML_TAG - # This emits the number without any scientific notation. # This is better than self.to_f.to_s since it doesn't lose precision. # # Note that reconstituting YAML floats to native floats may lose precision. def to_yaml(opts = {}) + return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? + YAML.quick_emit(nil, opts) do |out| string = to_s out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain) end end + def encode_with(coder) + string = to_s + coder.represent_scalar(nil, YAML_MAPPING[string] || string) + end + def to_d self end diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 3e5d1a2a42..46e9daaa8f 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -2,49 +2,35 @@ require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/reachable' class Class #:nodoc: - # Rubinius - if defined?(Class.__subclasses__) - alias :subclasses :__subclasses__ + begin + ObjectSpace.each_object(Class.new) {} def descendants descendants = [] - __subclasses__.each do |k| - descendants << k - descendants.concat k.descendants + ObjectSpace.each_object(class << self; self; end) do |k| + descendants.unshift k unless k == self end descendants end - else # MRI - begin - ObjectSpace.each_object(Class.new) {} - - def descendants - descendants = [] - ObjectSpace.each_object(class << self; self; end) do |k| - descendants.unshift k unless k == self - end - descendants - end - rescue StandardError # JRuby - def descendants - descendants = [] - ObjectSpace.each_object(Class) do |k| - descendants.unshift k if k < self - end - descendants.uniq! - descendants + rescue StandardError # JRuby + def descendants + descendants = [] + ObjectSpace.each_object(Class) do |k| + descendants.unshift k if k < self end + descendants.uniq! + descendants end + end - # Returns an array with the direct children of +self+. - # - # Integer.subclasses # => [Bignum, Fixnum] - def subclasses - subclasses, chain = [], descendants - chain.each do |k| - subclasses << k unless chain.any? { |c| c > k } - end - subclasses + # Returns an array with the direct children of +self+. + # + # Integer.subclasses # => [Bignum, Fixnum] + def subclasses + subclasses, chain = [], descendants + chain.each do |k| + subclasses << k unless chain.any? { |c| c > k } end + subclasses end end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index bb0f747960..c930abc003 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -101,7 +101,13 @@ module ActiveSupport #:nodoc: self end + def encode_with(coder) + coder.represent_scalar nil, to_str + end + def to_yaml(*args) + return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? + to_str.to_yaml(*args) end end diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb index 2e389b5c12..c7ed931c1b 100644 --- a/activesupport/lib/active_support/json/backends/yaml.rb +++ b/activesupport/lib/active_support/json/backends/yaml.rb @@ -20,8 +20,8 @@ module ActiveSupport json = json.read end YAML.load(convert_json_to_yaml(json)) - rescue *EXCEPTIONS - raise ParseError, "Invalid JSON string" + rescue *EXCEPTIONS => e + raise ParseError, "Invalid JSON string: '%s'" % json end protected @@ -39,7 +39,7 @@ module ActiveSupport if json[pos..scanner.pos-2] =~ DATE_REGEX # found a date, track the exact positions of the quotes so we can # overwrite them with spaces later. - times << pos << scanner.pos + times << pos end quoting = false end @@ -70,7 +70,7 @@ module ActiveSupport 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] = ' ' + chunk.insert(times.shift - scanner.pos - 1, '! ') end chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do ustr = $1 diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index c8cac52027..b2ea196003 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -153,6 +153,12 @@ class Object end end +class Struct + def as_json(options = nil) #:nodoc: + Hash[members.zip(values)] + end +end + class TrueClass AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze def as_json(options = nil) AS_JSON end #:nodoc: diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 5b8c342f4f..ac20988230 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -1,3 +1,8 @@ +begin + require 'psych' +rescue LoadError +end + require 'yaml' YAML.add_builtin_type("omap") do |type, val| diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 8137f8e17e..3da216ac78 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -137,7 +137,17 @@ module ActiveSupport end end + def encode_with(coder) + if coder.respond_to?(:represent_object) + coder.represent_object(nil, utc) + else + coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ")) + end + end + def to_yaml(options = {}) + return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? + utc.to_yaml(options) end diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 7fdcb11465..16570c6aea 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -1,5 +1,6 @@ require 'libxml' require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini LibXML implementation module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index fe2c1b9349..2536b1f33e 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -1,5 +1,6 @@ require 'libxml' require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini LibXML implementation using a SAX-based parser module ActiveSupport @@ -82,4 +83,4 @@ module ActiveSupport end end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index e03a744257..04ec9e8ab8 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -5,6 +5,7 @@ rescue LoadError => e raise e end require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini Nokogiri implementation module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index 25afbfcd1c..93fd3dfe57 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -5,6 +5,7 @@ rescue LoadError => e raise e end require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini Nokogiri implementation using a SAX-based parser module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index 36692af1d3..a13ad10118 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/object/blank' +require 'stringio' # = XmlMini ReXML implementation module ActiveSupport diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index d592973d7a..b38e08a9f4 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -4,10 +4,10 @@ require 'active_support/core_ext/big_decimal' class BigDecimalTest < Test::Unit::TestCase def test_to_yaml - assert_equal("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_equal("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) + assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) + assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) + assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) + assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) end def test_to_d diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 2b86da67fa..5c226c2d09 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -106,11 +106,11 @@ class TimeWithZoneTest < Test::Unit::TestCase end def test_to_yaml - assert_equal "--- 2000-01-01 00:00:00 Z\n", @twz.to_yaml + assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) end def test_ruby_to_yaml - assert_equal "--- \n:twz: 2000-01-01 00:00:00 Z\n", {:twz => @twz}.to_yaml + assert_match(/---\s*\n:twz: 2000-01-01 00:00:00(\.0+)?\s*Z\n/, {:twz => @twz}.to_yaml) end def test_httpdate diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index d2e3efaa6b..a0beb97537 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -57,9 +57,7 @@ class TestJSONDecoding < ActiveSupport::TestCase ActiveSupport.parse_json_times = true silence_warnings do ActiveSupport::JSON.with_backend backend do - assert_nothing_raised do - assert_equal expected, ActiveSupport::JSON.decode(json) - end + assert_equal expected, ActiveSupport::JSON.decode(json) end end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 7469ae70fd..d5fcbf15b7 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -215,6 +215,36 @@ class TestJSONEncoding < Test::Unit::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end + def test_struct_encoding + Struct.new('UserNameAndEmail', :name, :email) + Struct.new('UserNameAndDate', :name, :date) + Struct.new('Custom', :name, :sub) + user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com' + user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01) + custom = Struct::Custom.new 'David', user_birthday + + + json_strings = "" + json_string_and_date = "" + json_custom = "" + + assert_nothing_raised do + json_strings = user_email.to_json + json_string_and_date = user_birthday.to_json + json_custom = custom.to_json + end + + assert_equal({"name" => "David", + "sub" => { + "name" => "David", + "date" => "2010/01/01" }}, JSON.parse(json_custom)) + + assert_equal({"name" => "David", "email" => "sample@example.com"}, + JSON.parse(json_strings)) + + assert_equal({"name" => "David", "date" => "2010/01/01"}, + JSON.parse(json_string_and_date)) + end protected diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index bf61f9e58c..a4e2acbb32 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -1,4 +1,10 @@ require 'abstract_unit' +begin + require 'psych' +rescue LoadError +end + +require 'yaml' class SafeBufferTest < ActiveSupport::TestCase def setup @@ -38,4 +44,20 @@ class SafeBufferTest < ActiveSupport::TestCase new_buffer = @buffer.to_s assert_equal ActiveSupport::SafeBuffer, new_buffer.class end + + def test_to_yaml + str = 'hello!' + buf = ActiveSupport::SafeBuffer.new str + yaml = buf.to_yaml + + assert_match(/^--- #{str}/, yaml) + assert_equal 'hello!', YAML.load(yaml) + end + + def test_nested + str = 'hello!' + data = { 'str' => ActiveSupport::SafeBuffer.new(str) } + yaml = YAML.dump data + assert_equal({'str' => str}, YAML.load(yaml)) + end end diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index 68d406e31c..154355cac1 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -38,6 +38,10 @@ # Note that if you are working on a guide generation will by default process # only that one, so ONLY is rarely used nowadays. # +# LANGUAGE +# Use LANGUAGE when you want to generate translated guides in <tt>source/<LANGUAGE></tt> +# folder (such as <tt>source/es</tt>). Ignore it when generating English guides. +# # EDGE # Set to "1" to indicate generated guides should be marked as edge. This # inserts a badge and changes the preamble of the home page. @@ -63,6 +67,7 @@ module RailsGuides GUIDES_RE = /\.(?:textile|html\.erb)$/ def initialize(output=nil) + @lang = ENV['LANGUAGE'] initialize_dirs(output) create_output_dir_if_needed set_flags_from_environment @@ -76,8 +81,8 @@ module RailsGuides private def initialize_dirs(output) @guides_dir = File.join(File.dirname(__FILE__), '..') - @source_dir = File.join(@guides_dir, "source") - @output_dir = output || File.join(@guides_dir, "output") + @source_dir = File.join(@guides_dir, "source", @lang.to_s) + @output_dir = output || File.join(@guides_dir, "output", @lang.to_s) end def create_output_dir_if_needed diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index f0081b48c0..b7926f3a3b 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -180,7 +180,7 @@ Active Record provides a rich API for accessing data within a database. Below ar <ruby> # find all users named David who are Code Artists and sort by created_at in reverse chronological order - users = User.all(:conditions => { :name => 'David', :occupation => 'Code Artist'}, :order => 'created_at DESC') + users = User.where(:name => 'David', :occupation => 'Code Artist').order('created_at DESC') </ruby> You can learn more about querying an Active Record model in the "Active Record Query Interface":"active_record_querying.html" guide. diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 6e45bf3188..64a68f7592 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -19,8 +19,6 @@ Code examples throughout this guide will refer to one or more of the following m TIP: All of the following models use +id+ as the primary key, unless specified otherwise. -<br /> - <ruby> class Client < ActiveRecord::Base has_one :address @@ -440,7 +438,7 @@ And this will give you a single +Order+ object for each date where there are ord The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) +SELECT * FROM orders GROUP BY date(created_at) ORDER BY created_at </sql> h3. Having @@ -461,6 +459,36 @@ SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15' This will return single order objects for each day, but only for the last month. +h3. Overriding Conditions + +You can specify certain conditions to be excepted by using the +except+ method. + +For example: + +<ruby> +Post.where('id > 10').limit(20).order('id asc').except(:order) +</ruby> + +The SQL that would be executed: + +<sql> +SELECT * FROM posts WHERE id > 10 LIMIT 20 +</sql> + +You can also override conditions using the +only+ method. + +For example: + +<ruby> +Post.where('id > 10').limit(20).order('id desc').only(:order, :where) +</ruby> + +The SQL that would be executed: + +<sql> +SELECT * FROM posts WHERE id > 10 ORDER BY id DESC +</sql> + h3. Readonly Objects Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. @@ -664,7 +692,7 @@ Eager loading is the mechanism for loading the associated records of the objects Consider the following code, which finds 10 clients and prints their postcodes: <ruby> -clients = Client.all(:limit => 10) +clients = Client.limit(10) clients.each do |client| puts client.address.postcode diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 9a1f913ded..c283a9bd99 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1264,7 +1264,7 @@ Active Support adds that functionality to <tt>%</tt> in previous versions of Rub NOTE: Defined in +active_support/core_ext/string/interpolation.rb+. -h4. +starts_with?+ and +ends_width?+ +h4. +starts_with?+ and +ends_with?+ Active Support defines 3rd person aliases of +String#start_with?+ and +String#end_with?+: diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index 972e7ea840..3d7fcdc198 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -1,10 +1,10 @@ h2. AJAX on Rails -This guide covers the built-in Ajax/Javascript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics: +This guide covers the built-in Ajax/JavaScript functionality of Rails (and more); it will enable you to create rich and dynamic AJAX applications with ease! We will cover the following topics: * Quick introduction to AJAX and related technologies -* Handling Javascript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us -* Testing Javascript functionality +* Handling JavaScript the Rails way: Rails helpers, RJS, Prototype and script.aculo.us +* Testing JavaScript functionality endprologue. @@ -12,7 +12,7 @@ h3. Hello AJAX - a Quick Intro If you are a 'show me the code' type of person, you might want to skip this part and jump to the RJS section right away. However, I would really recommend to read it - you'll need the basics of DOM, http requests and other topics discussed here to really understand Ajax on Rails. -h4. Asynchronous Javascript + XML +h4. Asynchronous JavaScript + XML Basic terminology, new style of creating web apps @@ -31,7 +31,7 @@ How do 'standard' and AJAX requests differ, why does this matter for understandi h3. Built-in Rails Helpers -Rails' Javascript framework of choice is "Prototype":http://www.prototypejs.org. Prototype is a generic-purpose Javascript framework that aims to ease the development of dynamic web applications by offering DOM manipulation, AJAX and other Javascript functionality ranging from utility functions to object oriented constructs. It is not specifically written for any language, so Rails provides a set of helpers to enable seamless integration of Prototype with your Rails views. +Rails' JavaScript framework of choice is "Prototype":http://www.prototypejs.org. Prototype is a generic-purpose JavaScript framework that aims to ease the development of dynamic web applications by offering DOM manipulation, AJAX and other JavaScript functionality ranging from utility functions to object oriented constructs. It is not specifically written for any language, so Rails provides a set of helpers to enable seamless integration of Prototype with your Rails views. To get access to these helpers, all you have to do is to include the prototype framework in your pages - typically in your master layout, application.html.erb - like so: <ruby> @@ -136,7 +136,7 @@ link_to_remote "Add new item", :before => "$('progress').show()", :complete => "$('progress').hide()", :success => "display_item_added(request)", - :failure => "display_error(request)", + :failure => "display_error(request)" </ruby> ** *:type* If you want to fire a synchronous request for some obscure reason (blocking the browser while the request is processed and doesn't return a status code), you can use the +:type+ option with the value of +:synchronous+. * Finally, using the +html_options+ parameter you can add HTML attributes to the generated tag. It works like the same parameter of the +link_to+ helper. There are interesting side effects for the +href+ and +onclick+ parameters though: @@ -153,7 +153,7 @@ There are three different ways of adding AJAX forms to your view using Rails Pro * +form_remote_tag+ AJAXifies the form by serializing and sending it's data in the background * +submit_to_remote+ and +button_to_remote+ is more rarely used than the previous two. Rather than creating an AJAX form, you add a button/input -Let's se them in action one by one! +Let's see them in action one by one! h5. +remote_form_for+ @@ -183,7 +183,7 @@ h3. JavaScript the Rails way: RJS In the last section we sent some AJAX requests to the server, and inserted the HTML response into the page (with the +:update+ option). However, sometimes a more complicated interaction with the page is needed, which you can either achieve with JavaScript... or with RJS! You are sending JavaScript instructions to the server in both cases, but while in the former case you have to write vanilla JavaScript, in the second you can code Rails, and sit back while Rails generates the JavaScript for you - so basically RJS is a Ruby DSL to write JavaScript in your Rails code. -h4. Javascript without RJS +h4. JavaScript without RJS First we'll check out how to send JavaScript to the server manually. You are practically never going to need this, but it's interesting to understand what's going on under the hood. @@ -329,9 +329,9 @@ h4. Drag and Drop -h3. Testing Javascript +h3. Testing JavaScript -Javascript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with Javascript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it: +JavaScript testing reminds me the definition of the world 'classic' by Mark Twain: "A classic is something that everybody wants to have read and nobody wants to read." It's similar with JavaScript testing: everyone would like to have it, yet it's not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it: * (Fire)Watir * Selenium @@ -339,4 +339,4 @@ Javascript testing reminds me the definition of the world 'classic' by Mark Twai * Cucumber+Webrat * Mention stuff like screw.unit/jsSpec -Note to self: check out the RailsConf JS testing video
\ No newline at end of file +Note to self: check out the RailsConf JS testing video diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 6e72ae6ead..eee18f1131 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -397,6 +397,15 @@ Some parts of Rails can also be configured externally by supplying environment v * +ENV["RAILS_CACHE_ID"]+ and +ENV["RAILS_APP_VERSION"]+ are used to generate expanded cache keys in Rails' caching code. This allows you to have multiple separate caches from the same application. + +h3. Using Initializer Files + +After loading the framework and any gems and plugins in your application, Rails turns to loading initializers. An initializer is any file of Ruby code stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks, plugins and gems are loaded, such as options to configure settings for these parts. + +NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down. + +TIP: If you have any ordering dependency in your initializers, you can control the load order by naming. For example, +01_critical.rb+ will be loaded before +02_normal.rb+. + h3. Initialization events Rails has 5 initialization events which can be hooked into (listed in order that they are ran): @@ -551,8 +560,7 @@ TIP: If you have any ordering dependency in your initializers, you can control t *+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+. -*+disable_dependency_loading+* - +*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to +true+ and +config.dependency_loading+ is set to +false+. h3. Changelog diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile index 4fec1760c4..3adbbfa7aa 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -396,7 +396,7 @@ This command will generate the +Thud+ application, and then apply the template t Templates don't have to be stored on the local system, the +-m+ option also supports online templates: <shell> - rails new thud -m https://gist.github.com/722911 + rails new thud -m https://gist.github.com/722911.txt </shell> Whilst the final section of this guide doesn't cover how to generate the most awesome template known to man, it will take you through the methods available at your disposal so that you can develop it yourself. These same methods are also available for generators. diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index bb8bf8b240..e47ac7aed6 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -713,12 +713,12 @@ end Then Active Record will look for messages in this order: <ruby> -activerecord.errors.models.admin.attributes.title.blank +activerecord.errors.models.admin.attributes.name.blank activerecord.errors.models.admin.blank -activerecord.errors.models.user.attributes.title.blank +activerecord.errors.models.user.attributes.name.blank activerecord.errors.models.user.blank activerecord.errors.messages.blank -errors.attributes.title.blank +errors.attributes.name.blank errors.messages.blank </ruby> diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index f60d72352d..1d81c8f95b 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -498,7 +498,7 @@ You specify a request-based constraint the same way that you specify a segment c match "photos", :constraints => {:subdomain => "admin"} </ruby> -You can also specify constrains in a block form: +You can also specify constraints in a block form: <ruby> namespace :admin do @@ -598,7 +598,7 @@ You can specify what Rails should route +"/"+ to with the +root+ method: root :to => 'pages#main' </ruby> -You should put the +root+ route at the end of the file. You also need to delete the public/index.html.erb file for the root route to take effect. +You should put the +root+ route at the end of the file. You also need to delete the +public/index.html+ file for the root route to take effect. h3. Customizing Resourceful Routes @@ -633,7 +633,7 @@ You can use the +:constraints+ option to specify a required format on the implic resources :photos, :constraints => {:id => /[A-Z][A-Z][0-9]+/} </ruby> -This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, the router would no longer match +/photos/1+ to this route. Instead, +/photos/RR27+ would match. +This declaration constraints the +:id+ parameter to match the supplied regular expression. So, in this case, the router would no longer match +/photos/1+ to this route. Instead, +/photos/RR27+ would match. You can specify a single constraint to apply to a number of routes by using the block form: diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile index 655c3462d2..6576758856 100644 --- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile +++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile @@ -62,7 +62,13 @@ To force process of all the guides, pass +ALL=1+. It is also recommended that you work with +WARNINGS=1+, this detects duplicate IDs and warns about broken internal links. -h3. HTML validation +If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +LANGUAGE+ environment variable. + +<plain> +rake generate_guides LANGUAGE=es +</plain> + +h3. HTML Validation Please do validate the generated HTML with diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 733c8a755e..5f0889f406 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -229,13 +229,13 @@ $ rake db:test:load Above +rake db:migrate+ runs any pending migrations on the _development_ environment and updates +db/schema.rb+. +rake db:test:load+ recreates the test database from the current +db/schema.rb+. On subsequent attempts, it is a good idea to first run +db:test:prepare+, as it first checks for pending migrations and warns you appropriately. -NOTE: +db:test:prepare+ will fail with an error if +db/schema.rb+ doesn't exists. +NOTE: +db:test:prepare+ will fail with an error if +db/schema.rb+ doesn't exist. h5. Rake Tasks for Preparing your Application for Testing |_.Tasks |_.Description| |+rake db:test:clone+ |Recreate the test database from the current environment's database schema| -|+rake db:test:clone_structure+ |Recreate the test databases from the development structure| +|+rake db:test:clone_structure+ |Recreate the test database from the development structure| |+rake db:test:load+ |Recreate the test database from the current +schema.rb+| |+rake db:test:prepare+ |Check for pending migrations and load the test schema| |+rake db:test:purge+ |Empty the test database.| @@ -512,12 +512,12 @@ After a request has been made by using one of the 5 methods (+get+, +post+, etc. As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name, except for +assigns+. For example: <ruby> - flash["gordon"] flash[:gordon] - session["shmession"] session[:shmession] - cookies["are_good_for_u"] cookies[:are_good_for_u] +flash["gordon"] flash[:gordon] +session["shmession"] session[:shmession] +cookies["are_good_for_u"] cookies[:are_good_for_u] # Because you can't use assigns[:something] for historical reasons: - assigns["something"] assigns(:something) +assigns["something"] assigns(:something) </ruby> h4. Instance Variables Available diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 2b32f7edf1..443d6f47ad 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -6,7 +6,7 @@ require 'rails/script_rails_loader' Rails::ScriptRailsLoader.exec_script_rails! require 'rails/ruby_version_check' -Signal.trap("INT") { puts; exit } +Signal.trap("INT") { puts; exit(1) } if ARGV.first == 'plugin' ARGV.shift diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 46363d7921..02ccdf8060 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -62,6 +62,7 @@ when 'application', 'runner' when 'new' puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n" puts "Type 'rails' for help." + exit(1) when '--version', '-v' ARGV.unshift '--version' @@ -91,4 +92,5 @@ In addition to those, there are: All commands can be run with -h for more information. EOT + exit(1) end diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb index 47c6752ca3..3b57b925ba 100644 --- a/railties/lib/rails/commands/application.rb +++ b/railties/lib/rails/commands/application.rb @@ -15,4 +15,16 @@ require 'rubygems' if ARGV.include?("--dev") require 'rails/generators' require 'rails/generators/rails/app/app_generator' +module Rails + module Generators + class AppGenerator + # We want to exit on failure to be kind to other libraries + # This is only when accessing via CLI + def self.exit_on_failure? + true + end + end + end +end + Rails::Generators::AppGenerator.start diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 14d245ab2e..300fd6da05 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -1,4 +1,10 @@ require 'erb' + +begin + require 'psych' +rescue LoadError +end + require 'yaml' require 'optparse' require 'rbconfig' diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 1d81e08cd3..6e5e842370 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -5,19 +5,20 @@ require 'rbconfig' require 'rails/engine/railties' module Rails - # Rails::Engine allows you to wrap a specific Rails application and share it accross - # different applications. Since Rails 3.0, every Rails::Application is nothing - # more than an Engine, allowing you to share it very easily. + # Rails::Engine allows you to wrap a specific Rails application and share it across + # different applications. Since Rails 3.0, every <tt>Rails::Application</tt> is nothing + # more than an engine, allowing you to share it very easily. # - # Any Rails::Engine is also a Rails::Railtie, so the same methods (like rake_tasks and - # generators) and configuration available in the latter can also be used in the former. + # Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same methods + # (like <tt>rake_tasks</tt> and +generators+) and configuration available in the + # latter can also be used in the former. # # == Creating an Engine # - # In Rails versions before to 3.0, your gems automatically behaved as Engine, however + # In Rails versions prior to 3.0, your gems automatically behaved as engines, however, # this coupled Rails to Rubygems. Since Rails 3.0, if you want a gem to automatically - # behave as Engine, you have to specify an Engine for it somewhere inside your plugin - # lib folder (similar to how we specify a Railtie): + # behave as an engine, you have to specify an +Engine+ for it somewhere inside + # your plugin's +lib+ folder (similar to how we specify a +Railtie+): # # # lib/my_engine.rb # module MyEngine @@ -25,16 +26,17 @@ module Rails # end # end # - # Then ensure that this file is loaded at the top of your config/application.rb (or in - # your Gemfile) and it will automatically load models, controllers and helpers - # inside app, load routes at "config/routes.rb", load locales at "config/locales/*", - # load tasks at "lib/tasks/*". + # Then ensure that this file is loaded at the top of your <tt>config/application.rb</tt> + # (or in your +Gemfile+) and it will automatically load models, controllers and helpers + # inside +app+, load routes at <tt>config/routes.rb</tt>, load locales at + # <tt>config/locales/*</tt>, and load tasks at <tt>lib/tasks/*</tt>. # # == Configuration # - # Besides the Railtie configuration which is shared across the application, in a - # Rails::Engine you can access autoload_paths, eager_load_paths and autoload_once_paths, - # which differently from a Railtie, are scoped to the current Engine. + # Besides the +Railtie+ configuration which is shared across the application, in a + # <tt>Rails::Engine</tt> you can access <tt>autoload_paths</tt>, <tt>eager_load_paths</tt> + # and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to + # the current engine. # # Example: # @@ -49,7 +51,7 @@ module Rails # # == Generators # - # You can set up generators for engine with config.generators method: + # You can set up generators for engines with <tt>config.generators</tt> method: # # class MyEngine < Rails::Engine # config.generators do |g| @@ -59,7 +61,7 @@ module Rails # end # end # - # You can also set generators for application by using config.app_generators: + # You can also set generators for an application by using <tt>config.app_generators</tt>: # # class MyEngine < Rails::Engine # # note that you can also pass block to app_generators in the same way you @@ -69,25 +71,25 @@ module Rails # # == Paths # - # Since Rails 3.0, both your Application and Engines do not have hardcoded paths. - # This means that you are not required to place your controllers at "app/controllers", + # Since Rails 3.0, both your application and engines do not have hardcoded paths. + # This means that you are not required to place your controllers at <tt>app/controllers</tt>, # but in any place which you find convenient. # - # For example, let's suppose you want to lay your controllers at lib/controllers, all - # you need to do is: + # For example, let's suppose you want to place your controllers in <tt>lib/controllers</tt>. + # All you would need to do is: # # class MyEngine < Rails::Engine # paths["app/controllers"] = "lib/controllers" # end # - # You can also have your controllers being loaded from both "app/controllers" and - # "lib/controllers": + # You can also have your controllers loaded from both <tt>app/controllers</tt> and + # <tt>lib/controllers</tt>: # # class MyEngine < Rails::Engine # paths["app/controllers"] << "lib/controllers" # end # - # The available paths in an Engine are: + # The available paths in an engine are: # # class MyEngine < Rails::Engine # paths["app"] #=> ["app"] @@ -103,16 +105,17 @@ module Rails # paths["config/routes"] #=> ["config/routes.rb"] # end # - # Your Application class adds a couple more paths to this set. And as in your Application, - # all folders under "app" are automatically added to the load path. So if you have - # "app/observers", it's added by default. + # Your <tt>Application</tt> class adds a couple more paths to this set. And as in your + # <tt>Application</tt>,all folders under +app+ are automatically added to the load path. + # So if you have <tt>app/observers</tt>, it's added by default. # # == Endpoint # - # Engine can be also a rack application. It can be useful if you have a rack application that - # you would like to wrap with Engine and provide some of the Engine's features. + # An engine can be also a rack application. It can be useful if you have a rack application that + # you would like to wrap with +Engine+ and provide some of the +Engine+'s features. + # + # To do that, use the +endpoint+ method: # - # To do that, use endpoint method: # module MyEngine # class Engine < Rails::Engine # endpoint MyRackApplication @@ -121,14 +124,14 @@ module Rails # # Now you can mount your engine in application's routes just like that: # - # MyRailsApp::Application.routes.draw do - # mount MyEngine::Engine => "/engine" - # end + # MyRailsApp::Application.routes.draw do + # mount MyEngine::Engine => "/engine" + # end # # == Middleware stack # - # As Engine can now be rack endpoint, it can also have a middleware stack. The usage is exactly - # the same as in application: + # As an engine can now be rack endpoint, it can also have a middleware stack. The usage is exactly + # the same as in <tt>Application</tt>: # # module MyEngine # class Engine < Rails::Engine @@ -138,8 +141,8 @@ module Rails # # == Routes # - # If you don't specify endpoint, routes will be used as default endpoint. You can use them - # just like you use application's routes: + # If you don't specify an endpoint, routes will be used as the default endpoint. You can use them + # just like you use an application's routes: # # # ENGINE/config/routes.rb # MyEngine::Engine.routes.draw do @@ -148,30 +151,31 @@ module Rails # # == Mount priority # - # Note that now there can be more than one router in you application and it's better to avoid - # passing requests through many routers. Consider such situation: + # Note that now there can be more than one router in your application, and it's better to avoid + # passing requests through many routers. Consider this situation: # # MyRailsApp::Application.routes.draw do # mount MyEngine::Engine => "/blog" # match "/blog/omg" => "main#omg" # end # - # MyEngine is mounted at "/blog" path and additionaly "/blog/omg" points application's controller. - # In such situation request to "/blog/omg" will go through MyEngine and if there is no such route - # in Engine's routes, it will be dispatched to "main#omg". It's much better to swap that: + # +MyEngine+ is mounted at <tt>/blog</tt>, and <tt>/blog/omg</tt> points to application's + # controller. In such a situation, requests to <tt>/blog/omg</tt> will go through +MyEngine+, + # and if there is no such route in +Engine+'s routes, it will be dispatched to <tt>main#omg</tt>. + # It's much better to swap that: # # MyRailsApp::Application.routes.draw do # match "/blog/omg" => "main#omg" # mount MyEngine::Engine => "/blog" # end # - # Now, Engine will get only requests that were not handled by application. + # Now, +Engine+ will get only requests that were not handled by +Application+. # # == Asset path # - # When you use engine with its own public directory, you will probably want to copy or symlink it - # to application's public directory. To simplify generating paths for assets, you can set asset_path - # for an Engine: + # When you use +Engine+ with its own public directory, you will probably want to copy or symlink it + # to application's public directory. To simplify generating paths for assets, you can set <tt>asset_path</tt> + # for an engine: # # module MyEngine # class Engine < Rails::Engine @@ -179,27 +183,30 @@ module Rails # end # end # - # With such config, asset paths will be automatically modified inside Engine: - # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg" + # With such a config, asset paths will be automatically modified inside +Engine+: + # + # image_path("foo.jpg") #=> "/my_engine/images/foo.jpg" # # == Serving static files # - # By default, rails use ActionDispatch::Static to serve static files in development mode. This is ok - # while you develop your application, but when you want to deploy it, assets from engine will not be + # By default, Rails uses <tt>ActionDispatch::Static</tt> to serve static files in development mode. This is ok + # while you develop your application, but when you want to deploy it, assets from an engine will not be # served by default. You should choose one of the two following strategies: # # * enable serving static files by setting config.serve_static_assets to true - # * copy engine's public files to application's public folder with rake ENGINE_NAME:install:assets, for example - # rake my_engine:install:assets + # * copy engine's public files to application's public folder with <tt>rake ENGINE_NAME:install:assets</tt>, for example + # <tt>rake my_engine:install:assets</tt> # # == Engine name # - # There are some places where engine's name is used: - # * routes: when you mount engine with mount(MyEngine::Engine => '/my_engine'), it's used as default :as option - # * some of the rake tasks are based on engine name, e.g. my_engine:install:migrations, my_engine:install:assets + # There are some places where an Engine's name is used: + # * routes: when you mount an Engine with <tt>mount(MyEngine::Engine => '/my_engine')</tt>, + # it's used as default :as option + # * some of the rake tasks are based on engine name, e.g. <tt>my_engine:install:migrations</tt>, + # <tt>my_engine:install:assets</tt> # - # Engine name is set by default based on class name. For MyEngine::Engine it will be my_engine_engine. - # You can change it manually it manually using engine_name method: + # Engine name is set by default based on class name. For <tt>MyEngine::Engine</tt> it will be + # <tt>my_engine_engine</tt>. You can change it manually it manually using the <tt>engine_name</tt> method: # # module MyEngine # class Engine < Rails::Engine @@ -209,12 +216,12 @@ module Rails # # == Isolated Engine # - # Normally when you create controllers, helpers and models inside engine, they are treated - # as they were created inside the application. This means all applications helpers and named routes - # will be available to your engine controllers. + # Normally when you create controllers, helpers and models inside an engine, they are treated + # as they were created inside the application. This means all application helpers and named routes + # will be available to your engine's controllers. # - # However, sometimes you want to isolate your engine from the application, specially if your engine - # have its own router. To do that, you simply need to call +isolate_namespace+. This method requires + # However, sometimes you want to isolate your engine from the application, especially if your engine + # has its own router. To do that, you simply need to call +isolate_namespace+. This method requires # you to pass a module where all your controllers, helpers and models should be nested to: # # module MyEngine @@ -223,7 +230,8 @@ module Rails # end # end # - # With such Engine, everything that is inside MyEngine module, will be isolated from application. + # With such an engine, everything that is inside the +MyEngine+ module will be isolated from + # the application. # # Consider such controller: # @@ -232,24 +240,24 @@ module Rails # end # end # - # If engine is marked as isolated, FooController has access only to helpers from engine and - # url_helpers from MyEngine::Engine.routes. + # If an engine is marked as isolated, +FooController+ has access only to helpers from +Engine+ and + # <tt>url_helpers</tt> from <tt>MyEngine::Engine.routes</tt>. # - # The next thing that changes in isolated engine is routes behaviour. Normally, when you namespace - # your controllers, you also need to do namespace all your routes. With isolated engine, + # The next thing that changes in isolated engines is the behaviour of routes. Normally, when you namespace + # your controllers, you also need to do namespace all your routes. With an isolated engine, # the namespace is applied by default, so you can ignore it in routes: # # MyEngine::Engine.routes.draw do # resources :articles # end - # - # The routes above will automatically point to MyEngine::ApplicationContoller. Further more, you don't - # need to use longer url helpers like "my_engine_articles_path". Instead, you shuold simply use - # articles_path as you would do with your application. - # - # To make that behaviour consistent with other parts of framework, isolated engine has influence also on - # ActiveModel::Naming. When you use namespaced model, like MyEngine::Article, it will normally - # use the prefix "my_engine". In isolated engine, the prefix will be ommited in url helpers and + # + # The routes above will automatically point to <tt>MyEngine::ApplicationContoller</tt>. Furthermore, you don't + # need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you shuold simply use + # <tt>articles_path</tt> as you would do with your application. + # + # To make that behaviour consistent with other parts of the framework, an isolated engine also has influence on + # <tt>ActiveModel::Naming</tt>. When you use a namespaced model, like <tt>MyEngine::Article</tt>, it will normally + # use the prefix "my_engine". In an isolated engine, the prefix will be ommited in url helpers and # form fields for convenience. # # polymorphic_url(MyEngine::Article.new) #=> "articles_path" @@ -264,9 +272,9 @@ module Rails # # == Using Engine's routes outside Engine # - # Since now you can mount engine inside application's routes, you do not have direct access to engine's - # url_helpers inside application. When you mount Engine in application's routes, a special helper is - # created to allow you to do that. Consider such scenario: + # Since you can now mount an engine inside application's routes, you do not have direct access to +Engine+'s + # <tt>url_helpers</tt> inside +Application+. When you mount an engine in an application's routes, a special helper is + # created to allow you to do that. Consider such a scenario: # # # APP/config/routes.rb # MyApplication::Application.routes.draw do @@ -274,7 +282,7 @@ module Rails # match "/foo" => "foo#index" # end # - # Now, you can use my_engine helper inside your application: + # Now, you can use the <tt>my_engine</tt> helper inside your application: # # class FooController < ApplicationController # def index @@ -282,7 +290,7 @@ module Rails # end # end # - # There is also 'main_app' helper that gives you access to application's routes inside Engine: + # There is also a <tt>main_app</tt> helper that gives you access to application's routes inside Engine: # # module MyEngine # class BarController @@ -292,34 +300,34 @@ module Rails # end # end # - # Note that the :as option given to mount takes the engine_name as default, so most of the time - # you can simply ommit it. + # Note that the <tt>:as</tt> option given to mount takes the <tt>engine_name</tT> as default, so most of the time + # you can simply omit it. # - # Finally, if you want to generate url to engine's route using polymorphic_url, you also need + # Finally, if you want to generate a url to an engine's route using <tt>polymorphic_url</tt>, you also need # to pass the engine helper. Let's say that you want to create a form pointing to one of the # engine's routes. All you need to do is pass the helper as the first element in array with # attributes for url: # - # form_for([my_engine, @user]) + # form_for([my_engine, @user]) # - # This code will use my_engine.user_path(@user) to generate the proper route. + # This code will use <tt>my_engine.user_path(@user)</tt> to generate the proper route. # # == Migrations & seed data # - # Engines can have their own migrations. Default path for migrations is exactly the same - # as in application: db/migrate + # Engines can have their own migrations. The default path for migrations is exactly the same + # as in application: <tt>db/migrate</tt> # # To use engine's migrations in application you can use rake task, which copies them to # application's dir: # # rake ENGINE_NAME:install:migrations # - # Note that some of the migrations may be skipped if migration with the same name already exists - # in application. In such situation you must decide whether to leave that migration or rename the + # Note that some of the migrations may be skipped if a migration with the same name already exists + # in application. In such a situation you must decide whether to leave that migration or rename the # migration in application and rerun copying migrations. # # If your engine has migrations, you may also want to prepare data for the database in - # seeds.rb file. You can load that data using load_seed method, e.g. + # the <tt>seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g. # # MyEngine::Engine.load_seed # diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 7766050632..ab7ed4eb9e 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -112,12 +112,7 @@ module Rails end def database_gemfile_entry - entry = "" - unless options[:skip_active_record] - entry = "gem '#{gem_for_database}'" - entry << ", :require => '#{require_for_database}'" if require_for_database - end - entry + options[:skip_active_record] ? "" : "gem '#{gem_for_database}'" end def rails_gemfile_entry @@ -150,19 +145,12 @@ gem 'rails', '#{Rails::VERSION::STRING}' case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" - when "sqlite3" then "sqlite3-ruby" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" else options[:database] end end - def require_for_database - case options[:database] - when "sqlite3" then "sqlite3" - end - end - def bundle_if_dev_or_edge bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') run "#{bundle_command} install" if dev_or_edge? diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 322baf766f..7d5a865b80 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -17,7 +17,7 @@ source 'http://rubygems.org' # Bundle the extra gems: # gem 'bj' # gem 'nokogiri' -# gem 'sqlite3-ruby', :require => 'sqlite3' +# gem 'sqlite3' # gem 'aws-s3', :require => 'aws/s3' # Bundle gems for the local environment. Make sure to diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README index fe7013d52d..9f0f1d0e38 100644 --- a/railties/lib/rails/generators/rails/app/templates/README +++ b/railties/lib/rails/generators/rails/app/templates/README @@ -91,7 +91,7 @@ mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example: class WeblogController < ActionController::Base def index - @posts = Post.find(:all) + @posts = Post.all debugger end end @@ -139,7 +139,7 @@ To reload your controllers and models after launching the console run <tt>reload!</tt> More information about irb can be found at: -link:http://www.rubycentral.com/pickaxe/irb.html +link:http://www.rubycentral.org/pickaxe/irb.html == dbconsole diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml index 025d62a8d8..90d87cc295 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml @@ -1,5 +1,5 @@ # SQLite version 3.x -# gem install sqlite3-ruby (not necessary on OS X Leopard) +# gem install sqlite3 development: adapter: sqlite3 database: db/development.sqlite3 diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 9461589ff5..8b1aed974f 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -16,7 +16,7 @@ module Rails end def readme - copy_file "README.rdoc" + template "README.rdoc" end def gemfile @@ -44,7 +44,7 @@ module Rails end def config - template "config/routes.rb" if mountable? + template "config/routes.rb" if full? end def test diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb index 42ddf380d8..8e158d5831 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb @@ -1,3 +1,6 @@ +<% if mountable? -%> <%= camelized %>::Engine.routes.draw do - +<% else -%> +Rails.application.routes.draw do +<% end -%> end diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index b3dc1f894c..32acc66f10 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -21,7 +21,7 @@ module Rails request = ActionDispatch::Request.new(env) path = request.fullpath - info "\n\nStarted #{env["REQUEST_METHOD"]} \"#{path}\" " \ + info "\n\nStarted #{request.request_method} \"#{path}\" " \ "for #{request.ip} at #{Time.now.to_default_s}" end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 0fe5cdc4a8..018c2fa6bf 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -64,6 +64,23 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "things-43/config/application.rb", /^module Things43$/ end + def test_application_new_exits_with_non_zero_code_on_invalid_application_name + # TODO: Suppress the output of this (it's because of a Thor::Error) + `rails new test` + assert_equal false, $?.success? + end + + def test_application_new_exits_with_message_and_non_zero_code_when_generating_inside_existing_rails_directory + app_root = File.join(destination_root, 'myfirstapp') + run_generator [app_root] + output = nil + Dir.chdir(app_root) do + output = `rails new mysecondapp` + end + assert_equal "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\nType 'rails' for help.\n", output + assert_equal false, $?.success? + end + def test_application_name_is_detected_if_it_exists_and_app_folder_renamed app_root = File.join(destination_root, "myapp") app_moved_root = File.join(destination_root, "myapp_moved") @@ -106,7 +123,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_database_is_added_by_default run_generator assert_file "config/database.yml", /sqlite3/ - assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/ + assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ end def test_config_another_database diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 2a9e8046b8..2a8b337144 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -38,8 +38,10 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "things-43/lib/things-43.rb", /module Things43/ end - def test_generating_test_files + def test_generating_without_options run_generator + assert_file "README.rdoc", /Bukkits/ + assert_no_file "config/routes.rb" assert_file "test/test_helper.rb" assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/ end @@ -66,7 +68,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_database_entry_is_assed_by_default_in_full_mode run_generator([destination_root, "--full"]) assert_file "test/dummy/config/database.yml", /sqlite/ - assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/ + assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ end def test_config_another_database @@ -151,6 +153,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_creating_engine_in_full_mode run_generator [destination_root, "--full"] + assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ end |